diff options
Diffstat (limited to 'src')
412 files changed, 34528 insertions, 31915 deletions
diff --git a/src/bancache.cpp b/src/bancache.cpp index 52449e55e..13e4dc7c7 100644 --- a/src/bancache.cpp +++ b/src/bancache.cpp @@ -18,155 +18,89 @@ */ -/* $Core */ - #include "inspircd.h" -#include "bancache.h" -BanCacheHit *BanCacheManager::AddHit(const std::string &ip, const std::string &type, const std::string &reason) +BanCacheHit::BanCacheHit(const std::string& type, const std::string& reason, time_t seconds) + : Type(type) + , Reason(reason) + , Expiry(ServerInstance->Time() + seconds) { - BanCacheHit *b; - - if (this->BanHash->find(ip) != this->BanHash->end()) // can't have two cache entries on the same IP, sorry.. - return NULL; - - b = new BanCacheHit(ip, type, reason); - - this->BanHash->insert(std::make_pair(ip, b)); - return b; } BanCacheHit *BanCacheManager::AddHit(const std::string &ip, const std::string &type, const std::string &reason, time_t seconds) { - BanCacheHit *b; - - if (this->BanHash->find(ip) != this->BanHash->end()) // can't have two cache entries on the same IP, sorry.. + BanCacheHit*& b = BanHash[ip]; + if (b != NULL) // can't have two cache entries on the same IP, sorry.. return NULL; - b = new BanCacheHit(ip, type, reason, seconds); - - this->BanHash->insert(std::make_pair(ip, b)); + b = new BanCacheHit(type, reason, (seconds ? seconds : 86400)); return b; } BanCacheHit *BanCacheManager::GetHit(const std::string &ip) { - BanCacheHash::iterator i = this->BanHash->find(ip); + BanCacheHash::iterator i = this->BanHash.find(ip); - if (i == this->BanHash->end()) + if (i == this->BanHash.end()) return NULL; // free and safe - else - { - if (ServerInstance->Time() > i->second->Expiry) - { - ServerInstance->Logs->Log("BANCACHE", DEBUG, "Hit on " + ip + " is out of date, removing!"); - RemoveHit(i->second); - return NULL; // out of date - } - return i->second; // hit. - } + if (RemoveIfExpired(i)) + return NULL; // expired + + return i->second; // hit. } -bool BanCacheManager::RemoveHit(BanCacheHit *b) +bool BanCacheManager::RemoveIfExpired(BanCacheHash::iterator& it) { - BanCacheHash::iterator i; - - if (!b) - return false; // I don't think so. - - i = this->BanHash->find(b->IP); - - if (i == this->BanHash->end()) - { - // err.. - ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCacheManager::RemoveHit(): I got asked to remove a hit that wasn't in the hash(?)"); - } - else - { - this->BanHash->erase(i); - } + if (ServerInstance->Time() < it->second->Expiry) + return false; - delete b; + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "Hit on " + it->first + " is out of date, removing!"); + delete it->second; + it = BanHash.erase(it); return true; } -unsigned int BanCacheManager::RemoveEntries(const std::string &type, bool positive) +void BanCacheManager::RemoveEntries(const std::string& type, bool positive) { - int removed = 0; - - BanCacheHash::iterator safei; - if (positive) - ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCacheManager::RemoveEntries(): Removing positive hits for " + type); + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCacheManager::RemoveEntries(): Removing positive hits for " + type); else - ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCacheManager::RemoveEntries(): Removing negative hits for " + type); + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCacheManager::RemoveEntries(): Removing all negative hits"); - for (BanCacheHash::iterator n = BanHash->begin(); n != BanHash->end(); ) + for (BanCacheHash::iterator i = BanHash.begin(); i != BanHash.end(); ) { - safei = n; - safei++; + if (RemoveIfExpired(i)) + continue; // updates the iterator if expired - BanCacheHit *b = n->second; + BanCacheHit* b = i->second; + bool remove = false; - /* Safe to delete items here through iterator 'n' */ - if (b->Type == type || !positive) // if removing negative hits, ignore type.. + if (positive) { - if ((positive && !b->Reason.empty()) || b->Reason.empty()) - { - /* we need to remove this one. */ - ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCacheManager::RemoveEntries(): Removing a hit on " + b->IP); - delete b; - BanHash->erase(n); // WORD TO THE WISE: don't use RemoveHit here, because we MUST remove the iterator in a safe way. - removed++; - } + // when removing positive hits, remove only if the type matches + remove = b->IsPositive() && (b->Type == type); + } + else + { + // when removing negative hits, remove all of them + remove = !b->IsPositive(); } - /* End of safe section */ - n = safei; - } - - - return removed; -} - -void BanCacheManager::RehashCache() -{ - BanCacheHash* NewHash = new BanCacheHash(); - - BanCacheHash::iterator safei; - for (BanCacheHash::iterator n = BanHash->begin(); n != BanHash->end(); ) - { - safei = n; - safei++; - - /* Safe to delete items here through iterator 'n' */ - BanCacheHit *b = n->second; - - if (ServerInstance->Time() > b->Expiry) + if (remove) { /* we need to remove this one. */ + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCacheManager::RemoveEntries(): Removing a hit on " + i->first); delete b; - BanHash->erase(n); // WORD TO THE WISE: don't use RemoveHit here, because we MUST remove the iterator in a safe way. + i = BanHash.erase(i); } else - { - /* Actually inserts a std::pair */ - NewHash->insert(*n); - } - - /* End of safe section */ - - n = safei; + ++i; } - - delete BanHash; - BanHash = NewHash; } BanCacheManager::~BanCacheManager() { - for (BanCacheHash::iterator n = BanHash->begin(); n != BanHash->end(); ++n) + for (BanCacheHash::iterator n = BanHash.begin(); n != BanHash.end(); ++n) delete n->second; - delete BanHash; } diff --git a/src/base.cpp b/src/base.cpp index 66a3cb140..f698bad2e 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -23,26 +23,32 @@ #include "inspircd.h" #include "base.h" #include <time.h> +#ifdef INSPIRCD_ENABLE_RTTI #include <typeinfo> +#endif classbase::classbase() { - if (ServerInstance && ServerInstance->Logs) - ServerInstance->Logs->Log("CULLLIST", DEBUG, "classbase::+ @%p", (void*)this); + if (ServerInstance) + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "classbase::+ @%p", (void*)this); } CullResult classbase::cull() { - if (ServerInstance && ServerInstance->Logs) - ServerInstance->Logs->Log("CULLLIST", DEBUG, "classbase::-%s @%p", + if (ServerInstance) +#ifdef INSPIRCD_ENABLE_RTTI + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "classbase::-%s @%p", typeid(*this).name(), (void*)this); +#else + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "classbase::- @%p", (void*)this); +#endif return CullResult(); } classbase::~classbase() { - if (ServerInstance && ServerInstance->Logs) - ServerInstance->Logs->Log("CULLLIST", DEBUG, "classbase::~ @%p", (void*)this); + if (ServerInstance) + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "classbase::~ @%p", (void*)this); } CullResult::CullResult() @@ -73,15 +79,15 @@ refcountbase::refcountbase() : refcount(0) refcountbase::~refcountbase() { - if (refcount && ServerInstance && ServerInstance->Logs) - ServerInstance->Logs->Log("CULLLIST", DEBUG, "refcountbase::~ @%p with refcount %d", + if (refcount && ServerInstance) + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "refcountbase::~ @%p with refcount %d", (void*)this, refcount); } usecountbase::~usecountbase() { - if (usecount && ServerInstance && ServerInstance->Logs) - ServerInstance->Logs->Log("CULLLIST", DEBUG, "usecountbase::~ @%p with refcount %d", + if (usecount && ServerInstance) + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "usecountbase::~ @%p with refcount %d", (void*)this, usecount); } @@ -89,7 +95,13 @@ ServiceProvider::~ServiceProvider() { } -ExtensionItem::ExtensionItem(const std::string& Key, Module* mod) : ServiceProvider(mod, Key, SERVICE_METADATA) +void ServiceProvider::RegisterService() +{ +} + +ExtensionItem::ExtensionItem(const std::string& Key, ExtensibleType exttype, Module* mod) + : ServiceProvider(mod, Key, SERVICE_METADATA) + , type(exttype) { } @@ -132,6 +144,12 @@ void* ExtensionItem::unset_raw(Extensible* container) return rv; } +void ExtensionItem::RegisterService() +{ + if (!ServerInstance->Extensions.Register(this)) + throw ModuleException("Extension already exists: " + name); +} + bool ExtensionManager::Register(ExtensionItem* item) { return types.insert(std::make_pair(item->name, item)).second; @@ -139,10 +157,10 @@ bool ExtensionManager::Register(ExtensionItem* item) void ExtensionManager::BeginUnregister(Module* module, std::vector<reference<ExtensionItem> >& list) { - std::map<std::string, reference<ExtensionItem> >::iterator i = types.begin(); + ExtMap::iterator i = types.begin(); while (i != types.end()) { - std::map<std::string, reference<ExtensionItem> >::iterator me = i++; + ExtMap::iterator me = i++; ExtensionItem* item = me->second; if (item->creator == module) { @@ -154,7 +172,7 @@ void ExtensionManager::BeginUnregister(Module* module, std::vector<reference<Ext ExtensionItem* ExtensionManager::GetItem(const std::string& name) { - std::map<std::string, reference<ExtensionItem> >::iterator i = types.find(name); + ExtMap::iterator i = types.find(name); if (i == types.end()) return NULL; return i->second; @@ -168,41 +186,41 @@ void Extensible::doUnhookExtensions(const std::vector<reference<ExtensionItem> > ExtensibleStore::iterator e = extensions.find(item); if (e != extensions.end()) { - item->free(e->second); + item->free(this, e->second); extensions.erase(e); } } } -static struct DummyExtensionItem : LocalExtItem -{ - DummyExtensionItem() : LocalExtItem("", NULL) {} - void free(void*) {} -} dummy; - Extensible::Extensible() + : culled(false) { - extensions[&dummy] = NULL; } CullResult Extensible::cull() { + FreeAllExtItems(); + culled = true; + return classbase::cull(); +} + +void Extensible::FreeAllExtItems() +{ for(ExtensibleStore::iterator i = extensions.begin(); i != extensions.end(); ++i) { - i->first->free(i->second); + i->first->free(this, i->second); } extensions.clear(); - return classbase::cull(); } Extensible::~Extensible() { - if (!extensions.empty() && ServerInstance && ServerInstance->Logs) - ServerInstance->Logs->Log("CULLLIST", DEBUG, - "Extensible destructor called without cull @%p", (void*)this); + if ((!extensions.empty() || !culled) && ServerInstance) + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "Extensible destructor called without cull @%p", (void*)this); } -LocalExtItem::LocalExtItem(const std::string& Key, Module* mod) : ExtensionItem(Key, mod) +LocalExtItem::LocalExtItem(const std::string& Key, ExtensibleType exttype, Module* mod) + : ExtensionItem(Key, exttype, mod) { } @@ -219,8 +237,10 @@ void LocalExtItem::unserialize(SerializeFormat format, Extensible* container, co { } -LocalStringExt::LocalStringExt(const std::string& Key, Module* Owner) - : SimpleExtItem<std::string>(Key, Owner) { } +LocalStringExt::LocalStringExt(const std::string& Key, ExtensibleType exttype, Module* Owner) + : SimpleExtItem<std::string>(Key, exttype, Owner) +{ +} LocalStringExt::~LocalStringExt() { @@ -228,12 +248,19 @@ LocalStringExt::~LocalStringExt() std::string LocalStringExt::serialize(SerializeFormat format, const Extensible* container, void* item) const { - if (item && format == FORMAT_USER) + if ((item) && (format != FORMAT_NETWORK)) return *static_cast<std::string*>(item); return ""; } -LocalIntExt::LocalIntExt(const std::string& Key, Module* mod) : LocalExtItem(Key, mod) +void LocalStringExt::unserialize(SerializeFormat format, Extensible* container, const std::string& value) +{ + if (format != FORMAT_NETWORK) + set(container, value); +} + +LocalIntExt::LocalIntExt(const std::string& Key, ExtensibleType exttype, Module* mod) + : LocalExtItem(Key, exttype, mod) { } @@ -243,11 +270,17 @@ LocalIntExt::~LocalIntExt() std::string LocalIntExt::serialize(SerializeFormat format, const Extensible* container, void* item) const { - if (format != FORMAT_USER) + if (format == FORMAT_NETWORK) return ""; return ConvToStr(reinterpret_cast<intptr_t>(item)); } +void LocalIntExt::unserialize(SerializeFormat format, Extensible* container, const std::string& value) +{ + if (format != FORMAT_NETWORK) + set(container, ConvToInt(value)); +} + intptr_t LocalIntExt::get(const Extensible* container) const { return reinterpret_cast<intptr_t>(get_raw(container)); @@ -261,11 +294,12 @@ intptr_t LocalIntExt::set(Extensible* container, intptr_t value) return reinterpret_cast<intptr_t>(unset_raw(container)); } -void LocalIntExt::free(void*) +void LocalIntExt::free(Extensible* container, void* item) { } -StringExtItem::StringExtItem(const std::string& Key, Module* mod) : ExtensionItem(Key, mod) +StringExtItem::StringExtItem(const std::string& Key, ExtensibleType exttype, Module* mod) + : ExtensionItem(Key, exttype, mod) { } @@ -303,7 +337,7 @@ void StringExtItem::unset(Extensible* container) delete static_cast<std::string*>(old); } -void StringExtItem::free(void* item) +void StringExtItem::free(Extensible* container, void* item) { delete static_cast<std::string*>(item); } @@ -312,4 +346,3 @@ ModuleException::ModuleException(const std::string &message, Module* who) : CoreException(message, who ? who->ModuleSourceFile : "A Module") { } - diff --git a/src/channels.cpp b/src/channels.cpp index df2212a21..c9816db4b 100644 --- a/src/channels.cpp +++ b/src/channels.cpp @@ -23,172 +23,104 @@ */ -/* $Core */ - #include "inspircd.h" -#include <cstdarg> -#include "mode.h" +#include "listmode.h" -Channel::Channel(const std::string &cname, time_t ts) +namespace { - if (!ServerInstance->chanlist->insert(std::make_pair(cname, this)).second) - throw CoreException("Cannot create duplicate channel " + cname); - - this->name = cname; - this->age = ts ? ts : ServerInstance->Time(); - - maxbans = topicset = 0; - modes.reset(); + ChanModeReference ban(NULL, "ban"); } -void Channel::SetMode(char mode,bool mode_on) +Channel::Channel(const std::string &cname, time_t ts) + : name(cname), age(ts), topicset(0) { - modes[mode-65] = mode_on; + if (!ServerInstance->chanlist.insert(std::make_pair(cname, this)).second) + throw CoreException("Cannot create duplicate channel " + cname); } void Channel::SetMode(ModeHandler* mh, bool on) { - modes[mh->GetModeChar() - 65] = on; + modes[mh->GetId()] = on; } -void Channel::SetModeParam(char mode, const std::string& parameter) +void Channel::SetTopic(User* u, const std::string& ntopic, time_t topicts, const std::string* setter) { - CustomModeList::iterator n = custom_mode_params.find(mode); - // always erase, even if changing, so that the map gets the new value - if (n != custom_mode_params.end()) - custom_mode_params.erase(n); - if (parameter.empty()) - { - modes[mode-65] = false; - } - else + // Send a TOPIC message to the channel only if the new topic text differs + if (this->topic != ntopic) { - custom_mode_params[mode] = parameter; - modes[mode-65] = true; + this->topic = ntopic; + ClientProtocol::Messages::Topic topicmsg(u, this, this->topic); + Write(ServerInstance->GetRFCEvents().topic, topicmsg); } -} -void Channel::SetModeParam(ModeHandler* mode, const std::string& parameter) -{ - SetModeParam(mode->GetModeChar(), parameter); -} + // Always update setter and set time + if (!setter) + setter = ServerInstance->Config->FullHostInTopic ? &u->GetFullHost() : &u->nick; + this->setby.assign(*setter, 0, ServerInstance->Config->Limits.GetMaxMask()); + this->topicset = topicts; -std::string Channel::GetModeParameter(char mode) -{ - CustomModeList::iterator n = custom_mode_params.find(mode); - if (n != custom_mode_params.end()) - return n->second; - return ""; + FOREACH_MOD(OnPostTopicChange, (u, this, this->topic)); } -std::string Channel::GetModeParameter(ModeHandler* mode) -{ - CustomModeList::iterator n = custom_mode_params.find(mode->GetModeChar()); - if (n != custom_mode_params.end()) - return n->second; - return ""; -} - -int Channel::SetTopic(User *u, std::string &ntopic, bool forceset) +Membership* Channel::AddUser(User* user) { - if (!u) - u = ServerInstance->FakeClient; - if (IS_LOCAL(u) && !forceset) - { - ModResult res; - FIRST_MOD_RESULT(OnPreTopicChange, res, (u,this,ntopic)); - - if (res == MOD_RES_DENY) - return CMD_FAILURE; - if (res != MOD_RES_ALLOW) - { - if (!this->HasUser(u)) - { - u->WriteNumeric(442, "%s %s :You're not on that channel!",u->nick.c_str(), this->name.c_str()); - return CMD_FAILURE; - } - if (IsModeSet('t') && !ServerInstance->OnCheckExemption(u,this,"topiclock").check(GetPrefixValue(u) >= HALFOP_VALUE)) - { - u->WriteNumeric(482, "%s %s :You do not have access to change the topic on this channel", u->nick.c_str(), this->name.c_str()); - return CMD_FAILURE; - } - } - } - - this->topic.assign(ntopic, 0, ServerInstance->Config->Limits.MaxTopic); - this->setby.assign(ServerInstance->Config->FullHostInTopic ? u->GetFullHost() : u->nick, 0, 128); - this->WriteChannel(u, "TOPIC %s :%s", this->name.c_str(), this->topic.c_str()); - - this->topicset = ServerInstance->Time(); - - FOREACH_MOD(I_OnPostTopicChange,OnPostTopicChange(u, this, this->topic)); + std::pair<MemberMap::iterator, bool> ret = userlist.insert(std::make_pair(user, insp::aligned_storage<Membership>())); + if (!ret.second) + return NULL; - return CMD_SUCCESS; + Membership* memb = new(ret.first->second) Membership(user, this); + return memb; } -long Channel::GetUserCounter() +void Channel::DelUser(User* user) { - return userlist.size(); + MemberMap::iterator it = userlist.find(user); + if (it != userlist.end()) + DelUser(it); } -Membership* Channel::AddUser(User* user) +void Channel::CheckDestroy() { - Membership* memb = new Membership(user, this); - userlist[user] = memb; - return memb; -} - -void Channel::DelUser(User* user) -{ - UserMembIter a = userlist.find(user); + if (!userlist.empty()) + return; - if (a != userlist.end()) - { - a->second->cull(); - delete a->second; - userlist.erase(a); - } + ModResult res; + FIRST_MOD_RESULT(OnChannelPreDelete, res, (this)); + if (res == MOD_RES_DENY) + return; - if (userlist.empty()) - { - ModResult res; - FIRST_MOD_RESULT(OnChannelPreDelete, res, (this)); - if (res == MOD_RES_DENY) - return; - chan_hash::iterator iter = ServerInstance->chanlist->find(this->name); - /* kill the record */ - if (iter != ServerInstance->chanlist->end()) - { - FOREACH_MOD(I_OnChannelDelete, OnChannelDelete(this)); - ServerInstance->chanlist->erase(iter); - } + // If the channel isn't in chanlist then it is already in the cull list, don't add it again + chan_hash::iterator iter = ServerInstance->chanlist.find(this->name); + if ((iter == ServerInstance->chanlist.end()) || (iter->second != this)) + return; - ClearInvites(); - ServerInstance->GlobalCulls.AddItem(this); - } + FOREACH_MOD(OnChannelDelete, (this)); + ServerInstance->chanlist.erase(iter); + ServerInstance->GlobalCulls.AddItem(this); } -bool Channel::HasUser(User* user) +void Channel::DelUser(const MemberMap::iterator& membiter) { - return (userlist.find(user) != userlist.end()); + Membership* memb = membiter->second; + memb->cull(); + memb->~Membership(); + userlist.erase(membiter); + + // If this channel became empty then it should be removed + CheckDestroy(); } Membership* Channel::GetUser(User* user) { - UserMembIter i = userlist.find(user); + MemberMap::iterator i = userlist.find(user); if (i == userlist.end()) return NULL; return i->second; } -const UserMembList* Channel::GetUsers() -{ - return &userlist; -} - void Channel::SetDefaultModes() { - ServerInstance->Logs->Log("CHANNELS", DEBUG, "SetDefaultModes %s", + ServerInstance->Logs->Log("CHANNELS", LOG_DEBUG, "SetDefaultModes %s", ServerInstance->Config->DefaultModes.c_str()); irc::spacesepstream list(ServerInstance->Config->DefaultModes); std::string modeseq; @@ -201,7 +133,10 @@ void Channel::SetDefaultModes() ModeHandler* mode = ServerInstance->Modes->FindMode(*n, MODETYPE_CHANNEL); if (mode) { - if (mode->GetNumParams(true)) + if (mode->IsPrefixMode()) + continue; + + if (mode->NeedsParam(true)) { list.GetToken(parameter); // If the parameter begins with a ':' then it's invalid @@ -211,7 +146,7 @@ void Channel::SetDefaultModes() else parameter.clear(); - if ((mode->GetNumParams(true)) && (parameter.empty())) + if ((mode->NeedsParam(true)) && (parameter.empty())) continue; mode->OnModeChange(ServerInstance->FakeClient, ServerInstance->FakeClient, this, parameter, true); @@ -223,204 +158,141 @@ void Channel::SetDefaultModes() * add a channel to a user, creating the record for it if needed and linking * it to the user record */ -Channel* Channel::JoinUser(User *user, const char* cn, bool override, const char* key, bool bursting, time_t TS) +Channel* Channel::JoinUser(LocalUser* user, std::string cname, bool override, const std::string& key) { - // Fix: unregistered users could be joined using /SAJOIN - if (!user || !cn || user->registered != REG_ALL) + if (user->registered != REG_ALL) + { + ServerInstance->Logs->Log("CHANNELS", LOG_DEBUG, "Attempted to join unregistered user " + user->uuid + " to channel " + cname); return NULL; - - std::string privs; - Channel *Ptr; + } /* * We don't restrict the number of channels that remote users or users that are override-joining may be in. - * We restrict local users to MaxChans channels. - * We restrict local operators to OperMaxChans channels. + * We restrict local users to <connect:maxchans> channels. + * We restrict local operators to <oper:maxchans> channels. * This is a lot more logical than how it was formerly. -- w00t */ - if (IS_LOCAL(user) && !override) + if (!override) { - if (user->HasPrivPermission("channels/high-join-limit")) + unsigned int maxchans = user->GetClass()->maxchans; + if (user->IsOper()) { - if (user->chans.size() >= ServerInstance->Config->OperMaxChans) - { - user->WriteNumeric(ERR_TOOMANYCHANNELS, "%s %s :You are on too many channels",user->nick.c_str(), cn); - return NULL; - } + unsigned int opermaxchans = ConvToInt(user->oper->getConfig("maxchans")); + // If not set, use 2.0's <channels:opers>, if that's not set either, use limit from CC + if (!opermaxchans && user->HasPrivPermission("channels/high-join-limit")) + opermaxchans = ServerInstance->Config->OperMaxChans; + if (opermaxchans) + maxchans = opermaxchans; } - else + if (user->chans.size() >= maxchans) { - unsigned int maxchans = user->GetClass()->maxchans; - if (!maxchans) - maxchans = ServerInstance->Config->MaxChans; - if (user->chans.size() >= maxchans) - { - user->WriteNumeric(ERR_TOOMANYCHANNELS, "%s %s :You are on too many channels",user->nick.c_str(), cn); - return NULL; - } + user->WriteNumeric(ERR_TOOMANYCHANNELS, cname, "You are on too many channels"); + return NULL; } } - std::string cname; - cname.assign(std::string(cn), 0, ServerInstance->Config->Limits.ChanMax); - Ptr = ServerInstance->FindChan(cname); - bool created_by_local = false; + // Crop channel name if it's too long + if (cname.length() > ServerInstance->Config->Limits.ChanMax) + cname.resize(ServerInstance->Config->Limits.ChanMax); - if (!Ptr) + Channel* chan = ServerInstance->FindChan(cname); + bool created_by_local = (chan == NULL); // Flag that will be passed to modules in the OnUserJoin() hook later + std::string privs; // Prefix mode(letter)s to give to the joining user + + if (!chan) { - /* - * Fix: desync bug was here, don't set @ on remote users - spanningtree handles their permissions. bug #358. -- w00t - */ - if (!IS_LOCAL(user)) - { - if (!TS) - ServerInstance->Logs->Log("CHANNELS",DEBUG,"*** BUG *** Channel::JoinUser called for REMOTE user '%s' on channel '%s' but no TS given!", user->nick.c_str(), cn); - } - else - { - privs = "o"; - created_by_local = true; - } + privs = ServerInstance->Config->DefaultModes.substr(0, ServerInstance->Config->DefaultModes.find(' ')); - if (IS_LOCAL(user) && override == false) + if (override == false) { + // Ask the modules whether they're ok with the join, pass NULL as Channel* as the channel is yet to be created ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnUserPreJoin, MOD_RESULT, (user, NULL, cname.c_str(), privs, key ? key : "")); + FIRST_MOD_RESULT(OnUserPreJoin, MOD_RESULT, (user, NULL, cname, privs, key)); if (MOD_RESULT == MOD_RES_DENY) - return NULL; + return NULL; // A module wasn't happy with the join, abort } - Ptr = new Channel(cname, TS); + chan = new Channel(cname, ServerInstance->Time()); + // Set the default modes on the channel (<options:defaultmodes>) + chan->SetDefaultModes(); } else { /* Already on the channel */ - if (Ptr->HasUser(user)) + if (chan->HasUser(user)) return NULL; - /* - * remote users are allowed us to bypass channel modes - * and bans (used by servers) - */ - if (IS_LOCAL(user) && override == false) + if (override == false) { ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnUserPreJoin, MOD_RESULT, (user, Ptr, cname.c_str(), privs, key ? key : "")); + FIRST_MOD_RESULT(OnUserPreJoin, MOD_RESULT, (user, chan, cname, privs, key)); + + // A module explicitly denied the join and (hopefully) generated a message + // describing the situation, so we may stop here without sending anything if (MOD_RESULT == MOD_RES_DENY) - { return NULL; - } - else if (MOD_RESULT == MOD_RES_PASSTHRU) - { - std::string ckey = Ptr->GetModeParameter('k'); - bool invited = IS_LOCAL(user)->IsInvited(Ptr->name.c_str()); - bool can_bypass = ServerInstance->Config->InvBypassModes && invited; - - if (!ckey.empty()) - { - FIRST_MOD_RESULT(OnCheckKey, MOD_RESULT, (user, Ptr, key ? key : "")); - if (!MOD_RESULT.check((key && ckey == key) || can_bypass)) - { - // If no key provided, or key is not the right one, and can't bypass +k (not invited or option not enabled) - user->WriteNumeric(ERR_BADCHANNELKEY, "%s %s :Cannot join channel (Incorrect channel key)",user->nick.c_str(), Ptr->name.c_str()); - return NULL; - } - } - - if (Ptr->IsModeSet('i')) - { - FIRST_MOD_RESULT(OnCheckInvite, MOD_RESULT, (user, Ptr)); - if (!MOD_RESULT.check(invited)) - { - user->WriteNumeric(ERR_INVITEONLYCHAN, "%s %s :Cannot join channel (Invite only)",user->nick.c_str(), Ptr->name.c_str()); - return NULL; - } - } - - std::string limit = Ptr->GetModeParameter('l'); - if (!limit.empty()) - { - FIRST_MOD_RESULT(OnCheckLimit, MOD_RESULT, (user, Ptr)); - if (!MOD_RESULT.check((Ptr->GetUserCounter() < atol(limit.c_str()) || can_bypass))) - { - user->WriteNumeric(ERR_CHANNELISFULL, "%s %s :Cannot join channel (Channel is full)",user->nick.c_str(), Ptr->name.c_str()); - return NULL; - } - } - if (Ptr->IsBanned(user) && !can_bypass) + // If no module returned MOD_RES_DENY or MOD_RES_ALLOW (which is the case + // most of the time) then proceed to check channel bans. + // + // If a module explicitly allowed the join (by returning MOD_RES_ALLOW), + // then this entire section is skipped + if (MOD_RESULT == MOD_RES_PASSTHRU) + { + if (chan->IsBanned(user)) { - user->WriteNumeric(ERR_BANNEDFROMCHAN, "%s %s :Cannot join channel (You're banned)",user->nick.c_str(), Ptr->name.c_str()); + user->WriteNumeric(ERR_BANNEDFROMCHAN, chan->name, "Cannot join channel (You're banned)"); return NULL; } - - /* - * If the user has invites for this channel, remove them now - * after a successful join so they don't build up. - */ - if (invited) - { - IS_LOCAL(user)->RemoveInvite(Ptr->name.c_str()); - } } } } - if (created_by_local) - { - /* As spotted by jilles, dont bother to set this on remote users */ - Ptr->SetDefaultModes(); - } - - return Channel::ForceChan(Ptr, user, privs, bursting, created_by_local); + // We figured that this join is allowed and also created the + // channel if it didn't exist before, now do the actual join + chan->ForceJoin(user, &privs, false, created_by_local); + return chan; } -Channel* Channel::ForceChan(Channel* Ptr, User* user, const std::string &privs, bool bursting, bool created) +Membership* Channel::ForceJoin(User* user, const std::string* privs, bool bursting, bool created_by_local) { - std::string nick = user->nick; + if (IS_SERVER(user)) + { + ServerInstance->Logs->Log("CHANNELS", LOG_DEBUG, "Attempted to join server user " + user->uuid + " to channel " + this->name); + return NULL; + } + + Membership* memb = this->AddUser(user); + if (!memb) + return NULL; // Already on the channel - Membership* memb = Ptr->AddUser(user); - user->chans.insert(Ptr); + user->chans.push_front(memb); - for (std::string::const_iterator x = privs.begin(); x != privs.end(); x++) + if (privs) { - const char status = *x; - ModeHandler* mh = ServerInstance->Modes->FindMode(status, MODETYPE_CHANNEL); - if (mh) + // If the user was granted prefix modes (in the OnUserPreJoin hook, or he's a + // remote user and his own server set the modes), then set them internally now + for (std::string::const_iterator i = privs->begin(); i != privs->end(); ++i) { - /* Set, and make sure that the mode handler knows this mode was now set */ - Ptr->SetPrefix(user, mh->GetModeChar(), true); - mh->OnModeChange(ServerInstance->FakeClient, ServerInstance->FakeClient, Ptr, nick, true); + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(*i); + if (mh) + { + std::string nick = user->nick; + // Set the mode on the user + mh->OnModeChange(ServerInstance->FakeClient, NULL, this, nick, true); + } } } + // Tell modules about this join, they have the chance now to populate except_list with users we won't send the JOIN (and possibly MODE) to CUList except_list; - FOREACH_MOD(I_OnUserJoin,OnUserJoin(memb, bursting, created, except_list)); + FOREACH_MOD(OnUserJoin, (memb, bursting, created_by_local, except_list)); - Ptr->WriteAllExcept(user, false, 0, except_list, "JOIN :%s", Ptr->name.c_str()); - - /* Theyre not the first ones in here, make sure everyone else sees the modes we gave the user */ - if ((Ptr->GetUserCounter() > 1) && (!memb->modes.empty())) - { - std::string ms = memb->modes; - for(unsigned int i=0; i < memb->modes.length(); i++) - ms.append(" ").append(user->nick); - - except_list.insert(user); - Ptr->WriteAllExcept(user, !ServerInstance->Config->CycleHostsFromUser, 0, except_list, "MODE %s +%s", Ptr->name.c_str(), ms.c_str()); - } + ClientProtocol::Events::Join joinevent(memb); + this->Write(joinevent, 0, except_list); - if (IS_LOCAL(user)) - { - if (Ptr->topicset) - { - user->WriteNumeric(RPL_TOPIC, "%s %s :%s", user->nick.c_str(), Ptr->name.c_str(), Ptr->topic.c_str()); - user->WriteNumeric(RPL_TOPICTIME, "%s %s %s %lu", user->nick.c_str(), Ptr->name.c_str(), Ptr->setby.c_str(), (unsigned long)Ptr->topicset); - } - Ptr->UserList(user); - } - FOREACH_MOD(I_OnPostJoin,OnPostJoin(memb)); - return Ptr; + FOREACH_MOD(OnPostJoin, (memb)); + return memb; } bool Channel::IsBanned(User* user) @@ -431,10 +303,18 @@ bool Channel::IsBanned(User* user) if (result != MOD_RES_PASSTHRU) return (result == MOD_RES_DENY); - for (BanList::iterator i = this->bans.begin(); i != this->bans.end(); i++) + ListModeBase* banlm = static_cast<ListModeBase*>(*ban); + if (!banlm) + return false; + + const ListModeBase::ModeList* bans = banlm->GetList(this); + if (bans) { - if (CheckBan(user, i->data)) - return true; + for (ListModeBase::ModeList::const_iterator it = bans->begin(); it != bans->end(); it++) + { + if (CheckBan(user, it->mask)) + return true; + } } return false; } @@ -454,14 +334,13 @@ bool Channel::CheckBan(User* user, const std::string& mask) if (at == std::string::npos) return false; - char tomatch[MAXBUF]; - snprintf(tomatch, MAXBUF, "%s!%s", user->nick.c_str(), user->ident.c_str()); - std::string prefix = mask.substr(0, at); - if (InspIRCd::Match(tomatch, prefix, NULL)) + const std::string nickIdent = user->nick + "!" + user->ident; + std::string prefix(mask, 0, at); + if (InspIRCd::Match(nickIdent, prefix, NULL)) { - std::string suffix = mask.substr(at + 1); - if (InspIRCd::Match(user->host, suffix, NULL) || - InspIRCd::Match(user->dhost, suffix, NULL) || + std::string suffix(mask, at + 1); + if (InspIRCd::Match(user->GetRealHost(), suffix, NULL) || + InspIRCd::Match(user->GetDisplayedHost(), suffix, NULL) || InspIRCd::MatchCIDR(user->GetIPString(), suffix, NULL)) return true; } @@ -474,12 +353,20 @@ ModResult Channel::GetExtBanStatus(User *user, char type) FIRST_MOD_RESULT(OnExtBanCheck, rv, (user, this, type)); if (rv != MOD_RES_PASSTHRU) return rv; - for (BanList::iterator i = this->bans.begin(); i != this->bans.end(); i++) + + ListModeBase* banlm = static_cast<ListModeBase*>(*ban); + if (!banlm) + return MOD_RES_PASSTHRU; + + const ListModeBase::ModeList* bans = banlm->GetList(this); + if (bans) { - if (i->data.length() > 2 && i->data[0] == type && i->data[1] == ':') + for (ListModeBase::ModeList::const_iterator it = bans->begin(); it != bans->end(); ++it) { - std::string val = i->data.substr(2); - if (CheckBan(user, val)) + if (it->mask.length() <= 2 || it->mask[0] != type || it->mask[1] != ':') + continue; + + if (CheckBan(user, it->mask.substr(2))) return MOD_RES_DENY; } } @@ -487,407 +374,124 @@ ModResult Channel::GetExtBanStatus(User *user, char type) } /* Channel::PartUser - * remove a channel from a users record, and return the number of users left. - * Therefore, if this function returns 0 the caller should delete the Channel. + * Remove a channel from a users record, remove the reference to the Membership object + * from the channel and destroy it. */ -void Channel::PartUser(User *user, std::string &reason) -{ - if (!user) - return; - - Membership* memb = GetUser(user); - - if (memb) - { - CUList except_list; - FOREACH_MOD(I_OnUserPart,OnUserPart(memb, reason, except_list)); - - WriteAllExcept(user, false, 0, except_list, "PART %s%s%s", this->name.c_str(), reason.empty() ? "" : " :", reason.c_str()); - - user->chans.erase(this); - this->RemoveAllPrefixes(user); - } - - this->DelUser(user); -} - -void Channel::KickUser(User *src, User *user, const char* reason) -{ - if (!src || !user || !reason) - return; - - Membership* memb = GetUser(user); - if (IS_LOCAL(src)) - { - if (!memb) - { - src->WriteNumeric(ERR_USERNOTINCHANNEL, "%s %s %s :They are not on that channel",src->nick.c_str(), user->nick.c_str(), this->name.c_str()); - return; - } - if ((ServerInstance->ULine(user->server)) && (!ServerInstance->ULine(src->server))) - { - src->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :Only a u-line may kick a u-line from a channel.",src->nick.c_str(), this->name.c_str()); - return; - } - - ModResult res; - if (ServerInstance->ULine(src->server)) - res = MOD_RES_ALLOW; - else - FIRST_MOD_RESULT(OnUserPreKick, res, (src,memb,reason)); - - if (res == MOD_RES_DENY) - return; - - if (res == MOD_RES_PASSTHRU) - { - unsigned int them = this->GetPrefixValue(src); - unsigned int req = HALFOP_VALUE; - for (std::string::size_type i = 0; i < memb->modes.length(); i++) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(memb->modes[i], MODETYPE_CHANNEL); - if (mh && mh->GetLevelRequired() > req) - req = mh->GetLevelRequired(); - } - - if (them < req) - { - src->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You must be a channel %soperator", - src->nick.c_str(), this->name.c_str(), req > HALFOP_VALUE ? "" : "half-"); - return; - } - } - } - - if (memb) - { - CUList except_list; - FOREACH_MOD(I_OnUserKick,OnUserKick(src, memb, reason, except_list)); - - WriteAllExcept(src, false, 0, except_list, "KICK %s %s :%s", name.c_str(), user->nick.c_str(), reason); - - user->chans.erase(this); - this->RemoveAllPrefixes(user); - } - - this->DelUser(user); -} - -void Channel::WriteChannel(User* user, const char* text, ...) -{ - char textbuffer[MAXBUF]; - va_list argsPtr; - - if (!user || !text) - return; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteChannel(user, std::string(textbuffer)); -} - -void Channel::WriteChannel(User* user, const std::string &text) -{ - char tb[MAXBUF]; - - if (!user) - return; - - snprintf(tb,MAXBUF,":%s %s", user->GetFullHost().c_str(), text.c_str()); - std::string out = tb; - - for (UserMembIter i = userlist.begin(); i != userlist.end(); i++) - { - if (IS_LOCAL(i->first)) - i->first->Write(out); - } -} - -void Channel::WriteChannelWithServ(const std::string& ServName, const char* text, ...) -{ - char textbuffer[MAXBUF]; - va_list argsPtr; - - if (!text) - return; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteChannelWithServ(ServName, std::string(textbuffer)); -} - -void Channel::WriteChannelWithServ(const std::string& ServName, const std::string &text) -{ - char tb[MAXBUF]; - - snprintf(tb,MAXBUF,":%s %s", ServName.empty() ? ServerInstance->Config->ServerName.c_str() : ServName.c_str(), text.c_str()); - std::string out = tb; - - for (UserMembIter i = userlist.begin(); i != userlist.end(); i++) - { - if (IS_LOCAL(i->first)) - i->first->Write(out); - } -} - -/* write formatted text from a source user to all users on a channel except - * for the sender (for privmsg etc) */ -void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const char* text, ...) +bool Channel::PartUser(User* user, std::string& reason) { - char textbuffer[MAXBUF]; - va_list argsPtr; + MemberMap::iterator membiter = userlist.find(user); - if (!text) - return; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteAllExceptSender(user, serversource, status, std::string(textbuffer)); -} - -void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...) -{ - char textbuffer[MAXBUF]; - va_list argsPtr; + if (membiter == userlist.end()) + return false; - if (!text) - return; + Membership* memb = membiter->second; + CUList except_list; + FOREACH_MOD(OnUserPart, (memb, reason, except_list)); - int offset = snprintf(textbuffer,MAXBUF,":%s ", serversource ? ServerInstance->Config->ServerName.c_str() : user->GetFullHost().c_str()); + ClientProtocol::Messages::Part partmsg(memb, reason); + Write(ServerInstance->GetRFCEvents().part, partmsg, 0, except_list); - va_start(argsPtr, text); - vsnprintf(textbuffer + offset, MAXBUF - offset, text, argsPtr); - va_end(argsPtr); + // Remove this channel from the user's chanlist + user->chans.erase(memb); + // Remove the Membership from this channel's userlist and destroy it + this->DelUser(membiter); - this->RawWriteAllExcept(user, serversource, status, except_list, std::string(textbuffer)); + return true; } -void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &text) +void Channel::KickUser(User* src, const MemberMap::iterator& victimiter, const std::string& reason) { - char tb[MAXBUF]; + Membership* memb = victimiter->second; + CUList except_list; + FOREACH_MOD(OnUserKick, (src, memb, reason, except_list)); - snprintf(tb,MAXBUF,":%s %s", serversource ? ServerInstance->Config->ServerName.c_str() : user->GetFullHost().c_str(), text.c_str()); + ClientProtocol::Messages::Kick kickmsg(src, memb, reason); + Write(ServerInstance->GetRFCEvents().kick, kickmsg, 0, except_list); - this->RawWriteAllExcept(user, serversource, status, except_list, std::string(tb)); + memb->user->chans.erase(memb); + this->DelUser(victimiter); } -void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &out) +void Channel::Write(ClientProtocol::Event& protoev, char status, const CUList& except_list) { unsigned int minrank = 0; if (status) { - ModeHandler* mh = ServerInstance->Modes->FindPrefix(status); + PrefixMode* mh = ServerInstance->Modes->FindPrefix(status); if (mh) minrank = mh->GetPrefixRank(); } - for (UserMembIter i = userlist.begin(); i != userlist.end(); i++) + for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++) { - if (IS_LOCAL(i->first) && (except_list.find(i->first) == except_list.end())) + LocalUser* user = IS_LOCAL(i->first); + if ((user) && (!except_list.count(user))) { /* User doesn't have the status we're after */ if (minrank && i->second->getRank() < minrank) continue; - i->first->Write(out); + user->Send(protoev); } } } -void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const std::string& text) -{ - CUList except_list; - except_list.insert(user); - this->WriteAllExcept(user, serversource, status, except_list, std::string(text)); -} - -/* - * return a count of the users on a specific channel accounting for - * invisible users who won't increase the count. e.g. for /LIST - */ -int Channel::CountInvisible() -{ - int count = 0; - for (UserMembIter i = userlist.begin(); i != userlist.end(); i++) - { - if (!i->first->quitting && !i->first->IsModeSet('i')) - count++; - } - - return count; -} - -char* Channel::ChanModes(bool showkey) +const char* Channel::ChanModes(bool showkey) { - static char scratch[MAXBUF]; - static char sparam[MAXBUF]; - char* offset = scratch; - std::string extparam; + static std::string scratch; + std::string sparam; - *scratch = '\0'; - *sparam = '\0'; + scratch.clear(); /* This was still iterating up to 190, Channel::modes is only 64 elements -- Om */ for(int n = 0; n < 64; n++) { - if(this->modes[n]) + ModeHandler* mh = ServerInstance->Modes->FindMode(n + 65, MODETYPE_CHANNEL); + if (mh && IsModeSet(mh)) { - *offset++ = n + 65; - extparam.clear(); + scratch.push_back(n + 65); + + ParamModeBase* pm = mh->IsParameterMode(); + if (!pm) + continue; + if (n == 'k' - 65 && !showkey) { - extparam = "<key>"; + sparam += " <key>"; } else { - extparam = this->GetModeParameter(n + 65); - } - if (!extparam.empty()) - { - charlcat(sparam,' ',MAXBUF); - strlcat(sparam,extparam.c_str(),MAXBUF); + sparam += ' '; + pm->GetParameter(this, sparam); } } } - /* Null terminate scratch */ - *offset = '\0'; - strlcat(scratch,sparam,MAXBUF); - return scratch; -} - -/* compile a userlist of a channel into a string, each nick seperated by - * spaces and op, voice etc status shown as @ and +, and send it to 'user' - */ -void Channel::UserList(User *user) -{ - if (!IS_LOCAL(user)) - return; - - bool has_privs = user->HasPrivPermission("channels/auspex"); - - if (this->IsModeSet('s') && !this->HasUser(user) && !has_privs) - { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(), this->name.c_str()); - return; - } - - std::string list = user->nick; - list.push_back(' '); - list.push_back(this->IsModeSet('s') ? '@' : this->IsModeSet('p') ? '*' : '='); - list.push_back(' '); - list.append(this->name).append(" :"); - std::string::size_type pos = list.size(); - - bool has_one = false; - - /* Improvement by Brain - this doesnt change in value, so why was it inside - * the loop? - */ - bool has_user = this->HasUser(user); - - const size_t maxlen = MAXBUF - 10 - ServerInstance->Config->ServerName.size(); - std::string prefixlist; - std::string nick; - for (UserMembIter i = userlist.begin(); i != userlist.end(); ++i) - { - if (i->first->quitting) - continue; - if ((!has_user) && (i->first->IsModeSet('i')) && (!has_privs)) - { - /* - * user is +i, and source not on the channel, does not show - * nick in NAMES list - */ - continue; - } - - prefixlist = this->GetPrefixChar(i->first); - nick = i->first->nick; - - FOREACH_MOD(I_OnNamesListItem, OnNamesListItem(user, i->second, prefixlist, nick)); - - /* Nick was nuked, a module wants us to skip it */ - if (nick.empty()) - continue; - - if (list.size() + prefixlist.length() + nick.length() + 1 > maxlen) - { - /* list overflowed into multiple numerics */ - user->WriteNumeric(RPL_NAMREPLY, list); - - // Erase all nicks, keep the constant part - list.erase(pos); - has_one = false; - } - - list.append(prefixlist).append(nick).push_back(' '); - - has_one = true; - } - - /* if whats left in the list isnt empty, send it */ - if (has_one) - { - user->WriteNumeric(RPL_NAMREPLY, list); - } - - user->WriteNumeric(RPL_ENDOFNAMES, "%s %s :End of /NAMES list.", user->nick.c_str(), this->name.c_str()); -} - -long Channel::GetMaxBans() -{ - /* Return the cached value if there is one */ - if (this->maxbans) - return this->maxbans; - - /* If there isnt one, we have to do some O(n) hax to find it the first time. (ick) */ - for (std::map<std::string,int>::iterator n = ServerInstance->Config->maxbans.begin(); n != ServerInstance->Config->maxbans.end(); n++) - { - if (InspIRCd::Match(this->name, n->first, NULL)) - { - this->maxbans = n->second; - return n->second; - } - } - - /* Screw it, just return the default of 64 */ - this->maxbans = 64; - return this->maxbans; + scratch += sparam; + return scratch.c_str(); } -void Channel::ResetMaxBans() +void Channel::WriteNotice(const std::string& text) { - this->maxbans = 0; + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this, text, MSG_NOTICE); + Write(ServerInstance->GetRFCEvents().privmsg, privmsg); } /* returns the status character for a given user on a channel, e.g. @ for op, * % for halfop etc. If the user has several modes set, the highest mode * the user has must be returned. */ -const char* Channel::GetPrefixChar(User *user) +char Membership::GetPrefixChar() const { - static char pf[2] = {0, 0}; - *pf = 0; + char pf = 0; unsigned int bestrank = 0; - UserMembIter m = userlist.find(user); - if (m != userlist.end()) + for (std::string::const_iterator i = modes.begin(); i != modes.end(); ++i) { - for(unsigned int i=0; i < m->second->modes.length(); i++) + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(*i); + if (mh && mh->GetPrefixRank() > bestrank && mh->GetPrefix()) { - char mchar = m->second->modes[i]; - ModeHandler* mh = ServerInstance->Modes->FindMode(mchar, MODETYPE_CHANNEL); - if (mh && mh->GetPrefixRank() > bestrank && mh->GetPrefix()) - { - bestrank = mh->GetPrefixRank(); - pf[0] = mh->GetPrefix(); - } + bestrank = mh->GetPrefixRank(); + pf = mh->GetPrefix(); } } return pf; @@ -899,160 +503,61 @@ unsigned int Membership::getRank() unsigned int rv = 0; if (mchar) { - ModeHandler* mh = ServerInstance->Modes->FindMode(mchar, MODETYPE_CHANNEL); + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(mchar); if (mh) rv = mh->GetPrefixRank(); } return rv; } -const char* Channel::GetAllPrefixChars(User* user) +std::string Membership::GetAllPrefixChars() const { - static char prefix[64]; - int ctr = 0; - - UserMembIter m = userlist.find(user); - if (m != userlist.end()) + std::string ret; + for (std::string::const_iterator i = modes.begin(); i != modes.end(); ++i) { - for(unsigned int i=0; i < m->second->modes.length(); i++) - { - char mchar = m->second->modes[i]; - ModeHandler* mh = ServerInstance->Modes->FindMode(mchar, MODETYPE_CHANNEL); - if (mh && mh->GetPrefix()) - prefix[ctr++] = mh->GetPrefix(); - } + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(*i); + if (mh && mh->GetPrefix()) + ret.push_back(mh->GetPrefix()); } - prefix[ctr] = 0; - return prefix; + return ret; } unsigned int Channel::GetPrefixValue(User* user) { - UserMembIter m = userlist.find(user); + MemberMap::iterator m = userlist.find(user); if (m == userlist.end()) return 0; return m->second->getRank(); } -bool Channel::SetPrefix(User* user, char prefix, bool adding) +bool Membership::SetPrefix(PrefixMode* delta_mh, bool adding) { - ModeHandler* delta_mh = ServerInstance->Modes->FindMode(prefix, MODETYPE_CHANNEL); - if (!delta_mh) - return false; - UserMembIter m = userlist.find(user); - if (m == userlist.end()) - return false; - for(unsigned int i=0; i < m->second->modes.length(); i++) + char prefix = delta_mh->GetModeChar(); + for (unsigned int i = 0; i < modes.length(); i++) { - char mchar = m->second->modes[i]; - ModeHandler* mh = ServerInstance->Modes->FindMode(mchar, MODETYPE_CHANNEL); + char mchar = modes[i]; + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(mchar); if (mh && mh->GetPrefixRank() <= delta_mh->GetPrefixRank()) { - m->second->modes = - m->second->modes.substr(0,i) + + modes = modes.substr(0,i) + (adding ? std::string(1, prefix) : "") + - m->second->modes.substr(mchar == prefix ? i+1 : i); + modes.substr(mchar == prefix ? i+1 : i); return adding != (mchar == prefix); } } if (adding) - m->second->modes += std::string(1, prefix); + modes.push_back(prefix); return adding; } -void Channel::RemoveAllPrefixes(User* user) -{ - UserMembIter m = userlist.find(user); - if (m != userlist.end()) - { - m->second->modes.clear(); - } -} -void Invitation::Create(Channel* c, LocalUser* u, time_t timeout) +void Membership::WriteNotice(const std::string& text) const { - if ((timeout != 0) && (ServerInstance->Time() >= timeout)) - // Expired, don't bother + LocalUser* const localuser = IS_LOCAL(user); + if (!localuser) return; - ServerInstance->Logs->Log("INVITATION", DEBUG, "Invitation::Create chan=%s user=%s", c->name.c_str(), u->uuid.c_str()); - - Invitation* inv = Invitation::Find(c, u, false); - if (inv) - { - if ((inv->expiry == 0) || (inv->expiry > timeout)) - return; - inv->expiry = timeout; - ServerInstance->Logs->Log("INVITATION", DEBUG, "Invitation::Create changed expiry in existing invitation %p", (void*) inv); - } - else - { - inv = new Invitation(c, u, timeout); - c->invites.push_back(inv); - u->invites.push_back(inv); - ServerInstance->Logs->Log("INVITATION", DEBUG, "Invitation::Create created new invitation %p", (void*) inv); - } -} - -Invitation* Invitation::Find(Channel* c, LocalUser* u, bool check_expired) -{ - ServerInstance->Logs->Log("INVITATION", DEBUG, "Invitation::Find chan=%s user=%s check_expired=%d", c ? c->name.c_str() : "NULL", u ? u->uuid.c_str() : "NULL", check_expired); - if (!u || u->invites.empty()) - return NULL; - - InviteList locallist; - locallist.swap(u->invites); - - Invitation* result = NULL; - for (InviteList::iterator i = locallist.begin(); i != locallist.end(); ) - { - Invitation* inv = *i; - if ((check_expired) && (inv->expiry != 0) && (inv->expiry <= ServerInstance->Time())) - { - /* Expired invite, remove it. */ - std::string expiration = ServerInstance->TimeString(inv->expiry); - ServerInstance->Logs->Log("INVITATION", DEBUG, "Invitation::Find ecountered expired entry: %p expired %s", (void*) inv, expiration.c_str()); - i = locallist.erase(i); - inv->cull(); - delete inv; - } - else - { - /* Is it what we're searching for? */ - if (inv->chan == c) - { - result = inv; - break; - } - ++i; - } - } - - locallist.swap(u->invites); - ServerInstance->Logs->Log("INVITATION", DEBUG, "Invitation::Find result=%p", (void*) result); - return result; -} - -Invitation::~Invitation() -{ - // Remove this entry from both lists - InviteList::iterator it = std::find(chan->invites.begin(), chan->invites.end(), this); - if (it != chan->invites.end()) - chan->invites.erase(it); - it = std::find(user->invites.begin(), user->invites.end(), this); - if (it != user->invites.end()) - user->invites.erase(it); -} - -void InviteBase::ClearInvites() -{ - ServerInstance->Logs->Log("INVITEBASE", DEBUG, "InviteBase::ClearInvites %p", (void*) this); - InviteList locallist; - locallist.swap(invites); - for (InviteList::const_iterator i = locallist.begin(); i != locallist.end(); ++i) - { - (*i)->cull(); - delete *i; - } + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this->chan, text, MSG_NOTICE); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); } diff --git a/src/cidr.cpp b/src/cidr.cpp index b245a1552..250ad9c69 100644 --- a/src/cidr.cpp +++ b/src/cidr.cpp @@ -19,8 +19,6 @@ */ -/* $Core */ - #include "inspircd.h" /* Match CIDR strings, e.g. 127.0.0.1 to 127.0.0.0/8 or 3ffe:1:5:6::8 to 3ffe:1::0/32 @@ -55,8 +53,8 @@ bool irc::sockets::MatchCIDR(const std::string &address, const std::string &cidr } else { - address_copy = address.substr(username_addr_pos + 1); - cidr_copy = cidr_mask.substr(username_mask_pos + 1); + address_copy.assign(address, username_addr_pos + 1, std::string::npos); + cidr_copy.assign(cidr_mask, username_mask_pos + 1, std::string::npos); } } else @@ -82,5 +80,3 @@ bool irc::sockets::MatchCIDR(const std::string &address, const std::string &cidr return mask == mask2; } - - diff --git a/src/clientprotocol.cpp b/src/clientprotocol.cpp new file mode 100644 index 000000000..a732855a5 --- /dev/null +++ b/src/clientprotocol.cpp @@ -0,0 +1,105 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +ClientProtocol::Serializer::Serializer(Module* mod, const char* Name) + : DataProvider(mod, std::string("serializer/") + Name) + , evprov(mod, "event/messagetag") +{ +} + +bool ClientProtocol::Serializer::HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const +{ + // Catch and block empty tags + if (tagname.empty()) + return false; + + const ::Events::ModuleEventProvider::SubscriberList& list = evprov.GetSubscribers(); + for (::Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i) + { + MessageTagProvider* const tagprov = static_cast<MessageTagProvider*>(*i); + const ModResult res = tagprov->OnClientProtocolProcessTag(user, tagname, tagvalue); + if (res == MOD_RES_ALLOW) + return tags.insert(std::make_pair(tagname, MessageTagData(tagprov, tagvalue))).second; + else if (res == MOD_RES_DENY) + break; + } + + // No module handles the tag but that's not an error + return true; +} + +ClientProtocol::TagSelection ClientProtocol::Serializer::MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const +{ + TagSelection tagwl; + for (TagMap::const_iterator i = tagmap.begin(); i != tagmap.end(); ++i) + { + const MessageTagData& tagdata = i->second; + if (tagdata.tagprov->ShouldSendTag(user, tagdata)) + tagwl.Select(tagmap, i); + } + return tagwl; +} + +const ClientProtocol::SerializedMessage& ClientProtocol::Serializer::SerializeForUser(LocalUser* user, Message& msg) +{ + if (!msg.msginit_done) + { + msg.msginit_done = true; + FOREACH_MOD_CUSTOM(evprov, MessageTagProvider, OnClientProtocolPopulateTags, (msg)); + } + return msg.GetSerialized(Message::SerializedInfo(this, MakeTagWhitelist(user, msg.GetTags()))); +} + +const ClientProtocol::SerializedMessage& ClientProtocol::Message::GetSerialized(const SerializedInfo& serializeinfo) const +{ + // First check if the serialized line they're asking for is in the cache + for (SerializedList::const_iterator i = serlist.begin(); i != serlist.end(); ++i) + { + const SerializedInfo& curr = i->first; + if (curr == serializeinfo) + return i->second; + } + + // Not cached, generate it and put it in the cache for later use + serlist.push_back(std::make_pair(serializeinfo, serializeinfo.serializer->Serialize(*this, serializeinfo.tagwl))); + return serlist.back().second; +} + +void ClientProtocol::Event::GetMessagesForUser(LocalUser* user, MessageList& messagelist) +{ + if (!eventinit_done) + { + eventinit_done = true; + FOREACH_MOD_CUSTOM(*event, EventHook, OnEventInit, (*this)); + } + + // Most of the time there's only a single message but in rare cases there are more + if (initialmsg) + messagelist.assign(1, initialmsg); + else + messagelist = *initialmsglist; + + // Let modules modify the message list + ModResult res; + FIRST_MOD_RESULT_CUSTOM(*event, EventHook, OnPreEventSend, res, (user, *this, messagelist)); + if (res == MOD_RES_DENY) + messagelist.clear(); +} diff --git a/src/command_parse.cpp b/src/command_parse.cpp index 53f19437b..d4dd7da21 100644 --- a/src/command_parse.cpp +++ b/src/command_parse.cpp @@ -23,117 +23,94 @@ #include "inspircd.h" -int InspIRCd::PassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype) +bool InspIRCd::PassCompare(Extensible* ex, const std::string& data, const std::string& input, const std::string& hashtype) { ModResult res; FIRST_MOD_RESULT(OnPassCompare, res, (ex, data, input, hashtype)); /* Module matched */ if (res == MOD_RES_ALLOW) - return 0; + return true; /* Module explicitly didnt match */ if (res == MOD_RES_DENY) - return 1; + return false; /* We dont handle any hash types except for plaintext - Thanks tra26 */ if (!hashtype.empty() && hashtype != "plaintext") - /* See below. 1 because they dont match */ - return 1; + return false; - return (data != input); // this seems back to front, but returns 0 if they *match*, 1 else + return TimingSafeCompare(data, input); } -/* LoopCall is used to call a command classes handler repeatedly based on the contents of a comma seperated list. - * There are two overriden versions of this method, one of which takes two potential lists and the other takes one. - * We need a version which takes two potential lists for JOIN, because a JOIN may contain two lists of items at once, - * the channel names and their keys as follows: - * JOIN #chan1,#chan2,#chan3 key1,,key3 - * Therefore, we need to deal with both lists concurrently. The first instance of this method does that by creating - * two instances of irc::commasepstream and reading them both together until the first runs out of tokens. - * The second version is much simpler and just has the one stream to read, and is used in NAMES, WHOIS, PRIVMSG etc. - * Both will only parse until they reach ServerInstance->Config->MaxTargets number of targets, to stop abuse via spam. - */ -int CommandParser::LoopCall(User* user, Command* CommandObj, const std::vector<std::string>& parameters, unsigned int splithere, int extra, bool usemax) +bool CommandParser::LoopCall(User* user, Command* handler, const CommandBase::Params& parameters, unsigned int splithere, int extra, bool usemax) { if (splithere >= parameters.size()) - return 0; - - if (extra >= (signed)parameters.size()) - extra = -1; + return false; - /* First check if we have more than one item in the list, if we don't we return zero here and the handler + /* First check if we have more than one item in the list, if we don't we return false here and the handler * which called us just carries on as it was. */ if (parameters[splithere].find(',') == std::string::npos) - return 0; + return false; /** Some lame ircds will weed out dupes using some shitty O(n^2) algorithm. * By using std::set (thanks for the idea w00t) we can cut this down a ton. * ...VOOODOOOO! + * + * Only check for duplicates if there is one list (allow them in JOIN). */ - std::set<irc::string> dupes; + insp::flat_set<std::string, irc::insensitive_swo> dupes; + bool check_dupes = (extra < 0); - /* Create two lists, one for channel names, one for keys + /* Create two sepstreams, if we have only one list, then initialize the second sepstream with + * an empty string. The second parameter of the constructor of the sepstream tells whether + * or not to allow empty tokens. + * We allow empty keys, so "JOIN #a,#b ,bkey" will be interpreted as "JOIN #a", "JOIN #b bkey" */ irc::commasepstream items1(parameters[splithere]); - irc::commasepstream items2(extra >= 0 ? parameters[extra] : ""); - std::string extrastuff; + irc::commasepstream items2(extra >= 0 ? parameters[extra] : "", true); std::string item; unsigned int max = 0; + LocalUser* localuser = IS_LOCAL(user); - /* Attempt to iterate these lists and call the command objech - * which called us, for every parameter pair until there are - * no more left to parse. + /* Attempt to iterate these lists and call the command handler + * for every parameter or parameter pair until there are no more + * left to parse. */ while (items1.GetToken(item) && (!usemax || max++ < ServerInstance->Config->MaxTargets)) { - if (dupes.find(item.c_str()) == dupes.end()) + if ((!check_dupes) || (dupes.insert(item).second)) { - std::vector<std::string> new_parameters(parameters); - - if (!items2.GetToken(extrastuff)) - extrastuff.clear(); - + CommandBase::Params new_parameters(parameters); new_parameters[splithere] = item; - if (extra >= 0) - new_parameters[extra] = extrastuff; - - CommandObj->Handle(new_parameters, user); - - dupes.insert(item.c_str()); - } - } - return 1; -} -bool CommandParser::IsValidCommand(const std::string &commandname, unsigned int pcnt, User * user) -{ - Commandtable::iterator n = cmdlist.find(commandname); - - if (n != cmdlist.end()) - { - if ((pcnt >= n->second->min_params)) - { - if (IS_LOCAL(user) && n->second->flags_needed) + if (extra >= 0) { - if (user->IsModeSet(n->second->flags_needed)) - { - return (user->HasPermission(commandname)); - } + // If we have two lists then get the next item from the second list. + // In case it runs out of elements then 'item' will be an empty string. + items2.GetToken(item); + new_parameters[extra] = item; } - else + + CommandBase::Params params(new_parameters, parameters.GetTags()); + CmdResult result = handler->Handle(user, params); + if (localuser) { - return true; + // Run the OnPostCommand hook with the last parameter being true to indicate + // that the event is being called in a loop. + item.clear(); + FOREACH_MOD(OnPostCommand, (handler, new_parameters, localuser, result, true)); } } } - return false; + + return true; } Command* CommandParser::GetHandler(const std::string &commandname) { - Commandtable::iterator n = cmdlist.find(commandname); + CommandMap::iterator n = cmdlist.find(commandname); if (n != cmdlist.end()) return n->second; @@ -142,9 +119,9 @@ Command* CommandParser::GetHandler(const std::string &commandname) // calls a handler function for a command -CmdResult CommandParser::CallHandler(const std::string &commandname, const std::vector<std::string>& parameters, User *user) +CmdResult CommandParser::CallHandler(const std::string& commandname, const CommandBase::Params& parameters, User* user, Command** cmd) { - Commandtable::iterator n = cmdlist.find(commandname); + CommandMap::iterator n = cmdlist.find(commandname); if (n != cmdlist.end()) { @@ -174,49 +151,30 @@ CmdResult CommandParser::CallHandler(const std::string &commandname, const std:: if (bOkay) { - return n->second->Handle(parameters,user); + if (cmd) + *cmd = n->second; + + ClientProtocol::TagMap tags; + return n->second->Handle(user, CommandBase::Params(parameters, tags)); } } } return CMD_INVALID; } -bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) +void CommandParser::ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& command_p) { - std::vector<std::string> command_p; - irc::tokenstream tokens(cmd); - std::string command, token; - tokens.GetToken(command); - - /* A client sent a nick prefix on their command (ick) - * rhapsody and some braindead bouncers do this -- - * the rfc says they shouldnt but also says the ircd should - * discard it if they do. - */ - if ((command.empty() || command[0] == ':') && !tokens.GetToken(command)) - { - // Penalise the user to discourage them from spamming the server with trash. - user->CommandFloodPenalty += 2000; - return false; - } - - while (tokens.GetToken(token) && (command_p.size() <= MAXPARAMETERS)) - command_p.push_back(token); - - std::transform(command.begin(), command.end(), command.begin(), ::toupper); - /* find the command, check it exists */ - Commandtable::iterator cm = cmdlist.find(command); + Command* handler = GetHandler(command); // Penalty to give if the command fails before the handler is executed unsigned int failpenalty = 0; /* Modify the user's penalty regardless of whether or not the command exists */ - bool do_more = true; if (!user->HasPrivPermission("users/flood/no-throttle")) { // If it *doesn't* exist, give it a slightly heftier penalty than normal to deter flooding us crap - unsigned int penalty = (cm != cmdlist.end() ? cm->second->Penalty * 1000 : 2000); + unsigned int penalty = (handler ? handler->Penalty * 1000 : 2000); user->CommandFloodPenalty += penalty; // Increase their penalty later if we fail and the command has 0 penalty by default (i.e. in Command::Penalty) to @@ -226,13 +184,12 @@ bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) failpenalty = 1000; } - - if (cm == cmdlist.end()) + if (!handler) { ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false, cmd)); + FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false)); if (MOD_RESULT == MOD_RES_DENY) - return true; + return; /* * This double lookup is in case a module (abbreviation) wishes to change a command. @@ -241,17 +198,19 @@ bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) * Thanks dz for making me actually understand why this is necessary! * -- w00t */ - cm = cmdlist.find(command); - if (cm == cmdlist.end()) + handler = GetHandler(command); + if (!handler) { if (user->registered == REG_ALL) - user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str()); - ServerInstance->stats->statsUnknown++; - return true; + user->WriteNumeric(ERR_UNKNOWNCOMMAND, command, "Unknown command"); + ServerInstance->stats.Unknown++; + return; } } - if (cm->second->max_params && command_p.size() > cm->second->max_params) + // If we were given more parameters than max_params then append the excess parameter(s) + // to command_p[maxparams-1], i.e. to the last param that is still allowed + if (handler->max_params && command_p.size() > handler->max_params) { /* * command_p input (assuming max_params 1): @@ -260,32 +219,21 @@ bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) * a * test */ - std::string lparam; - /* - * The '-1' here is a clever trick, we'll go backwards throwing everything into a temporary param - * and then just toss that into the array. - * -- w00t - */ - while (command_p.size() > (cm->second->max_params - 1)) - { - // BE CAREFUL: .end() returns past the end of the vector, hence decrement. - std::vector<std::string>::iterator it = command_p.end() - 1; + // Iterator to the last parameter that will be kept + const CommandBase::Params::iterator lastkeep = command_p.begin() + (handler->max_params - 1); + // Iterator to the first excess parameter + const CommandBase::Params::iterator firstexcess = lastkeep + 1; - lparam.insert(0, " " + *(it)); - command_p.erase(it); // remove last element + // Append all excess parameter(s) to the last parameter, seperated by spaces + for (CommandBase::Params::const_iterator i = firstexcess; i != command_p.end(); ++i) + { + lastkeep->push_back(' '); + lastkeep->append(*i); } - /* we now have (each iteration): - * ' test' - * ' a test' - * ' is a test' <-- final string - * ...now remove the ' ' at the start... - */ - lparam.erase(lparam.begin()); - - /* param is now 'is a test', which is exactly what we wanted! */ - command_p.push_back(lparam); + // Erase the excess parameter(s) + command_p.erase(firstexcess, command_p.end()); } /* @@ -293,106 +241,146 @@ bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) * truncate to max_params if necessary. -- w00t */ ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false, cmd)); + FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false)); if (MOD_RESULT == MOD_RES_DENY) - return true; + return; /* activity resets the ping pending timer */ user->nping = ServerInstance->Time() + user->MyClass->GetPingTime(); - if (cm->second->flags_needed) + if (handler->flags_needed) { - if (!user->IsModeSet(cm->second->flags_needed)) + if (!user->IsModeSet(handler->flags_needed)) { user->CommandFloodPenalty += failpenalty; - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - You do not have the required operator privileges",user->nick.c_str()); - return do_more; + user->WriteNumeric(ERR_NOPRIVILEGES, "Permission Denied - You do not have the required operator privileges"); + return; } + if (!user->HasPermission(command)) { user->CommandFloodPenalty += failpenalty; - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to command %s", - user->nick.c_str(), user->oper->NameStr(), command.c_str()); - return do_more; + user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Oper type %s does not have access to command %s", + user->oper->name.c_str(), command.c_str())); + return; } } - if ((user->registered == REG_ALL) && (!IS_OPER(user)) && (cm->second->IsDisabled())) + + if ((user->registered == REG_ALL) && (!user->IsOper()) && (handler->IsDisabled())) { /* command is disabled! */ user->CommandFloodPenalty += failpenalty; if (ServerInstance->Config->DisabledDontExist) { - user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str()); + user->WriteNumeric(ERR_UNKNOWNCOMMAND, command, "Unknown command"); } else { - user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :This command has been disabled.", - user->nick.c_str(), command.c_str()); + user->WriteNumeric(ERR_UNKNOWNCOMMAND, command, "This command has been disabled."); } ServerInstance->SNO->WriteToSnoMask('a', "%s denied for %s (%s@%s)", - command.c_str(), user->nick.c_str(), user->ident.c_str(), user->host.c_str()); - return do_more; + command.c_str(), user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str()); + return; } - if ((!command_p.empty()) && (command_p.back().empty()) && (!cm->second->allow_empty_last_param)) + if ((!command_p.empty()) && (command_p.back().empty()) && (!handler->allow_empty_last_param)) command_p.pop_back(); - if (command_p.size() < cm->second->min_params) + if (command_p.size() < handler->min_params) { user->CommandFloodPenalty += failpenalty; - user->WriteNumeric(ERR_NEEDMOREPARAMS, "%s %s :Not enough parameters.", user->nick.c_str(), command.c_str()); - if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (cm->second->syntax.length())) - user->WriteNumeric(RPL_SYNTAX, "%s :SYNTAX %s %s", user->nick.c_str(), cm->second->name.c_str(), cm->second->syntax.c_str()); - return do_more; + user->WriteNumeric(ERR_NEEDMOREPARAMS, command, "Not enough parameters."); + if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (handler->syntax.length())) + user->WriteNumeric(RPL_SYNTAX, handler->name, handler->syntax); + return; } - if ((user->registered != REG_ALL) && (!cm->second->WorksBeforeReg())) + + if ((user->registered != REG_ALL) && (!handler->WorksBeforeReg())) { user->CommandFloodPenalty += failpenalty; - user->WriteNumeric(ERR_NOTREGISTERED, "%s %s :You have not registered", user->nick.c_str(), command.c_str()); - return do_more; + user->WriteNumeric(ERR_NOTREGISTERED, command, "You have not registered"); } else { /* passed all checks.. first, do the (ugly) stats counters. */ - cm->second->use_count++; - cm->second->total_bytes += cmd.length(); + handler->use_count++; /* module calls too */ - FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, true, cmd)); + FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, true)); if (MOD_RESULT == MOD_RES_DENY) - return do_more; + return; /* * WARNING: be careful, the user may be deleted soon */ - CmdResult result = cm->second->Handle(command_p, user); + CmdResult result = handler->Handle(user, command_p); - FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, command_p, user, result,cmd)); - return do_more; + FOREACH_MOD(OnPostCommand, (handler, command_p, user, result, false)); } } void CommandParser::RemoveCommand(Command* x) { - Commandtable::iterator n = cmdlist.find(x->name); + CommandMap::iterator n = cmdlist.find(x->name); if (n != cmdlist.end() && n->second == x) cmdlist.erase(n); } +CommandBase::CommandBase(Module* mod, const std::string& cmd, unsigned int minpara, unsigned int maxpara) + : ServiceProvider(mod, cmd, SERVICE_COMMAND) + , flags_needed(0) + , min_params(minpara) + , max_params(maxpara) + , use_count(0) + , disabled(false) + , works_before_reg(false) + , allow_empty_last_param(true) + , Penalty(1) +{ +} + +CommandBase::~CommandBase() +{ +} + +void CommandBase::EncodeParameter(std::string& parameter, unsigned int index) +{ +} + +RouteDescriptor CommandBase::GetRouting(User* user, const Params& parameters) +{ + return ROUTE_LOCALONLY; +} + +Command::Command(Module* mod, const std::string& cmd, unsigned int minpara, unsigned int maxpara) + : CommandBase(mod, cmd, minpara, maxpara) + , force_manual_route(false) +{ +} + Command::~Command() { - ServerInstance->Parser->RemoveCommand(this); + ServerInstance->Parser.RemoveCommand(this); } -bool CommandParser::ProcessBuffer(std::string &buffer,LocalUser *user) +void Command::RegisterService() { - if (!user || buffer.empty()) - return true; + if (!ServerInstance->Parser.AddCommand(this)) + throw ModuleException("Command already exists: " + name); +} + +void CommandParser::ProcessBuffer(LocalUser* user, const std::string& buffer) +{ + ClientProtocol::ParseOutput parseoutput; + if (!user->serializer->Parse(user, buffer, parseoutput)) + return; + + std::string& command = parseoutput.cmd; + std::transform(command.begin(), command.end(), command.begin(), ::toupper); - ServerInstance->Logs->Log("USERINPUT", RAWIO, "C[%s] I :%s %s", - user->uuid.c_str(), user->nick.c_str(), buffer.c_str()); - return ProcessCommand(user,buffer); + CommandBase::Params parameters(parseoutput.params, parseoutput.tags); + ProcessCommand(user, command, parameters); } bool CommandParser::AddCommand(Command *f) @@ -410,88 +398,64 @@ CommandParser::CommandParser() { } -int CommandParser::TranslateUIDs(const std::vector<TranslateType> to, const std::vector<std::string> &source, std::string &dest, bool prefix_final, Command* custom_translator) +std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const CommandBase::Params& source, bool prefix_final, CommandBase* custom_translator) { std::vector<TranslateType>::const_iterator types = to.begin(); - User* user = NULL; - unsigned int i; - int translations = 0; - dest.clear(); + std::string dest; - for(i=0; i < source.size(); i++) + for (unsigned int i = 0; i < source.size(); i++) { - TranslateType t; - std::string item = source[i]; - - if (types == to.end()) - t = TR_TEXT; - else + TranslateType t = TR_TEXT; + // They might supply less translation types than parameters, + // in that case pretend that all remaining types are TR_TEXT + if (types != to.end()) { t = *types; types++; } - if (prefix_final && i == source.size() - 1) - dest.append(":"); + bool last = (i == (source.size() - 1)); + if (prefix_final && last) + dest.push_back(':'); - switch (t) - { - case TR_NICK: - /* Translate single nickname */ - user = ServerInstance->FindNick(item); - if (user) - { - dest.append(user->uuid); - translations++; - } - else - dest.append(item); - break; - case TR_CUSTOM: - if (custom_translator) - custom_translator->EncodeParameter(item, i); - dest.append(item); - break; - case TR_END: - case TR_TEXT: - default: - /* Do nothing */ - dest.append(item); - break; - } - if (i != source.size() - 1) - dest.append(" "); + TranslateSingleParam(t, source[i], dest, custom_translator, i); + + if (!last) + dest.push_back(' '); } - return translations; + return dest; } -int CommandParser::TranslateUIDs(TranslateType to, const std::string &source, std::string &dest) +void CommandParser::TranslateSingleParam(TranslateType to, const std::string& item, std::string& dest, CommandBase* custom_translator, unsigned int paramnumber) { - User* user = NULL; - int translations = 0; - dest.clear(); - switch (to) { case TR_NICK: + { /* Translate single nickname */ - user = ServerInstance->FindNick(source); + User* user = ServerInstance->FindNick(item); if (user) + dest.append(user->uuid); + else + dest.append(item); + break; + } + case TR_CUSTOM: + { + if (custom_translator) { - dest = user->uuid; - translations++; + std::string translated = item; + custom_translator->EncodeParameter(translated, paramnumber); + dest.append(translated); + break; } - else - dest = source; - break; - case TR_END: + // If no custom translator was given, fall through + } case TR_TEXT: default: /* Do nothing */ - dest = source; + dest.append(item); break; } - - return translations; } diff --git a/src/commands.cpp b/src/commands.cpp index 5bf2aeb03..c5b34c72f 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -21,128 +21,38 @@ */ -/* $Core */ - #include "inspircd.h" -#include "xline.h" -#include "command_parse.h" - -/* All other ircds when doing this check usually just look for a string of *@* or *. We're smarter than that, though. */ -bool InspIRCd::HostMatchesEveryone(const std::string &mask, User* user) +CmdResult SplitCommand::Handle(User* user, const Params& parameters) { - long matches = 0; - - ConfigTag* insane = Config->ConfValue("insane"); - - if (insane->getBool("hostmasks")) - return false; - - float itrigger = insane->getFloat("trigger", 95.5); - - for (user_hash::iterator u = this->Users->clientlist->begin(); u != this->Users->clientlist->end(); u++) + switch (user->usertype) { - if ((InspIRCd::MatchCIDR(u->second->MakeHost(), mask, ascii_case_insensitive_map)) || - (InspIRCd::MatchCIDR(u->second->MakeHostIP(), mask, ascii_case_insensitive_map))) - { - matches++; - } - } + case USERTYPE_LOCAL: + return HandleLocal(static_cast<LocalUser*>(user), parameters); - if (!matches) - return false; + case USERTYPE_REMOTE: + return HandleRemote(static_cast<RemoteUser*>(user), parameters); - float percent = ((float)matches / (float)this->Users->clientlist->size()) * 100; - if (percent > itrigger) - { - SNO->WriteToSnoMask('a', "\2WARNING\2: %s tried to set a G/K/E line mask of %s, which covers %.2f%% of the network!",user->nick.c_str(),mask.c_str(),percent); - return true; + case USERTYPE_SERVER: + return HandleServer(static_cast<FakeUser*>(user), parameters); } - return false; -} -bool InspIRCd::IPMatchesEveryone(const std::string &ip, User* user) -{ - long matches = 0; - - ConfigTag* insane = Config->ConfValue("insane"); - - if (insane->getBool("ipmasks")) - return false; - - float itrigger = insane->getFloat("trigger", 95.5); - - for (user_hash::iterator u = this->Users->clientlist->begin(); u != this->Users->clientlist->end(); u++) - { - if (InspIRCd::MatchCIDR(u->second->GetIPString(), ip, ascii_case_insensitive_map)) - matches++; - } - - if (!matches) - return false; - - float percent = ((float)matches / (float)this->Users->clientlist->size()) * 100; - if (percent > itrigger) - { - SNO->WriteToSnoMask('a', "\2WARNING\2: %s tried to set a Z line mask of %s, which covers %.2f%% of the network!",user->nick.c_str(),ip.c_str(),percent); - return true; - } - return false; -} - -bool InspIRCd::NickMatchesEveryone(const std::string &nick, User* user) -{ - long matches = 0; - - ConfigTag* insane = Config->ConfValue("insane"); - - if (insane->getBool("nickmasks")) - return false; - - float itrigger = insane->getFloat("trigger", 95.5); - - for (user_hash::iterator u = this->Users->clientlist->begin(); u != this->Users->clientlist->end(); u++) - { - if (InspIRCd::Match(u->second->nick, nick)) - matches++; - } - - if (!matches) - return false; - - float percent = ((float)matches / (float)this->Users->clientlist->size()) * 100; - if (percent > itrigger) - { - SNO->WriteToSnoMask('a', "\2WARNING\2: %s tried to set a Q line mask of %s, which covers %.2f%% of the network!",user->nick.c_str(),nick.c_str(),percent); - return true; - } - return false; -} - -CmdResult SplitCommand::Handle(const std::vector<std::string>& parms, User* u) -{ - if (IS_LOCAL(u)) - return HandleLocal(parms, IS_LOCAL(u)); - if (IS_REMOTE(u)) - return HandleRemote(parms, IS_REMOTE(u)); - if (IS_SERVER(u)) - return HandleServer(parms, IS_SERVER(u)); - ServerInstance->Logs->Log("COMMAND", DEFAULT, "Unknown user type in command (uuid=%s)!", u->uuid.c_str()); + ServerInstance->Logs->Log("COMMAND", LOG_DEFAULT, "Unknown user type %d in command (uuid=%s)!", + user->usertype, user->uuid.c_str()); return CMD_INVALID; } -CmdResult SplitCommand::HandleLocal(const std::vector<std::string>&, LocalUser*) +CmdResult SplitCommand::HandleLocal(LocalUser* user, const Params& parameters) { return CMD_INVALID; } -CmdResult SplitCommand::HandleRemote(const std::vector<std::string>&, RemoteUser*) +CmdResult SplitCommand::HandleRemote(RemoteUser* user, const Params& parameters) { return CMD_INVALID; } -CmdResult SplitCommand::HandleServer(const std::vector<std::string>&, FakeUser*) +CmdResult SplitCommand::HandleServer(FakeUser* user, const Params& parameters) { return CMD_INVALID; } - diff --git a/src/commands/cmd_admin.cpp b/src/commands/cmd_admin.cpp deleted file mode 100644 index 0d6c235f0..000000000 --- a/src/commands/cmd_admin.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /ADMIN. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandAdmin : public Command -{ - public: - /** Constructor for admin. - */ - CommandAdmin(Module* parent) : Command(parent,"ADMIN",0,0) - { - Penalty = 2; - syntax = "[<servername>]"; - } - - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() > 0) - return ROUTE_UNICAST(parameters[0]); - return ROUTE_LOCALONLY; - } -}; - -/** Handle /ADMIN - */ -CmdResult CommandAdmin::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) - return CMD_SUCCESS; - user->SendText(":%s %03d %s :Administrative info for %s", ServerInstance->Config->ServerName.c_str(), - RPL_ADMINME, user->nick.c_str(),ServerInstance->Config->ServerName.c_str()); - if (!ServerInstance->Config->AdminName.empty()) - user->SendText(":%s %03d %s :Name - %s", ServerInstance->Config->ServerName.c_str(), - RPL_ADMINLOC1, user->nick.c_str(), ServerInstance->Config->AdminName.c_str()); - user->SendText(":%s %03d %s :Nickname - %s", ServerInstance->Config->ServerName.c_str(), - RPL_ADMINLOC2, user->nick.c_str(), ServerInstance->Config->AdminNick.c_str()); - user->SendText(":%s %03d %s :E-Mail - %s", ServerInstance->Config->ServerName.c_str(), - RPL_ADMINEMAIL, user->nick.c_str(), ServerInstance->Config->AdminEmail.c_str()); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandAdmin) diff --git a/src/commands/cmd_away.cpp b/src/commands/cmd_away.cpp deleted file mode 100644 index fa3f7fae9..000000000 --- a/src/commands/cmd_away.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /AWAY. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandAway : public Command -{ - public: - /** Constructor for away. - */ - CommandAway ( Module* parent) : Command(parent,"AWAY",0,0) { syntax = "[<message>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /AWAY - */ -CmdResult CommandAway::Handle (const std::vector<std::string>& parameters, User *user) -{ - ModResult MOD_RESULT; - - if ((parameters.size()) && (!parameters[0].empty())) - { - FIRST_MOD_RESULT(OnSetAway, MOD_RESULT, (user, parameters[0])); - - if (MOD_RESULT == MOD_RES_DENY && IS_LOCAL(user)) - return CMD_FAILURE; - - user->awaytime = ServerInstance->Time(); - user->awaymsg.assign(parameters[0], 0, ServerInstance->Config->Limits.MaxAway); - - user->WriteNumeric(RPL_NOWAWAY, "%s :You have been marked as being away",user->nick.c_str()); - } - else - { - FIRST_MOD_RESULT(OnSetAway, MOD_RESULT, (user, "")); - - if (MOD_RESULT == MOD_RES_DENY && IS_LOCAL(user)) - return CMD_FAILURE; - - user->awaymsg.clear(); - user->WriteNumeric(RPL_UNAWAY, "%s :You are no longer marked as being away",user->nick.c_str()); - } - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandAway) diff --git a/src/commands/cmd_clearcache.cpp b/src/commands/cmd_clearcache.cpp deleted file mode 100644 index 5914f9a8f..000000000 --- a/src/commands/cmd_clearcache.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /CLEARCACHE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandClearcache : public Command -{ - public: - /** Constructor for clearcache. - */ - CommandClearcache ( Module* parent) : Command(parent,"CLEARCACHE",0) { flags_needed = 'o'; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /CLEARCACHE - */ -CmdResult CommandClearcache::Handle (const std::vector<std::string>& parameters, User *user) -{ - int n = ServerInstance->Res->ClearCache(); - user->WriteServ("NOTICE %s :*** Cleared DNS cache of %d items.", user->nick.c_str(), n); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandClearcache) diff --git a/src/commands/cmd_commands.cpp b/src/commands/cmd_commands.cpp deleted file mode 100644 index 1555b4d04..000000000 --- a/src/commands/cmd_commands.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /COMMANDS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandCommands : public Command -{ - public: - /** Constructor for commands. - */ - CommandCommands(Module* parent) : Command(parent,"COMMANDS",0,0) - { - Penalty = 3; - } - - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /COMMANDS - */ -CmdResult CommandCommands::Handle (const std::vector<std::string>&, User *user) -{ - std::vector<std::string> list; - list.reserve(ServerInstance->Parser->cmdlist.size()); - for (Commandtable::iterator i = ServerInstance->Parser->cmdlist.begin(); i != ServerInstance->Parser->cmdlist.end(); i++) - { - // Don't show S2S commands to users - if (i->second->flags_needed == FLAG_SERVERONLY) - continue; - - Module* src = i->second->creator; - char buffer[MAXBUF]; - snprintf(buffer, MAXBUF, ":%s %03d %s :%s %s %d %d", - ServerInstance->Config->ServerName.c_str(), RPL_COMMANDS, user->nick.c_str(), - i->second->name.c_str(), src->ModuleSourceFile.c_str(), - i->second->min_params, i->second->Penalty); - list.push_back(buffer); - } - sort(list.begin(), list.end()); - for(unsigned int i=0; i < list.size(); i++) - user->Write(list[i]); - user->WriteNumeric(RPL_COMMANDSEND, "%s :End of COMMANDS list",user->nick.c_str()); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandCommands) diff --git a/src/commands/cmd_connect.cpp b/src/commands/cmd_connect.cpp deleted file mode 100644 index 8fb82fab4..000000000 --- a/src/commands/cmd_connect.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /CONNECT. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandConnect : public Command -{ - public: - /** Constructor for connect. - */ - CommandConnect ( Module* parent) : Command(parent,"CONNECT",1) { flags_needed = 'o'; syntax = "<servername>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/* - * This is handled by the server linking module, if necessary. Do not remove this stub. - */ - -/** Handle /CONNECT - */ -CmdResult CommandConnect::Handle (const std::vector<std::string>&, User *user) -{ - user->WriteServ( "NOTICE %s :Look into loading a linking module (like m_spanningtree) if you want this to do anything useful.", user->nick.c_str()); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandConnect) diff --git a/src/commands/cmd_die.cpp b/src/commands/cmd_die.cpp deleted file mode 100644 index 1d6640213..000000000 --- a/src/commands/cmd_die.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /DIE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandDie : public Command -{ - public: - /** Constructor for die. - */ - CommandDie ( Module* parent) : Command(parent,"DIE",1) { flags_needed = 'o'; syntax = "<password>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -#include "exitcodes.h" - -/** Handle /DIE - */ -CmdResult CommandDie::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (!ServerInstance->PassCompare(user, ServerInstance->Config->diepass, parameters[0].c_str(), ServerInstance->Config->powerhash)) - { - { - std::string diebuf = "*** DIE command from " + user->GetFullHost() + ". Terminating."; - ServerInstance->Logs->Log("COMMAND",SPARSE, diebuf); - ServerInstance->SendError(diebuf); - } - - ServerInstance->Exit(EXIT_STATUS_DIE); - } - else - { - ServerInstance->Logs->Log("COMMAND",SPARSE, "Failed /DIE command from %s", user->GetFullRealHost().c_str()); - ServerInstance->SNO->WriteGlobalSno('a', "Failed DIE Command from %s.", user->GetFullRealHost().c_str()); - return CMD_FAILURE; - } - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandDie) diff --git a/src/commands/cmd_invite.cpp b/src/commands/cmd_invite.cpp deleted file mode 100644 index c69e6bd1b..000000000 --- a/src/commands/cmd_invite.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /INVITE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandInvite : public Command -{ - public: - /** Constructor for invite. - */ - CommandInvite ( Module* parent) : Command(parent,"INVITE", 0, 0) { Penalty = 4; syntax = "[<nick> <channel>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /INVITE - */ -CmdResult CommandInvite::Handle (const std::vector<std::string>& parameters, User *user) -{ - ModResult MOD_RESULT; - - if (parameters.size() == 2 || parameters.size() == 3) - { - User* u; - if (IS_LOCAL(user)) - u = ServerInstance->FindNickOnly(parameters[0]); - else - u = ServerInstance->FindNick(parameters[0]); - - Channel* c = ServerInstance->FindChan(parameters[1]); - time_t timeout = 0; - if (parameters.size() == 3) - { - if (IS_LOCAL(user)) - timeout = ServerInstance->Time() + ServerInstance->Duration(parameters[2]); - else - timeout = ConvToInt(parameters[2]); - } - - if ((!c) || (!u) || (u->registered != REG_ALL)) - { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(), c ? parameters[0].c_str() : parameters[1].c_str()); - return CMD_FAILURE; - } - - if ((IS_LOCAL(user)) && (!c->HasUser(user))) - { - user->WriteNumeric(ERR_NOTONCHANNEL, "%s %s :You're not on that channel!",user->nick.c_str(), c->name.c_str()); - return CMD_FAILURE; - } - - if (c->HasUser(u)) - { - user->WriteNumeric(ERR_USERONCHANNEL, "%s %s %s :is already on channel",user->nick.c_str(),u->nick.c_str(),c->name.c_str()); - return CMD_FAILURE; - } - - FIRST_MOD_RESULT(OnUserPreInvite, MOD_RESULT, (user,u,c,timeout)); - - if (MOD_RESULT == MOD_RES_DENY) - { - return CMD_FAILURE; - } - else if (MOD_RESULT == MOD_RES_PASSTHRU) - { - if (IS_LOCAL(user)) - { - unsigned int rank = c->GetPrefixValue(user); - if (rank < HALFOP_VALUE) - { - // Check whether halfop mode is available and phrase error message accordingly - ModeHandler* mh = ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL); - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You must be a channel %soperator", - user->nick.c_str(), c->name.c_str(), (mh && mh->name == "halfop" ? "half-" : "")); - return CMD_FAILURE; - } - } - } - - if (IS_LOCAL(u)) - IS_LOCAL(u)->InviteTo(c->name.c_str(), timeout); - u->WriteFrom(user,"INVITE %s :%s",u->nick.c_str(),c->name.c_str()); - user->WriteNumeric(RPL_INVITING, "%s %s %s",user->nick.c_str(),u->nick.c_str(),c->name.c_str()); - if (ServerInstance->Config->AnnounceInvites != ServerConfig::INVITE_ANNOUNCE_NONE) - { - char prefix; - switch (ServerInstance->Config->AnnounceInvites) - { - case ServerConfig::INVITE_ANNOUNCE_OPS: - { - prefix = '@'; - break; - } - case ServerConfig::INVITE_ANNOUNCE_DYNAMIC: - { - ModeHandler* mh = ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL); - prefix = (mh && mh->name == "halfop" ? mh->GetPrefix() : '@'); - break; - } - default: - { - prefix = 0; - break; - } - } - c->WriteAllExceptSender(user, true, prefix, "NOTICE %s :*** %s invited %s into the channel", c->name.c_str(), user->nick.c_str(), u->nick.c_str()); - } - FOREACH_MOD(I_OnUserInvite,OnUserInvite(user,u,c,timeout)); - } - else if (IS_LOCAL(user)) - { - // pinched from ircu - invite with not enough parameters shows channels - // youve been invited to but haven't joined yet. - InviteList& il = IS_LOCAL(user)->GetInviteList(); - for (InviteList::const_iterator i = il.begin(); i != il.end(); ++i) - { - user->WriteNumeric(RPL_INVITELIST, "%s :%s",user->nick.c_str(), (*i)->chan->name.c_str()); - } - user->WriteNumeric(RPL_ENDOFINVITELIST, "%s :End of INVITE list",user->nick.c_str()); - } - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandInvite) diff --git a/src/commands/cmd_ison.cpp b/src/commands/cmd_ison.cpp deleted file mode 100644 index 01d12e13b..000000000 --- a/src/commands/cmd_ison.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /ISON. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandIson : public Command -{ - public: - /** Constructor for ison. - */ - CommandIson ( Module* parent) : Command(parent,"ISON", 1) { - syntax = "<nick> {nick}"; - } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /ISON - */ -CmdResult CommandIson::Handle (const std::vector<std::string>& parameters, User *user) -{ - std::map<User*,User*> ison_already; - User *u; - std::string reply = "303 " + user->nick + " :"; - - for (unsigned int i = 0; i < parameters.size(); i++) - { - u = ServerInstance->FindNickOnly(parameters[i]); - if (ison_already.find(u) != ison_already.end()) - continue; - - if ((u) && (u->registered == REG_ALL)) - { - reply.append(u->nick).append(" "); - if (reply.length() > 450) - { - user->WriteServ(reply); - reply = "303 " + user->nick + " :"; - } - ison_already[u] = u; - } - else - { - if ((i == parameters.size() - 1) && (parameters[i].find(' ') != std::string::npos)) - { - /* Its a space seperated list of nicks (RFC1459 says to support this) - */ - irc::spacesepstream list(parameters[i]); - std::string item; - - while (list.GetToken(item)) - { - u = ServerInstance->FindNickOnly(item); - if (ison_already.find(u) != ison_already.end()) - continue; - - if ((u) && (u->registered == REG_ALL)) - { - reply.append(u->nick).append(" "); - if (reply.length() > 450) - { - user->WriteServ(reply); - reply = "303 " + user->nick + " :"; - } - ison_already[u] = u; - } - } - } - } - } - - if (!reply.empty()) - user->WriteServ(reply); - - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandIson) diff --git a/src/commands/cmd_join.cpp b/src/commands/cmd_join.cpp deleted file mode 100644 index 6124fcc1c..000000000 --- a/src/commands/cmd_join.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /JOIN. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandJoin : public Command -{ - public: - /** Constructor for join. - */ - CommandJoin ( Module* parent) : Command(parent,"JOIN", 1, 2) { syntax = "<channel>{,<channel>} {<key>{,<key>}}"; Penalty = 2; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /JOIN - */ -CmdResult CommandJoin::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() > 1) - { - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0, 1, false)) - return CMD_SUCCESS; - - if (ServerInstance->IsChannel(parameters[0].c_str(), ServerInstance->Config->Limits.ChanMax)) - { - Channel::JoinUser(user, parameters[0].c_str(), false, parameters[1].c_str(), false); - return CMD_SUCCESS; - } - } - else - { - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0, -1, false)) - return CMD_SUCCESS; - - if (ServerInstance->IsChannel(parameters[0].c_str(), ServerInstance->Config->Limits.ChanMax)) - { - Channel::JoinUser(user, parameters[0].c_str(), false, "", false); - return CMD_SUCCESS; - } - } - - user->WriteNumeric(ERR_NOSUCHCHANNEL, "%s %s :Invalid channel name",user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; -} - -COMMAND_INIT(CommandJoin) diff --git a/src/commands/cmd_kick.cpp b/src/commands/cmd_kick.cpp deleted file mode 100644 index c497dd459..000000000 --- a/src/commands/cmd_kick.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /KICK. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandKick : public Command -{ - public: - /** Constructor for kick. - */ - CommandKick ( Module* parent) : Command(parent,"KICK",2,3) { syntax = "<channel> <nick>{,<nick>} [<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /KICK - */ -CmdResult CommandKick::Handle (const std::vector<std::string>& parameters, User *user) -{ - std::string reason; - Channel* c = ServerInstance->FindChan(parameters[0]); - User* u; - - if (ServerInstance->Parser->LoopCall(user, this, parameters, 1)) - return CMD_SUCCESS; - - if (IS_LOCAL(user)) - u = ServerInstance->FindNickOnly(parameters[1]); - else - u = ServerInstance->FindNick(parameters[1]); - - if ((!u) || (!c) || (u->registered != REG_ALL)) - { - user->WriteServ( "401 %s %s :No such nick/channel", user->nick.c_str(), c ? parameters[1].c_str() : parameters[0].c_str()); - return CMD_FAILURE; - } - - if ((IS_LOCAL(user)) && (!c->HasUser(user)) && (!ServerInstance->ULine(user->server))) - { - user->WriteServ( "442 %s %s :You're not on that channel!", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - if (parameters.size() > 2) - { - reason.assign(parameters[2], 0, ServerInstance->Config->Limits.MaxKick); - } - else - { - reason.assign(user->nick, 0, ServerInstance->Config->Limits.MaxKick); - } - - c->KickUser(user, u, reason.c_str()); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandKick) diff --git a/src/commands/cmd_kill.cpp b/src/commands/cmd_kill.cpp deleted file mode 100644 index 7bdf32c74..000000000 --- a/src/commands/cmd_kill.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /KILL. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandKill : public Command -{ - public: - /** Constructor for kill. - */ - CommandKill ( Module* parent) : Command(parent,"KILL",2,2) { - flags_needed = 'o'; - syntax = "<nickname> <reason>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); - } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - // local kills of remote users are routed via the OnRemoteKill hook - if (IS_LOCAL(user)) - return ROUTE_LOCALONLY; - return ROUTE_BROADCAST; - } -}; - -/** Handle /KILL - */ -CmdResult CommandKill::Handle (const std::vector<std::string>& parameters, User *user) -{ - /* Allow comma seperated lists of users for /KILL (thanks w00t) */ - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) - return CMD_SUCCESS; - - User *u = ServerInstance->FindNick(parameters[0]); - if ((u) && (!IS_SERVER(u))) - { - /* - * Here, we need to decide how to munge kill messages. Whether to hide killer, what to show opers, etc. - * We only do this when the command is being issued LOCALLY, for remote KILL, we just copy the message we got. - * - * This conditional is so that we only append the "Killed (" prefix ONCE. If killer is remote, then the kill - * just gets processed and passed on, otherwise, if they are local, it gets prefixed. Makes sense :-) -- w00t - */ - - std::string killreason; - if (IS_LOCAL(user)) - { - /* - * Moved this event inside the IS_LOCAL check also, we don't want half the network killing a user - * and the other half not. This would be a bad thing. ;p -- w00t - */ - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnKill, MOD_RESULT, (user, u, parameters[1])); - - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - - killreason = "Killed ("; - if (!ServerInstance->Config->HideKillsServer.empty()) - { - // hidekills is on, use it - killreason += ServerInstance->Config->HideKillsServer; - } - else - { - // hidekills is off, do nothing - killreason += user->nick; - } - - killreason += " (" + parameters[1] + "))"; - } - else - { - /* Leave it alone, remote server has already formatted it */ - killreason.assign(parameters[1], 0, ServerInstance->Config->Limits.MaxQuit); - } - - /* - * Now we need to decide whether or not to send a local or remote snotice. Currently this checking is a little flawed. - * No time to fix it right now, so left a note. -- w00t - */ - if (!IS_LOCAL(u)) - { - // remote kill - if (!ServerInstance->Config->HideULineKills || !ServerInstance->ULine(user->server)) - ServerInstance->SNO->WriteToSnoMask('K', "Remote kill by %s: %s (%s)", user->nick.c_str(), u->GetFullRealHost().c_str(), parameters[1].c_str()); - FOREACH_MOD(I_OnRemoteKill, OnRemoteKill(user, u, killreason, killreason)); - } - else - { - // local kill - /* - * XXX - this isn't entirely correct, servers A - B - C, oper on A, client on C. Oper kills client, A and B will get remote kill - * snotices, C will get a local kill snotice. this isn't accurate, and needs fixing at some stage. -- w00t - */ - if (!ServerInstance->Config->HideULineKills || !ServerInstance->ULine(user->server)) - { - if (IS_LOCAL(user)) - ServerInstance->SNO->WriteGlobalSno('k',"Local Kill by %s: %s (%s)", user->nick.c_str(), u->GetFullRealHost().c_str(), parameters[1].c_str()); - else - ServerInstance->SNO->WriteToSnoMask('k',"Local Kill by %s: %s (%s)", user->nick.c_str(), u->GetFullRealHost().c_str(), parameters[1].c_str()); - } - ServerInstance->Logs->Log("KILL",DEFAULT,"LOCAL KILL: %s :%s!%s!%s (%s)", u->nick.c_str(), ServerInstance->Config->ServerName.c_str(), user->dhost.c_str(), user->nick.c_str(), parameters[1].c_str()); - /* Bug #419, make sure this message can only occur once even in the case of multiple KILL messages crossing the network, and change to show - * hidekillsserver as source if possible - */ - if (!u->quitting) - { - u->Write(":%s KILL %s :%s!%s!%s (%s)", ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(), - u->nick.c_str(), - ServerInstance->Config->ServerName.c_str(), - ServerInstance->Config->HideKillsServer.empty() ? user->dhost.c_str() : ServerInstance->Config->HideKillsServer.c_str(), - ServerInstance->Config->HideKillsServer.empty() ? user->nick.c_str() : ServerInstance->Config->HideKillsServer.c_str(), - parameters[1].c_str()); - } - } - - // send the quit out - ServerInstance->Users->QuitUser(u, killreason); - } - else - { - user->WriteServ( "401 %s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandKill) diff --git a/src/commands/cmd_links.cpp b/src/commands/cmd_links.cpp deleted file mode 100644 index f4152ebc5..000000000 --- a/src/commands/cmd_links.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /LINKS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandLinks : public Command -{ - public: - /** Constructor for links. - */ - CommandLinks ( Module* parent) : Command(parent,"LINKS",0,0) { } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /LINKS - */ -CmdResult CommandLinks::Handle (const std::vector<std::string>&, User *user) -{ - user->WriteNumeric(364, "%s %s %s :0 %s",user->nick.c_str(),ServerInstance->Config->ServerName.c_str(),ServerInstance->Config->ServerName.c_str(),ServerInstance->Config->ServerDesc.c_str()); - user->WriteNumeric(365, "%s * :End of /LINKS list.",user->nick.c_str()); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandLinks) diff --git a/src/commands/cmd_list.cpp b/src/commands/cmd_list.cpp deleted file mode 100644 index eb28fb89c..000000000 --- a/src/commands/cmd_list.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /LIST. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandList : public Command -{ - public: - /** Constructor for list. - */ - CommandList ( Module* parent) : Command(parent,"LIST", 0, 0) { Penalty = 5; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - - -/** Handle /LIST - */ -CmdResult CommandList::Handle (const std::vector<std::string>& parameters, User *user) -{ - int minusers = 0, maxusers = 0; - - user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str()); - - if ((parameters.size() == 1) && (!parameters[0].empty())) - { - if (parameters[0][0] == '<') - { - maxusers = atoi((parameters[0].c_str())+1); - } - else if (parameters[0][0] == '>') - { - minusers = atoi((parameters[0].c_str())+1); - } - } - - for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); i++) - { - // attempt to match a glob pattern - long users = i->second->GetUserCounter(); - - bool too_few = (minusers && (users <= minusers)); - bool too_many = (maxusers && (users >= maxusers)); - - if (too_many || too_few) - continue; - - if (parameters.size() && !parameters[0].empty() && (parameters[0][0] != '<' && parameters[0][0] != '>')) - { - if (!InspIRCd::Match(i->second->name, parameters[0]) && !InspIRCd::Match(i->second->topic, parameters[0])) - continue; - } - - // if the channel is not private/secret, OR the user is on the channel anyway - bool n = (i->second->HasUser(user) || user->HasPrivPermission("channels/auspex")); - - // If we're not in the channel and +s is set on it, we want to ignore it - if (n || !i->second->IsModeSet('s')) - { - if (!n && i->second->IsModeSet('p')) - { - /* Channel is +p and user is outside/not privileged */ - user->WriteNumeric(322, "%s * %ld :",user->nick.c_str(), users); - } - else - { - /* User is in the channel/privileged, channel is not +s */ - user->WriteNumeric(322, "%s %s %ld :[+%s] %s",user->nick.c_str(),i->second->name.c_str(),users,i->second->ChanModes(n),i->second->topic.c_str()); - } - } - } - user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str()); - - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandList) diff --git a/src/commands/cmd_loadmodule.cpp b/src/commands/cmd_loadmodule.cpp deleted file mode 100644 index 379e597e4..000000000 --- a/src/commands/cmd_loadmodule.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /LOADMODULE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandLoadmodule : public Command -{ - public: - /** Constructor for loadmodule. - */ - CommandLoadmodule ( Module* parent) : Command(parent,"LOADMODULE",1,1) { flags_needed='o'; syntax = "<modulename>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /LOADMODULE - */ -CmdResult CommandLoadmodule::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (ServerInstance->Modules->Load(parameters[0])) - { - ServerInstance->SNO->WriteGlobalSno('a', "NEW MODULE: %s loaded %s",user->nick.c_str(), parameters[0].c_str()); - user->WriteNumeric(975, "%s %s :Module successfully loaded.",user->nick.c_str(), parameters[0].c_str()); - return CMD_SUCCESS; - } - else - { - user->WriteNumeric(974, "%s %s :%s",user->nick.c_str(), parameters[0].c_str(), ServerInstance->Modules->LastError().c_str()); - return CMD_FAILURE; - } -} - -COMMAND_INIT(CommandLoadmodule) diff --git a/src/commands/cmd_map.cpp b/src/commands/cmd_map.cpp deleted file mode 100644 index 385a2c752..000000000 --- a/src/commands/cmd_map.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -class CommandMap : public Command -{ - public: - /** Constructor for map. - */ - CommandMap ( Module* parent) : Command(parent,"MAP",0,0) { Penalty=2; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /MAP - */ -CmdResult CommandMap::Handle (const std::vector<std::string>&, User *user) -{ - // as with /LUSERS this does nothing without a linking - // module to override its behaviour and display something - // better. - - if (IS_OPER(user)) - { - user->WriteNumeric(006, "%s :%s [%s]", user->nick.c_str(), ServerInstance->Config->ServerName.c_str(), ServerInstance->Config->GetSID().c_str()); - user->WriteNumeric(007, "%s :End of /MAP", user->nick.c_str()); - return CMD_SUCCESS; - } - user->WriteNumeric(006, "%s :%s",user->nick.c_str(),ServerInstance->Config->ServerName.c_str()); - user->WriteNumeric(007, "%s :End of /MAP",user->nick.c_str()); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandMap) diff --git a/src/commands/cmd_mode.cpp b/src/commands/cmd_mode.cpp deleted file mode 100644 index 17e21b182..000000000 --- a/src/commands/cmd_mode.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /MODE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandMode : public Command -{ - public: - /** Constructor for mode. - */ - CommandMode ( Module* parent) : Command(parent,"MODE",1) { syntax = "<target> <modes> {<mode-parameters>}"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - - -/** Handle /MODE - */ -CmdResult CommandMode::Handle (const std::vector<std::string>& parameters, User *user) -{ - ServerInstance->Modes->Process(parameters, user, false); - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandMode) diff --git a/src/commands/cmd_modules.cpp b/src/commands/cmd_modules.cpp deleted file mode 100644 index 2a15b43ed..000000000 --- a/src/commands/cmd_modules.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /MODULES. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandModules : public Command -{ - public: - /** Constructor for modules. - */ - CommandModules(Module* parent) : Command(parent,"MODULES",0,0) - { - Penalty = 4; - syntax = "[<servername>]"; - } - - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() >= 1) - return ROUTE_UNICAST(parameters[0]); - return ROUTE_LOCALONLY; - } -}; - -/** Handle /MODULES - */ -CmdResult CommandModules::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() >= 1 && parameters[0] != ServerInstance->Config->ServerName) - return CMD_SUCCESS; - - std::vector<std::string> module_names = ServerInstance->Modules->GetAllModuleNames(0); - - for (unsigned int i = 0; i < module_names.size(); i++) - { - Module* m = ServerInstance->Modules->Find(module_names[i]); - Version V = m->GetVersion(); - - if (IS_LOCAL(user) && user->HasPrivPermission("servers/auspex")) - { - std::string flags("SvcC"); - int pos = 0; - for (int mult = 1; mult <= VF_OPTCOMMON; mult *= 2, ++pos) - if (!(V.Flags & mult)) - flags[pos] = '-'; - -#ifdef PURE_STATIC - user->SendText(":%s 702 %s :%p %s %s :%s", ServerInstance->Config->ServerName.c_str(), - user->nick.c_str(), (void*)m, module_names[i].c_str(), flags.c_str(), V.description.c_str()); -#else - std::string srcrev = m->ModuleDLLManager->GetVersion(); - user->SendText(":%s 702 %s :%p %s %s :%s - %s", ServerInstance->Config->ServerName.c_str(), - user->nick.c_str(), (void*)m, module_names[i].c_str(), flags.c_str(), V.description.c_str(), srcrev.c_str()); -#endif - } - else - { - user->SendText(":%s 702 %s :%s %s", ServerInstance->Config->ServerName.c_str(), - user->nick.c_str(), module_names[i].c_str(), V.description.c_str()); - } - } - user->SendText(":%s 703 %s :End of MODULES list", ServerInstance->Config->ServerName.c_str(), user->nick.c_str()); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandModules) diff --git a/src/commands/cmd_motd.cpp b/src/commands/cmd_motd.cpp deleted file mode 100644 index 9ed5ff188..000000000 --- a/src/commands/cmd_motd.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /MOTD. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandMotd : public Command -{ - public: - /** Constructor for motd. - */ - CommandMotd ( Module* parent) : Command(parent,"MOTD",0,1) { syntax = "[<servername>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() > 0) - return ROUTE_UNICAST(parameters[0]); - return ROUTE_LOCALONLY; - } -}; - -/** Handle /MOTD - */ -CmdResult CommandMotd::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) - { - // Give extra penalty if a non-oper queries the /MOTD of a remote server - LocalUser* localuser = IS_LOCAL(user); - if ((localuser) && (!IS_OPER(user))) - localuser->CommandFloodPenalty += 2000; - return CMD_SUCCESS; - } - - ConfigTag* tag = ServerInstance->Config->EmptyTag; - if (IS_LOCAL(user)) - tag = user->GetClass()->config; - std::string motd_name = tag->getString("motd", "motd"); - ConfigFileCache::iterator motd = ServerInstance->Config->Files.find(motd_name); - if (motd == ServerInstance->Config->Files.end()) - { - user->SendText(":%s %03d %s :Message of the day file is missing.", - ServerInstance->Config->ServerName.c_str(), ERR_NOMOTD, user->nick.c_str()); - return CMD_SUCCESS; - } - - user->SendText(":%s %03d %s :%s message of the day", ServerInstance->Config->ServerName.c_str(), - RPL_MOTDSTART, user->nick.c_str(), ServerInstance->Config->ServerName.c_str()); - - for (file_cache::iterator i = motd->second.begin(); i != motd->second.end(); i++) - user->SendText(":%s %03d %s :- %s", ServerInstance->Config->ServerName.c_str(), RPL_MOTD, user->nick.c_str(), i->c_str()); - - user->SendText(":%s %03d %s :End of message of the day.", ServerInstance->Config->ServerName.c_str(), RPL_ENDOFMOTD, user->nick.c_str()); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandMotd) diff --git a/src/commands/cmd_names.cpp b/src/commands/cmd_names.cpp deleted file mode 100644 index fd4a9cef5..000000000 --- a/src/commands/cmd_names.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /NAMES. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandNames : public Command -{ - public: - /** Constructor for names. - */ - CommandNames ( Module* parent) : Command(parent,"NAMES",0,0) { syntax = "{<channel>{,<channel>}}"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -/** Handle /NAMES - */ -CmdResult CommandNames::Handle (const std::vector<std::string>& parameters, User *user) -{ - Channel* c; - - if (!parameters.size()) - { - user->WriteNumeric(366, "%s * :End of /NAMES list.",user->nick.c_str()); - return CMD_SUCCESS; - } - - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) - return CMD_SUCCESS; - - c = ServerInstance->FindChan(parameters[0]); - if (c) - { - c->UserList(user); - } - else - { - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); - } - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandNames) diff --git a/src/commands/cmd_notice.cpp b/src/commands/cmd_notice.cpp deleted file mode 100644 index d5ef7ba1d..000000000 --- a/src/commands/cmd_notice.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -/** Handle /NOTICE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandNotice : public Command -{ - public: - /** Constructor for notice. - */ - CommandNotice ( Module* parent) : Command(parent,"NOTICE",2,2) { syntax = "<target>{,<target>} <message>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (IS_LOCAL(user)) - // This is handled by the OnUserNotice hook to split the LoopCall pieces - return ROUTE_LOCALONLY; - else - return ROUTE_MESSAGE(parameters[0]); - } -}; - - -CmdResult CommandNotice::Handle (const std::vector<std::string>& parameters, User *user) -{ - User *dest; - Channel *chan; - - CUList exempt_list; - - user->idle_lastmsg = ServerInstance->Time(); - - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) - return CMD_SUCCESS; - if (parameters[0][0] == '$') - { - if (!user->HasPrivPermission("users/mass-message")) - return CMD_SUCCESS; - - ModResult MOD_RESULT; - std::string temp = parameters[1]; - FIRST_MOD_RESULT(OnUserPreNotice, MOD_RESULT, (user, (void*)parameters[0].c_str(), TYPE_SERVER, temp, 0, exempt_list)); - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - const char* text = temp.c_str(); - const char* servermask = (parameters[0].c_str()) + 1; - - FOREACH_MOD(I_OnText,OnText(user, (void*)parameters[0].c_str(), TYPE_SERVER, text, 0, exempt_list)); - if (InspIRCd::Match(ServerInstance->Config->ServerName,servermask, NULL)) - { - user->SendAll("NOTICE", "%s", text); - } - FOREACH_MOD(I_OnUserNotice,OnUserNotice(user, (void*)parameters[0].c_str(), TYPE_SERVER, text, 0, exempt_list)); - return CMD_SUCCESS; - } - char status = 0; - const char* target = parameters[0].c_str(); - - if (ServerInstance->Modes->FindPrefix(*target)) - { - status = *target; - target++; - } - if (*target == '#') - { - chan = ServerInstance->FindChan(target); - - exempt_list.insert(user); - - if (chan) - { - if (IS_LOCAL(user)) - { - if ((chan->IsModeSet('n')) && (!chan->HasUser(user))) - { - user->WriteNumeric(404, "%s %s :Cannot send to channel (no external messages)", user->nick.c_str(), chan->name.c_str()); - return CMD_FAILURE; - } - if ((chan->IsModeSet('m')) && (chan->GetPrefixValue(user) < VOICE_VALUE)) - { - user->WriteNumeric(404, "%s %s :Cannot send to channel (+m)", user->nick.c_str(), chan->name.c_str()); - return CMD_FAILURE; - } - - if (ServerInstance->Config->RestrictBannedUsers) - { - if (chan->IsBanned(user)) - { - user->WriteNumeric(404, "%s %s :Cannot send to channel (you're banned)", user->nick.c_str(), chan->name.c_str()); - return CMD_FAILURE; - } - } - } - ModResult MOD_RESULT; - - std::string temp = parameters[1]; - FIRST_MOD_RESULT(OnUserPreNotice, MOD_RESULT, (user,chan,TYPE_CHANNEL,temp,status, exempt_list)); - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - - const char* text = temp.c_str(); - - if (temp.empty()) - { - user->WriteNumeric(412, "%s :No text to send", user->nick.c_str()); - return CMD_FAILURE; - } - - FOREACH_MOD(I_OnText,OnText(user,chan,TYPE_CHANNEL,text,status,exempt_list)); - - if (status) - { - if (ServerInstance->Config->UndernetMsgPrefix) - { - chan->WriteAllExcept(user, false, status, exempt_list, "NOTICE %c%s :%c %s", status, chan->name.c_str(), status, text); - } - else - { - chan->WriteAllExcept(user, false, status, exempt_list, "NOTICE %c%s :%s", status, chan->name.c_str(), text); - } - } - else - { - chan->WriteAllExcept(user, false, status, exempt_list, "NOTICE %s :%s", chan->name.c_str(), text); - } - - FOREACH_MOD(I_OnUserNotice,OnUserNotice(user,chan,TYPE_CHANNEL,text,status,exempt_list)); - } - else - { - /* no such nick/channel */ - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), target); - return CMD_FAILURE; - } - return CMD_SUCCESS; - } - - const char* destnick = parameters[0].c_str(); - - if (IS_LOCAL(user)) - { - const char* targetserver = strchr(destnick, '@'); - - if (targetserver) - { - std::string nickonly; - - nickonly.assign(destnick, 0, targetserver - destnick); - dest = ServerInstance->FindNickOnly(nickonly); - if (dest && strcasecmp(dest->server.c_str(), targetserver + 1)) - { - /* Incorrect server for user */ - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - } - else - dest = ServerInstance->FindNickOnly(destnick); - } - else - dest = ServerInstance->FindNick(destnick); - - if ((dest) && (dest->registered == REG_ALL)) - { - if (parameters[1].empty()) - { - user->WriteNumeric(412, "%s :No text to send", user->nick.c_str()); - return CMD_FAILURE; - } - - ModResult MOD_RESULT; - std::string temp = parameters[1]; - FIRST_MOD_RESULT(OnUserPreNotice, MOD_RESULT, (user,dest,TYPE_USER,temp,0,exempt_list)); - if (MOD_RESULT == MOD_RES_DENY) { - return CMD_FAILURE; - } - const char* text = temp.c_str(); - - FOREACH_MOD(I_OnText,OnText(user,dest,TYPE_USER,text,0,exempt_list)); - - if (IS_LOCAL(dest)) - { - // direct write, same server - user->WriteTo(dest, "NOTICE %s :%s", dest->nick.c_str(), text); - } - - FOREACH_MOD(I_OnUserNotice,OnUserNotice(user,dest,TYPE_USER,text,0,exempt_list)); - } - else - { - /* no such nick/channel */ - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - return CMD_SUCCESS; - -} - -COMMAND_INIT(CommandNotice) diff --git a/src/commands/cmd_oper.cpp b/src/commands/cmd_oper.cpp deleted file mode 100644 index 95f6b98df..000000000 --- a/src/commands/cmd_oper.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -bool OneOfMatches(const char* host, const char* ip, const char* hostlist); - -/** Handle /OPER. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandOper : public SplitCommand -{ - public: - /** Constructor for oper. - */ - CommandOper ( Module* parent) : SplitCommand(parent,"OPER",2,2) { syntax = "<username> <password>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser *user); -}; - -bool OneOfMatches(const char* host, const char* ip, const std::string& hostlist) -{ - std::stringstream hl(hostlist); - std::string xhost; - while (hl >> xhost) - { - if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) - { - return true; - } - } - return false; -} - -CmdResult CommandOper::HandleLocal(const std::vector<std::string>& parameters, LocalUser *user) -{ - char TheHost[MAXBUF]; - char TheIP[MAXBUF]; - bool match_login = false; - bool match_pass = false; - bool match_hosts = false; - - snprintf(TheHost,MAXBUF,"%s@%s",user->ident.c_str(),user->host.c_str()); - snprintf(TheIP, MAXBUF,"%s@%s",user->ident.c_str(),user->GetIPString()); - - OperIndex::iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]); - if ((i != ServerInstance->Config->oper_blocks.end()) && (i->second->oper_block)) - { - OperInfo* ifo = i->second; - ConfigTag* tag = ifo->oper_block; - match_login = true; - match_pass = !ServerInstance->PassCompare(user, tag->getString("password"), parameters[1], tag->getString("hash")); - match_hosts = OneOfMatches(TheHost,TheIP,tag->getString("host")); - - if (match_pass && match_hosts) - { - /* found this oper's opertype */ - user->Oper(ifo); - return CMD_SUCCESS; - } - } - - std::string fields; - if (!match_login) - fields.append("login "); - if (!match_pass) - fields.append("password "); - if (!match_hosts) - fields.append("hosts"); - - // tell them they suck, and lag them up to help prevent brute-force attacks - user->WriteNumeric(491, "%s :Invalid oper credentials",user->nick.c_str()); - user->CommandFloodPenalty += 10000; - - ServerInstance->SNO->WriteGlobalSno('o', "WARNING! Failed oper attempt by %s using login '%s': The following fields do not match: %s", user->GetFullRealHost().c_str(), parameters[0].c_str(), fields.c_str()); - ServerInstance->Logs->Log("OPER",DEFAULT,"OPER: Failed oper attempt by %s using login '%s': The following fields did not match: %s", user->GetFullRealHost().c_str(), parameters[0].c_str(), fields.c_str()); - return CMD_FAILURE; -} - -COMMAND_INIT(CommandOper) diff --git a/src/commands/cmd_part.cpp b/src/commands/cmd_part.cpp deleted file mode 100644 index aadb42d90..000000000 --- a/src/commands/cmd_part.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /PART. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandPart : public Command -{ - public: - /** Constructor for part. - */ - CommandPart (Module* parent) : Command(parent,"PART", 1, 2) { Penalty = 5; syntax = "<channel>{,<channel>} [<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandPart::Handle (const std::vector<std::string>& parameters, User *user) -{ - std::string reason; - - if (IS_LOCAL(user)) - { - if (!ServerInstance->Config->FixedPart.empty()) - reason = ServerInstance->Config->FixedPart; - else if (parameters.size() > 1) - reason = ServerInstance->Config->PrefixPart + parameters[1] + ServerInstance->Config->SuffixPart; - } - else - { - if (parameters.size() > 1) - reason = parameters[1]; - } - - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) - return CMD_SUCCESS; - - Channel* c = ServerInstance->FindChan(parameters[0]); - - if (c) - { - c->PartUser(user, reason); - } - else - { - user->WriteServ( "401 %s %s :No such channel", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandPart) diff --git a/src/commands/cmd_pass.cpp b/src/commands/cmd_pass.cpp deleted file mode 100644 index 9fc758874..000000000 --- a/src/commands/cmd_pass.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /PASS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandPass : public SplitCommand -{ - public: - /** Constructor for pass. - */ - CommandPass (Module* parent) : SplitCommand(parent,"PASS",1,1) { works_before_reg = true; Penalty = 0; syntax = "<password>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser *user); -}; - - -CmdResult CommandPass::HandleLocal(const std::vector<std::string>& parameters, LocalUser *user) -{ - // Check to make sure they haven't registered -- Fix by FCS - if (user->registered == REG_ALL) - { - user->CommandFloodPenalty += 1000; - user->WriteNumeric(ERR_ALREADYREGISTERED, "%s :You may not reregister",user->nick.c_str()); - return CMD_FAILURE; - } - user->password = parameters[0]; - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandPass) diff --git a/src/commands/cmd_ping.cpp b/src/commands/cmd_ping.cpp deleted file mode 100644 index dd14b52c8..000000000 --- a/src/commands/cmd_ping.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /PING. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandPing : public Command -{ - public: - /** Constructor for ping. - */ - CommandPing ( Module* parent) : Command(parent,"PING", 1, 2) { syntax = "<servername> [:<servername>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandPing::Handle (const std::vector<std::string>& parameters, User *user) -{ - user->WriteServ("PONG %s :%s", ServerInstance->Config->ServerName.c_str(), parameters[0].c_str()); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandPing) diff --git a/src/commands/cmd_pong.cpp b/src/commands/cmd_pong.cpp deleted file mode 100644 index 3b6f17f3b..000000000 --- a/src/commands/cmd_pong.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /PONG. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandPong : public Command -{ - public: - /** Constructor for pong. - */ - CommandPong ( Module* parent) : Command(parent,"PONG", 0, 1) { Penalty = 0; syntax = "<ping-text>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandPong::Handle (const std::vector<std::string>&, User *user) -{ - // set the user as alive so they survive to next ping - LocalUser* localuser = IS_LOCAL(user); - if (localuser) - { - // Increase penalty unless we've sent a PING and this is the reply - if (localuser->lastping) - localuser->CommandFloodPenalty += 1000; - else - localuser->lastping = 1; - } - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandPong) diff --git a/src/commands/cmd_privmsg.cpp b/src/commands/cmd_privmsg.cpp deleted file mode 100644 index cefdd4800..000000000 --- a/src/commands/cmd_privmsg.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /PRIVMSG. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandPrivmsg : public Command -{ - public: - /** Constructor for privmsg. - */ - CommandPrivmsg ( Module* parent) : Command(parent,"PRIVMSG",2,2) { syntax = "<target>{,<target>} <message>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (IS_LOCAL(user)) - // This is handled by the OnUserMessage hook to split the LoopCall pieces - return ROUTE_LOCALONLY; - else - return ROUTE_MESSAGE(parameters[0]); - } -}; - -CmdResult CommandPrivmsg::Handle (const std::vector<std::string>& parameters, User *user) -{ - User *dest; - Channel *chan; - CUList except_list; - - user->idle_lastmsg = ServerInstance->Time(); - - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) - return CMD_SUCCESS; - - if (parameters[0][0] == '$') - { - if (!user->HasPrivPermission("users/mass-message")) - return CMD_SUCCESS; - - ModResult MOD_RESULT; - std::string temp = parameters[1]; - FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, (void*)parameters[0].c_str(), TYPE_SERVER, temp, 0, except_list)); - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - - const char* text = temp.c_str(); - const char* servermask = (parameters[0].c_str()) + 1; - - FOREACH_MOD(I_OnText,OnText(user, (void*)parameters[0].c_str(), TYPE_SERVER, text, 0, except_list)); - if (InspIRCd::Match(ServerInstance->Config->ServerName, servermask, NULL)) - { - user->SendAll("PRIVMSG", "%s", text); - } - FOREACH_MOD(I_OnUserMessage,OnUserMessage(user, (void*)parameters[0].c_str(), TYPE_SERVER, text, 0, except_list)); - return CMD_SUCCESS; - } - char status = 0; - const char* target = parameters[0].c_str(); - - if (ServerInstance->Modes->FindPrefix(*target)) - { - status = *target; - target++; - } - if (*target == '#') - { - chan = ServerInstance->FindChan(target); - - except_list.insert(user); - - if (chan) - { - if (IS_LOCAL(user) && chan->GetPrefixValue(user) < VOICE_VALUE) - { - if (chan->IsModeSet('n') && !chan->HasUser(user)) - { - user->WriteNumeric(404, "%s %s :Cannot send to channel (no external messages)", user->nick.c_str(), chan->name.c_str()); - return CMD_FAILURE; - } - - if (chan->IsModeSet('m')) - { - user->WriteNumeric(404, "%s %s :Cannot send to channel (+m)", user->nick.c_str(), chan->name.c_str()); - return CMD_FAILURE; - } - - if (ServerInstance->Config->RestrictBannedUsers) - { - if (chan->IsBanned(user)) - { - user->WriteNumeric(404, "%s %s :Cannot send to channel (you're banned)", user->nick.c_str(), chan->name.c_str()); - return CMD_FAILURE; - } - } - } - ModResult MOD_RESULT; - - std::string temp = parameters[1]; - FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user,chan,TYPE_CHANNEL,temp,status,except_list)); - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - - const char* text = temp.c_str(); - - /* Check again, a module may have zapped the input string */ - if (temp.empty()) - { - user->WriteNumeric(412, "%s :No text to send", user->nick.c_str()); - return CMD_FAILURE; - } - - FOREACH_MOD(I_OnText,OnText(user,chan,TYPE_CHANNEL,text,status,except_list)); - - if (status) - { - if (ServerInstance->Config->UndernetMsgPrefix) - { - chan->WriteAllExcept(user, false, status, except_list, "PRIVMSG %c%s :%c %s", status, chan->name.c_str(), status, text); - } - else - { - chan->WriteAllExcept(user, false, status, except_list, "PRIVMSG %c%s :%s", status, chan->name.c_str(), text); - } - } - else - { - chan->WriteAllExcept(user, false, status, except_list, "PRIVMSG %s :%s", chan->name.c_str(), text); - } - - FOREACH_MOD(I_OnUserMessage,OnUserMessage(user,chan,TYPE_CHANNEL,text,status,except_list)); - } - else - { - /* no such nick/channel */ - user->WriteNumeric(401, "%s %s :No such nick/channel", user->nick.c_str(), target); - return CMD_FAILURE; - } - return CMD_SUCCESS; - } - - const char* destnick = parameters[0].c_str(); - - if (IS_LOCAL(user)) - { - const char* targetserver = strchr(destnick, '@'); - - if (targetserver) - { - std::string nickonly; - - nickonly.assign(destnick, 0, targetserver - destnick); - dest = ServerInstance->FindNickOnly(nickonly); - if (dest && strcasecmp(dest->server.c_str(), targetserver + 1)) - { - /* Incorrect server for user */ - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - } - else - dest = ServerInstance->FindNickOnly(destnick); - } - else - dest = ServerInstance->FindNick(destnick); - - if ((dest) && (dest->registered == REG_ALL)) - { - if (parameters[1].empty()) - { - user->WriteNumeric(412, "%s :No text to send", user->nick.c_str()); - return CMD_FAILURE; - } - - if (IS_AWAY(dest)) - { - /* auto respond with aweh msg */ - user->WriteNumeric(301, "%s %s :%s", user->nick.c_str(), dest->nick.c_str(), dest->awaymsg.c_str()); - } - - ModResult MOD_RESULT; - - std::string temp = parameters[1]; - FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, dest, TYPE_USER, temp, 0, except_list)); - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - - const char* text = temp.c_str(); - - FOREACH_MOD(I_OnText,OnText(user, dest, TYPE_USER, text, 0, except_list)); - - if (IS_LOCAL(dest)) - { - // direct write, same server - user->WriteTo(dest, "PRIVMSG %s :%s", dest->nick.c_str(), text); - } - - FOREACH_MOD(I_OnUserMessage,OnUserMessage(user, dest, TYPE_USER, text, 0, except_list)); - } - else - { - /* no such nick/channel */ - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandPrivmsg) diff --git a/src/commands/cmd_quit.cpp b/src/commands/cmd_quit.cpp deleted file mode 100644 index 6a6b447e5..000000000 --- a/src/commands/cmd_quit.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /QUIT. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandQuit : public Command -{ - public: - /** Constructor for quit. - */ - CommandQuit ( Module* parent) : Command(parent,"QUIT",0,1) { works_before_reg = true; syntax = "[<message>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - - -CmdResult CommandQuit::Handle (const std::vector<std::string>& parameters, User *user) -{ - - std::string quitmsg; - - if (IS_LOCAL(user)) - { - if (!ServerInstance->Config->FixedQuit.empty()) - quitmsg = ServerInstance->Config->FixedQuit; - else - quitmsg = parameters.size() ? - ServerInstance->Config->PrefixQuit + std::string(parameters[0]) + ServerInstance->Config->SuffixQuit - : "Client exited"; - } - else - quitmsg = parameters.size() ? parameters[0] : "Client exited"; - - std::string* operquit = ServerInstance->OperQuit.get(user); - if (operquit) - { - ServerInstance->Users->QuitUser(user, quitmsg, operquit->c_str()); - } - else - { - ServerInstance->Users->QuitUser(user, quitmsg); - } - - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandQuit) diff --git a/src/commands/cmd_reloadmodule.cpp b/src/commands/cmd_reloadmodule.cpp deleted file mode 100644 index eac364f0e..000000000 --- a/src/commands/cmd_reloadmodule.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -class CommandReloadmodule : public Command -{ - public: - /** Constructor for reloadmodule. - */ - CommandReloadmodule ( Module* parent) : Command( parent, "RELOADMODULE",1) { flags_needed = 'o'; syntax = "<modulename>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -class ReloadModuleWorker : public HandlerBase1<void, bool> -{ - public: - const std::string name; - const std::string uid; - ReloadModuleWorker(const std::string& uuid, const std::string& modn) - : name(modn), uid(uuid) {} - void Call(bool result) - { - ServerInstance->SNO->WriteGlobalSno('a', "RELOAD MODULE: %s %ssuccessfully reloaded", - name.c_str(), result ? "" : "un"); - User* user = ServerInstance->FindNick(uid); - if (user) - user->WriteNumeric(975, "%s %s :Module %ssuccessfully reloaded.", - user->nick.c_str(), name.c_str(), result ? "" : "un"); - ServerInstance->GlobalCulls.AddItem(this); - } -}; - -CmdResult CommandReloadmodule::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters[0] == "cmd_reloadmodule.so") - { - user->WriteNumeric(975, "%s %s :You cannot reload cmd_reloadmodule.so (unload and load it)", - user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - Module* m = ServerInstance->Modules->Find(parameters[0]); - if (m) - { - ServerInstance->Modules->Reload(m, (creator->dying ? NULL : new ReloadModuleWorker(user->uuid, parameters[0]))); - return CMD_SUCCESS; - } - else - { - user->WriteNumeric(975, "%s %s :Could not find module by that name", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } -} - -COMMAND_INIT(CommandReloadmodule) diff --git a/src/commands/cmd_rules.cpp b/src/commands/cmd_rules.cpp deleted file mode 100644 index 7aacf8c31..000000000 --- a/src/commands/cmd_rules.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /RULES. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandRules : public Command -{ - public: - /** Constructor for rules. - */ - CommandRules ( Module* parent) : Command(parent,"RULES",0,0) { syntax = "[<servername>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() > 0) - return ROUTE_UNICAST(parameters[0]); - return ROUTE_LOCALONLY; - } -}; - -CmdResult CommandRules::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) - { - // Give extra penalty if a non-oper queries the /RULES of a remote server - LocalUser* localuser = IS_LOCAL(user); - if ((localuser) && (!IS_OPER(user))) - localuser->CommandFloodPenalty += 2000; - return CMD_SUCCESS; - } - - ConfigTag* tag = ServerInstance->Config->EmptyTag; - if (IS_LOCAL(user)) - tag = user->GetClass()->config; - std::string rules_name = tag->getString("rules", "rules"); - ConfigFileCache::iterator rules = ServerInstance->Config->Files.find(rules_name); - if (rules == ServerInstance->Config->Files.end()) - { - user->SendText(":%s %03d %s :RULES file is missing.", - ServerInstance->Config->ServerName.c_str(), ERR_NORULES, user->nick.c_str()); - return CMD_SUCCESS; - } - user->SendText(":%s %03d %s :%s server rules:", ServerInstance->Config->ServerName.c_str(), - RPL_RULESTART, user->nick.c_str(), ServerInstance->Config->ServerName.c_str()); - - for (file_cache::iterator i = rules->second.begin(); i != rules->second.end(); i++) - user->SendText(":%s %03d %s :- %s", ServerInstance->Config->ServerName.c_str(), RPL_RULES, user->nick.c_str(),i->c_str()); - - user->SendText(":%s %03d %s :End of RULES command.", ServerInstance->Config->ServerName.c_str(), RPL_RULESEND, user->nick.c_str()); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandRules) diff --git a/src/commands/cmd_server.cpp b/src/commands/cmd_server.cpp deleted file mode 100644 index 05954f3e8..000000000 --- a/src/commands/cmd_server.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /SERVER. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandServer : public Command -{ - public: - /** Constructor for server. - */ - CommandServer ( Module* parent) : Command(parent,"SERVER") { works_before_reg = true;} - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandServer::Handle (const std::vector<std::string>&, User *user) -{ - if (user->registered == REG_ALL) - { - user->WriteNumeric(ERR_ALREADYREGISTERED, "%s :You are already registered. (Perhaps your IRC client does not have a /SERVER command).",user->nick.c_str()); - } - else - { - user->WriteNumeric(ERR_NOTREGISTERED, "%s %s :You may not register as a server (servers have separate ports from clients, change your config)", user->nick.c_str(), name.c_str()); - } - return CMD_FAILURE; -} - -COMMAND_INIT(CommandServer) diff --git a/src/commands/cmd_squit.cpp b/src/commands/cmd_squit.cpp deleted file mode 100644 index ce73ee05b..000000000 --- a/src/commands/cmd_squit.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /SQUIT. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandSquit : public Command -{ - public: - /** Constructor for squit. - */ - CommandSquit ( Module* parent) : Command(parent,"SQUIT",1,2) { flags_needed = 'o'; syntax = "<servername>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - - -/* - * This is handled by the server linking module, if necessary. Do not remove this stub. - */ - - -CmdResult CommandSquit::Handle (const std::vector<std::string>&, User *user) -{ - user->WriteServ( "NOTICE %s :Look into loading a linking module (like m_spanningtree) if you want this to do anything useful.", user->nick.c_str()); - return CMD_FAILURE; -} - -COMMAND_INIT(CommandSquit) diff --git a/src/commands/cmd_stats.cpp b/src/commands/cmd_stats.cpp deleted file mode 100644 index aa5bf44cd..000000000 --- a/src/commands/cmd_stats.cpp +++ /dev/null @@ -1,439 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "xline.h" -#include "commands/cmd_whowas.h" - -#ifdef _WIN32 -#include <psapi.h> -#pragma comment(lib, "psapi.lib") // For GetProcessMemoryInfo() -#endif - -/** Handle /STATS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandStats : public Command -{ - void DoStats(char statschar, User* user, string_list &results); - public: - /** Constructor for stats. - */ - CommandStats ( Module* parent) : Command(parent,"STATS",1,2) { allow_empty_last_param = false; syntax = "<stats-symbol> [<servername>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() > 1) - return ROUTE_UNICAST(parameters[1]); - return ROUTE_LOCALONLY; - } -}; - -void CommandStats::DoStats(char statschar, User* user, string_list &results) -{ - std::string sn(ServerInstance->Config->ServerName); - - bool isPublic = ServerInstance->Config->UserStats.find(statschar) != std::string::npos; - bool isRemoteOper = IS_REMOTE(user) && IS_OPER(user); - bool isLocalOperWithPrivs = IS_LOCAL(user) && user->HasPrivPermission("servers/auspex"); - - if (!isPublic && !isRemoteOper && !isLocalOperWithPrivs) - { - ServerInstance->SNO->WriteToSnoMask('t', - "%s '%c' denied for %s (%s@%s)", - (IS_LOCAL(user) ? "Stats" : "Remote stats"), - statschar, user->nick.c_str(), user->ident.c_str(), user->host.c_str()); - results.push_back(sn + " 481 " + user->nick + " :Permission denied - STATS " + statschar + " requires the servers/auspex priv."); - return; - } - - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnStats, MOD_RESULT, (statschar, user, results)); - if (MOD_RESULT == MOD_RES_DENY) - { - results.push_back(sn+" 219 "+user->nick+" "+statschar+" :End of /STATS report"); - ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)", - (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->host.c_str()); - return; - } - - switch (statschar) - { - /* stats p (show listening ports) */ - case 'p': - { - for (std::vector<ListenSocket*>::const_iterator i = ServerInstance->ports.begin(); i != ServerInstance->ports.end(); ++i) - { - ListenSocket* ls = *i; - std::string ip = ls->bind_addr; - if (ip.empty()) - ip.assign("*"); - std::string type = ls->bind_tag->getString("type", "clients"); - std::string hook = ls->bind_tag->getString("ssl", "plaintext"); - - results.push_back(sn+" 249 "+user->nick+" :"+ ip + ":"+ConvToStr(ls->bind_port)+ - " (" + type + ", " + hook + ")"); - } - } - break; - - /* These stats symbols must be handled by a linking module */ - case 'n': - case 'c': - break; - - case 'i': - { - for (ClassVector::iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++) - { - ConnectClass* c = *i; - std::stringstream res; - res << sn << " 215 " << user->nick << " I " << c->name << ' '; - if (c->type == CC_ALLOW) - res << '+'; - if (c->type == CC_DENY) - res << '-'; - - if (c->type == CC_NAMED) - res << '*'; - else - res << c->host; - - res << ' ' << c->config->getString("port", "*") << ' '; - - res << c->GetRecvqMax() << ' ' << c->GetSendqSoftMax() << ' ' << c->GetSendqHardMax() - << ' ' << c->GetCommandRate() << ' ' << c->GetPenaltyThreshold(); - if (c->fakelag) - res << '*'; - results.push_back(res.str()); - } - } - break; - - case 'Y': - { - int idx = 0; - for (ClassVector::iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++) - { - ConnectClass* c = *i; - results.push_back(sn+" 215 "+user->nick+" i NOMATCH * "+c->GetHost()+" "+ConvToStr(c->limit ? c->limit : ServerInstance->SE->GetMaxFds())+" "+ConvToStr(idx)+" "+ServerInstance->Config->ServerName+" *"); - results.push_back(sn+" 218 "+user->nick+" Y "+ConvToStr(idx)+" "+ConvToStr(c->GetPingTime())+" 0 "+ConvToStr(c->GetSendqHardMax())+" :"+ - ConvToStr(c->GetRecvqMax())+" "+ConvToStr(c->GetRegTimeout())); - idx++; - } - } - break; - - case 'U': - { - for(std::map<irc::string, bool>::iterator i = ServerInstance->Config->ulines.begin(); i != ServerInstance->Config->ulines.end(); ++i) - { - results.push_back(sn+" 248 "+user->nick+" U "+std::string(i->first.c_str())); - } - } - break; - - case 'P': - { - unsigned int idx = 0; - for (std::list<User*>::const_iterator i = ServerInstance->Users->all_opers.begin(); i != ServerInstance->Users->all_opers.end(); ++i) - { - User* oper = *i; - if (!ServerInstance->ULine(oper->server)) - { - results.push_back(sn+" 249 " + user->nick + " :" + oper->nick + " (" + oper->ident + "@" + oper->dhost + ") Idle: " + - (IS_LOCAL(oper) ? ConvToStr(ServerInstance->Time() - oper->idle_lastmsg) + " secs" : "unavailable")); - idx++; - } - } - results.push_back(sn+" 249 "+user->nick+" :"+ConvToStr(idx)+" OPER(s)"); - } - break; - - case 'k': - ServerInstance->XLines->InvokeStats("K",216,user,results); - break; - case 'g': - ServerInstance->XLines->InvokeStats("G",223,user,results); - break; - case 'q': - ServerInstance->XLines->InvokeStats("Q",217,user,results); - break; - case 'Z': - ServerInstance->XLines->InvokeStats("Z",223,user,results); - break; - case 'e': - ServerInstance->XLines->InvokeStats("E",223,user,results); - break; - case 'E': - results.push_back(sn+" 249 "+user->nick+" :Total events: "+ConvToStr(ServerInstance->SE->TotalEvents)); - results.push_back(sn+" 249 "+user->nick+" :Read events: "+ConvToStr(ServerInstance->SE->ReadEvents)); - results.push_back(sn+" 249 "+user->nick+" :Write events: "+ConvToStr(ServerInstance->SE->WriteEvents)); - results.push_back(sn+" 249 "+user->nick+" :Error events: "+ConvToStr(ServerInstance->SE->ErrorEvents)); - break; - - /* stats m (list number of times each command has been used, plus bytecount) */ - case 'm': - for (Commandtable::iterator i = ServerInstance->Parser->cmdlist.begin(); i != ServerInstance->Parser->cmdlist.end(); i++) - { - if (i->second->use_count) - { - /* RPL_STATSCOMMANDS */ - results.push_back(sn+" 212 "+user->nick+" "+i->second->name+" "+ConvToStr(i->second->use_count)+" "+ConvToStr(i->second->total_bytes)); - } - } - break; - - /* stats z (debug and memory info) */ - case 'z': - { - results.push_back(sn+" 249 "+user->nick+" :Users: "+ConvToStr(ServerInstance->Users->clientlist->size())); - results.push_back(sn+" 249 "+user->nick+" :Channels: "+ConvToStr(ServerInstance->chanlist->size())); - results.push_back(sn+" 249 "+user->nick+" :Commands: "+ConvToStr(ServerInstance->Parser->cmdlist.size())); - - if (ServerInstance->Config->WhoWasGroupSize && ServerInstance->Config->WhoWasMaxGroups) - { - Module* whowas = ServerInstance->Modules->Find("cmd_whowas.so"); - if (whowas) - { - WhowasRequest req(NULL, whowas, WhowasRequest::WHOWAS_STATS); - req.user = user; - req.Send(); - results.push_back(sn+" 249 "+user->nick+" :"+req.value); - } - } - - float kbitpersec_in, kbitpersec_out, kbitpersec_total; - char kbitpersec_in_s[30], kbitpersec_out_s[30], kbitpersec_total_s[30]; - - ServerInstance->SE->GetStats(kbitpersec_in, kbitpersec_out, kbitpersec_total); - - snprintf(kbitpersec_total_s, 30, "%03.5f", kbitpersec_total); - snprintf(kbitpersec_out_s, 30, "%03.5f", kbitpersec_out); - snprintf(kbitpersec_in_s, 30, "%03.5f", kbitpersec_in); - - results.push_back(sn+" 249 "+user->nick+" :Bandwidth total: "+ConvToStr(kbitpersec_total_s)+" kilobits/sec"); - results.push_back(sn+" 249 "+user->nick+" :Bandwidth out: "+ConvToStr(kbitpersec_out_s)+" kilobits/sec"); - results.push_back(sn+" 249 "+user->nick+" :Bandwidth in: "+ConvToStr(kbitpersec_in_s)+" kilobits/sec"); - -#ifndef _WIN32 - /* Moved this down here so all the not-windows stuff (look w00tie, I didn't say win32!) is in one ifndef. - * Also cuts out some identical code in both branches of the ifndef. -- Om - */ - rusage R; - - /* Not sure why we were doing '0' with a RUSAGE_SELF comment rather than just using RUSAGE_SELF -- Om */ - if (!getrusage(RUSAGE_SELF,&R)) /* RUSAGE_SELF */ - { - results.push_back(sn+" 249 "+user->nick+" :Total allocation: "+ConvToStr(R.ru_maxrss)+"K"); - results.push_back(sn+" 249 "+user->nick+" :Signals: "+ConvToStr(R.ru_nsignals)); - results.push_back(sn+" 249 "+user->nick+" :Page faults: "+ConvToStr(R.ru_majflt)); - results.push_back(sn+" 249 "+user->nick+" :Swaps: "+ConvToStr(R.ru_nswap)); - results.push_back(sn+" 249 "+user->nick+" :Context Switches: Voluntary; "+ConvToStr(R.ru_nvcsw)+" Involuntary; "+ConvToStr(R.ru_nivcsw)); - - char percent[30]; - - float n_elapsed = (ServerInstance->Time() - ServerInstance->stats->LastSampled.tv_sec) * 1000000 - + (ServerInstance->Time_ns() - ServerInstance->stats->LastSampled.tv_nsec) / 1000; - float n_eaten = ((R.ru_utime.tv_sec - ServerInstance->stats->LastCPU.tv_sec) * 1000000 + R.ru_utime.tv_usec - ServerInstance->stats->LastCPU.tv_usec); - float per = (n_eaten / n_elapsed) * 100; - - snprintf(percent, 30, "%03.5f%%", per); - results.push_back(sn+" 249 "+user->nick+" :CPU Use (now): "+percent); - - n_elapsed = ServerInstance->Time() - ServerInstance->startup_time; - n_eaten = (float)R.ru_utime.tv_sec + R.ru_utime.tv_usec / 100000.0; - per = (n_eaten / n_elapsed) * 100; - snprintf(percent, 30, "%03.5f%%", per); - results.push_back(sn+" 249 "+user->nick+" :CPU Use (total): "+percent); - } -#else - PROCESS_MEMORY_COUNTERS MemCounters; - if (GetProcessMemoryInfo(GetCurrentProcess(), &MemCounters, sizeof(MemCounters))) - { - results.push_back(sn+" 249 "+user->nick+" :Total allocation: "+ConvToStr((MemCounters.WorkingSetSize + MemCounters.PagefileUsage) / 1024)+"K"); - results.push_back(sn+" 249 "+user->nick+" :Pagefile usage: "+ConvToStr(MemCounters.PagefileUsage / 1024)+"K"); - results.push_back(sn+" 249 "+user->nick+" :Page faults: "+ConvToStr(MemCounters.PageFaultCount)); - } - - FILETIME CreationTime; - FILETIME ExitTime; - FILETIME KernelTime; - FILETIME UserTime; - LARGE_INTEGER ThisSample; - if(GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime) && - QueryPerformanceCounter(&ThisSample)) - { - KernelTime.dwHighDateTime += UserTime.dwHighDateTime; - KernelTime.dwLowDateTime += UserTime.dwLowDateTime; - double n_eaten = (double)( ( (uint64_t)(KernelTime.dwHighDateTime - ServerInstance->stats->LastCPU.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime - ServerInstance->stats->LastCPU.dwLowDateTime) )/100000; - double n_elapsed = (double)(ThisSample.QuadPart - ServerInstance->stats->LastSampled.QuadPart) / ServerInstance->stats->QPFrequency.QuadPart; - double per = (n_eaten/n_elapsed); - - char percent[30]; - - snprintf(percent, 30, "%03.5f%%", per); - results.push_back(sn+" 249 "+user->nick+" :CPU Use (now): "+percent); - - n_elapsed = ServerInstance->Time() - ServerInstance->startup_time; - n_eaten = (double)(( (uint64_t)(KernelTime.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime))/100000; - per = (n_eaten / n_elapsed); - snprintf(percent, 30, "%03.5f%%", per); - results.push_back(sn+" 249 "+user->nick+" :CPU Use (total): "+percent); - } -#endif - } - break; - - case 'T': - { - char buffer[MAXBUF]; - results.push_back(sn+" 249 "+user->nick+" :accepts "+ConvToStr(ServerInstance->stats->statsAccept)+" refused "+ConvToStr(ServerInstance->stats->statsRefused)); - results.push_back(sn+" 249 "+user->nick+" :unknown commands "+ConvToStr(ServerInstance->stats->statsUnknown)); - results.push_back(sn+" 249 "+user->nick+" :nick collisions "+ConvToStr(ServerInstance->stats->statsCollisions)); - results.push_back(sn+" 249 "+user->nick+" :dns requests "+ConvToStr(ServerInstance->stats->statsDnsGood+ServerInstance->stats->statsDnsBad)+" succeeded "+ConvToStr(ServerInstance->stats->statsDnsGood)+" failed "+ConvToStr(ServerInstance->stats->statsDnsBad)); - results.push_back(sn+" 249 "+user->nick+" :connection count "+ConvToStr(ServerInstance->stats->statsConnects)); - snprintf(buffer,MAXBUF," 249 %s :bytes sent %5.2fK recv %5.2fK", - user->nick.c_str(),ServerInstance->stats->statsSent / 1024.0,ServerInstance->stats->statsRecv / 1024.0); - results.push_back(sn+buffer); - } - break; - - /* stats o */ - case 'o': - { - ConfigTagList tags = ServerInstance->Config->ConfTags("oper"); - for(ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - results.push_back(sn+" 243 "+user->nick+" O "+tag->getString("host")+" * "+ - tag->getString("name") + " " + tag->getString("type")+" 0"); - } - } - break; - case 'O': - { - for(OperIndex::iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); i++) - { - // just the types, not the actual oper blocks... - if (i->first[0] != ' ') - continue; - OperInfo* tag = i->second; - tag->init(); - std::string umodes; - std::string cmodes; - for(char c='A'; c <= 'z'; c++) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_USER); - if (mh && mh->NeedsOper() && tag->AllowedUserModes[c - 'A']) - umodes.push_back(c); - mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL); - if (mh && mh->NeedsOper() && tag->AllowedChanModes[c - 'A']) - cmodes.push_back(c); - } - results.push_back(sn+" 243 "+user->nick+" O "+tag->NameStr() + " " + umodes + " " + cmodes); - } - } - break; - - /* stats l (show user I/O stats) */ - case 'l': - results.push_back(sn+" 211 "+user->nick+" :nick[ident@host] sendq cmds_out bytes_out cmds_in bytes_in time_open"); - for (LocalUserList::iterator n = ServerInstance->Users->local_users.begin(); n != ServerInstance->Users->local_users.end(); n++) - { - LocalUser* i = *n; - results.push_back(sn+" 211 "+user->nick+" "+i->nick+"["+i->ident+"@"+i->dhost+"] "+ConvToStr(i->eh.getSendQSize())+" "+ConvToStr(i->cmds_out)+" "+ConvToStr(i->bytes_out)+" "+ConvToStr(i->cmds_in)+" "+ConvToStr(i->bytes_in)+" "+ConvToStr(ServerInstance->Time() - i->signon)); - } - break; - - /* stats L (show user I/O stats with IP addresses) */ - case 'L': - results.push_back(sn+" 211 "+user->nick+" :nick[ident@ip] sendq cmds_out bytes_out cmds_in bytes_in time_open"); - for (LocalUserList::iterator n = ServerInstance->Users->local_users.begin(); n != ServerInstance->Users->local_users.end(); n++) - { - LocalUser* i = *n; - results.push_back(sn+" 211 "+user->nick+" "+i->nick+"["+i->ident+"@"+i->GetIPString()+"] "+ConvToStr(i->eh.getSendQSize())+" "+ConvToStr(i->cmds_out)+" "+ConvToStr(i->bytes_out)+" "+ConvToStr(i->cmds_in)+" "+ConvToStr(i->bytes_in)+" "+ConvToStr(ServerInstance->Time() - i->signon)); - } - break; - - /* stats u (show server uptime) */ - case 'u': - { - time_t current_time = 0; - current_time = ServerInstance->Time(); - time_t server_uptime = current_time - ServerInstance->startup_time; - struct tm* stime; - stime = gmtime(&server_uptime); - /* i dont know who the hell would have an ircd running for over a year nonstop, but - * Craig suggested this, and it seemed a good idea so in it went */ - if (stime->tm_year > 70) - { - char buffer[MAXBUF]; - snprintf(buffer,MAXBUF," 242 %s :Server up %d years, %d days, %.2d:%.2d:%.2d",user->nick.c_str(),(stime->tm_year-70),stime->tm_yday,stime->tm_hour,stime->tm_min,stime->tm_sec); - results.push_back(sn+buffer); - } - else - { - char buffer[MAXBUF]; - snprintf(buffer,MAXBUF," 242 %s :Server up %d days, %.2d:%.2d:%.2d",user->nick.c_str(),stime->tm_yday,stime->tm_hour,stime->tm_min,stime->tm_sec); - results.push_back(sn+buffer); - } - } - break; - - default: - break; - } - - results.push_back(sn+" 219 "+user->nick+" "+statschar+" :End of /STATS report"); - ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)", - (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->host.c_str()); - return; -} - -CmdResult CommandStats::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() > 1 && parameters[1] != ServerInstance->Config->ServerName) - { - // Give extra penalty if a non-oper does /STATS <remoteserver> - LocalUser* localuser = IS_LOCAL(user); - if ((localuser) && (!IS_OPER(user))) - localuser->CommandFloodPenalty += 2000; - return CMD_SUCCESS; - } - string_list values; - char search = parameters[0][0]; - DoStats(search, user, values); - for (size_t i = 0; i < values.size(); i++) - user->SendText(":%s", values[i].c_str()); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandStats) diff --git a/src/commands/cmd_time.cpp b/src/commands/cmd_time.cpp deleted file mode 100644 index db452d381..000000000 --- a/src/commands/cmd_time.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /TIME. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandTime : public Command -{ - public: - /** Constructor for time. - */ - CommandTime ( Module* parent) : Command(parent,"TIME",0,0) { syntax = "[<servername>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() > 0) - return ROUTE_UNICAST(parameters[0]); - return ROUTE_LOCALONLY; - } -}; - -CmdResult CommandTime::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) - return CMD_SUCCESS; - struct tm* timeinfo; - time_t local = ServerInstance->Time(); - - timeinfo = localtime(&local); - - char tms[26]; - snprintf(tms,26,"%s",asctime(timeinfo)); - tms[24] = 0; - - user->SendText(":%s %03d %s %s :%s", ServerInstance->Config->ServerName.c_str(), RPL_TIME, user->nick.c_str(),ServerInstance->Config->ServerName.c_str(),tms); - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandTime) diff --git a/src/commands/cmd_topic.cpp b/src/commands/cmd_topic.cpp deleted file mode 100644 index 412ca1c06..000000000 --- a/src/commands/cmd_topic.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /TOPIC. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandTopic : public Command -{ - public: - /** Constructor for topic. - */ - CommandTopic ( Module* parent) : Command(parent,"TOPIC",1, 2) { syntax = "<channel> [<topic>]"; Penalty = 2; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandTopic::Handle (const std::vector<std::string>& parameters, User *user) -{ - Channel* c; - - c = ServerInstance->FindChan(parameters[0]); - if (!c) - { - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - if (parameters.size() == 1) - { - if (c) - { - if ((c->IsModeSet('s')) && (!c->HasUser(user))) - { - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), c->name.c_str()); - return CMD_FAILURE; - } - - if (c->topic.length()) - { - user->WriteNumeric(332, "%s %s :%s", user->nick.c_str(), c->name.c_str(), c->topic.c_str()); - user->WriteNumeric(333, "%s %s %s %lu", user->nick.c_str(), c->name.c_str(), c->setby.c_str(), (unsigned long)c->topicset); - } - else - { - user->WriteNumeric(RPL_NOTOPICSET, "%s %s :No topic is set.", user->nick.c_str(), c->name.c_str()); - } - } - return CMD_SUCCESS; - } - else if (parameters.size()>1) - { - std::string t = parameters[1]; // needed, in case a module wants to change it - c->SetTopic(user, t); - } - - return CMD_SUCCESS; -} - - -COMMAND_INIT(CommandTopic) diff --git a/src/commands/cmd_unloadmodule.cpp b/src/commands/cmd_unloadmodule.cpp deleted file mode 100644 index 6d0f5f41c..000000000 --- a/src/commands/cmd_unloadmodule.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /UNLOADMODULE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandUnloadmodule : public Command -{ - public: - /** Constructor for unloadmodule. - */ - CommandUnloadmodule ( Module* parent) : Command(parent,"UNLOADMODULE",1) { flags_needed = 'o'; syntax = "<modulename>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandUnloadmodule::Handle (const std::vector<std::string>& parameters, User *user) -{ - if (parameters[0] == "cmd_unloadmodule.so" || parameters[0] == "cmd_loadmodule.so") - { - user->WriteNumeric(972, "%s %s :You cannot unload module loading commands!", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - - Module* m = ServerInstance->Modules->Find(parameters[0]); - if (m && ServerInstance->Modules->Unload(m)) - { - ServerInstance->SNO->WriteGlobalSno('a', "MODULE UNLOADED: %s unloaded %s", user->nick.c_str(), parameters[0].c_str()); - user->WriteNumeric(973, "%s %s :Module successfully unloaded.",user->nick.c_str(), parameters[0].c_str()); - } - else - { - user->WriteNumeric(972, "%s %s :%s",user->nick.c_str(), parameters[0].c_str(), - m ? ServerInstance->Modules->LastError().c_str() : "No such module"); - return CMD_FAILURE; - } - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandUnloadmodule) diff --git a/src/commands/cmd_user.cpp b/src/commands/cmd_user.cpp deleted file mode 100644 index 09e1e3319..000000000 --- a/src/commands/cmd_user.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /USER. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandUser : public SplitCommand -{ - public: - /** Constructor for user. - */ - CommandUser ( Module* parent) : SplitCommand(parent,"USER",4,4) { works_before_reg = true; Penalty = 0; syntax = "<username> <localhost> <remotehost> <GECOS>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser *user); -}; - -CmdResult CommandUser::HandleLocal(const std::vector<std::string>& parameters, LocalUser *user) -{ - /* A user may only send the USER command once */ - if (!(user->registered & REG_USER)) - { - if (!ServerInstance->IsIdent(parameters[0].c_str())) - { - /* - * RFC says we must use this numeric, so we do. Let's make it a little more nub friendly though. :) - * -- Craig, and then w00t. - */ - user->WriteNumeric(461, "%s USER :Your username is not valid",user->nick.c_str()); - return CMD_FAILURE; - } - else - { - /* - * The ident field is IDENTMAX+2 in size to account for +1 for the optional - * ~ character, and +1 for null termination, therefore we can safely use up to - * IDENTMAX here. - */ - user->ChangeIdent(parameters[0].c_str()); - user->fullname.assign(parameters[3].empty() ? "No info" : parameters[3], 0, ServerInstance->Config->Limits.MaxGecos); - user->registered = (user->registered | REG_USER); - } - } - else - { - user->CommandFloodPenalty += 1000; - user->WriteNumeric(462, "%s :You may not reregister", user->nick.c_str()); - return CMD_FAILURE; - } - - /* parameters 2 and 3 are local and remote hosts, and are ignored */ - if (user->registered == REG_NICKUSER) - { - ModResult MOD_RESULT; - - /* user is registered now, bit 0 = USER command, bit 1 = sent a NICK command */ - FIRST_MOD_RESULT(OnUserRegister, MOD_RESULT, (user)); - if (MOD_RESULT == MOD_RES_DENY) - return CMD_FAILURE; - - } - - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandUser) diff --git a/src/commands/cmd_version.cpp b/src/commands/cmd_version.cpp deleted file mode 100644 index 7620197fd..000000000 --- a/src/commands/cmd_version.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /VERSION. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandVersion : public Command -{ - public: - /** Constructor for version. - */ - CommandVersion ( Module* parent) : Command(parent,"VERSION",0,0) { syntax = "[<servername>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandVersion::Handle (const std::vector<std::string>&, User *user) -{ - std::string version = ServerInstance->GetVersionString(IS_OPER(user)); - user->WriteNumeric(RPL_VERSION, "%s :%s", user->nick.c_str(), version.c_str()); - ServerInstance->Config->Send005(user); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandVersion) diff --git a/src/commands/cmd_wallops.cpp b/src/commands/cmd_wallops.cpp deleted file mode 100644 index 198997a95..000000000 --- a/src/commands/cmd_wallops.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /WALLOPS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandWallops : public Command -{ - public: - /** Constructor for wallops. - */ - CommandWallops ( Module* parent) : Command(parent,"WALLOPS",1,1) { flags_needed = 'o'; syntax = "<any-text>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - -CmdResult CommandWallops::Handle (const std::vector<std::string>& parameters, User *user) -{ - std::string wallop("WALLOPS :"); - wallop.append(parameters[0]); - - for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); i++) - { - User* t = *i; - if (t->IsModeSet('w')) - user->WriteTo(t,wallop); - } - - FOREACH_MOD(I_OnWallops,OnWallops(user,parameters[0])); - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandWallops) diff --git a/src/commands/cmd_who.cpp b/src/commands/cmd_who.cpp deleted file mode 100644 index 1a18ab410..000000000 --- a/src/commands/cmd_who.cpp +++ /dev/null @@ -1,408 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /WHO. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandWho : public Command -{ - bool CanView(Channel* chan, User* user); - bool opt_viewopersonly; - bool opt_showrealhost; - bool opt_realname; - bool opt_mode; - bool opt_ident; - bool opt_metadata; - bool opt_port; - bool opt_away; - bool opt_local; - bool opt_far; - bool opt_time; - - public: - /** Constructor for who. - */ - CommandWho ( Module* parent) : Command(parent,"WHO", 1) { - syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [afhilMmoprt]"; - } - void SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string &initial, Channel* ch, User* u, std::vector<std::string> &whoresults); - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - bool whomatch(User* cuser, User* user, const char* matchtext); -}; - - -static Channel* get_first_visible_channel(User *source, User *u) -{ - UCListIter i = u->chans.begin(); - while (i != u->chans.end()) - { - Channel* c = *i++; - - /* XXX move the +I check into m_hidechans */ - if (source == u || !(c->IsModeSet('s') || c->IsModeSet('p') || u->IsModeSet('I')) || c->HasUser(source)) - return c; - } - return NULL; -} - -bool CommandWho::whomatch(User* cuser, User* user, const char* matchtext) -{ - bool match = false; - bool positive = false; - - if (user->registered != REG_ALL) - return false; - - if (opt_local && !IS_LOCAL(user)) - return false; - else if (opt_far && IS_LOCAL(user)) - return false; - - if (opt_mode) - { - for (const char* n = matchtext; *n; n++) - { - if (*n == '+') - { - positive = true; - continue; - } - else if (*n == '-') - { - positive = false; - continue; - } - if (user->IsModeSet(*n) != positive) - return false; - } - return true; - } - else - { - /* - * This was previously one awesome pile of ugly nested if, when really, it didn't need - * to be, since only one condition was ever checked, a chained if works just fine. - * -- w00t - */ - if (opt_metadata) - { - match = false; - const Extensible::ExtensibleStore& list = user->GetExtList(); - for(Extensible::ExtensibleStore::const_iterator i = list.begin(); i != list.end(); ++i) - if (InspIRCd::Match(i->first->name, matchtext)) - match = true; - } - else if (opt_realname) - match = InspIRCd::Match(user->fullname, matchtext); - else if (opt_showrealhost) - match = InspIRCd::Match(user->host, matchtext, ascii_case_insensitive_map); - else if (opt_ident) - match = InspIRCd::Match(user->ident, matchtext, ascii_case_insensitive_map); - else if (opt_port) - { - irc::portparser portrange(matchtext, false); - long portno = -1; - while ((portno = portrange.GetToken())) - if (IS_LOCAL(user) && portno == IS_LOCAL(user)->GetServerPort()) - { - match = true; - break; - } - } - else if (opt_away) - match = InspIRCd::Match(user->awaymsg, matchtext); - else if (opt_time) - { - long seconds = ServerInstance->Duration(matchtext); - - // Okay, so time matching, we want all users connected `seconds' ago - if (user->signon >= ServerInstance->Time() - seconds) - match = true; - } - - /* - * Once the conditionals have been checked, only check dhost/nick/server - * if they didn't match this user -- and only match if we don't find a match. - * - * This should make things minutely faster, and again, less ugly. - * -- w00t - */ - if (!match) - match = InspIRCd::Match(user->dhost, matchtext, ascii_case_insensitive_map); - - if (!match) - match = InspIRCd::Match(user->nick, matchtext); - - /* Don't allow server name matches if HideWhoisServer is enabled, unless the command user has the priv */ - if (!match && (ServerInstance->Config->HideWhoisServer.empty() || cuser->HasPrivPermission("users/auspex"))) - match = InspIRCd::Match(user->server, matchtext); - - return match; - } -} - -bool CommandWho::CanView(Channel* chan, User* user) -{ - if (!user || !chan) - return false; - - /* Bug #383 - moved higher up the list, because if we are in the channel - * we can see all its users - */ - if (chan->HasUser(user)) - return true; - /* Opers see all */ - if (user->HasPrivPermission("users/auspex")) - return true; - /* Cant see inside a +s or a +p channel unless we are a member (see above) */ - else if (!chan->IsModeSet('s') && !chan->IsModeSet('p')) - return true; - - return false; -} - -void CommandWho::SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string &initial, Channel* ch, User* u, std::vector<std::string> &whoresults) -{ - if (!ch) - ch = get_first_visible_channel(user, u); - - std::string wholine = initial + (ch ? ch->name : "*") + " " + u->ident + " " + - (opt_showrealhost ? u->host : u->dhost) + " "; - if (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex")) - wholine.append(ServerInstance->Config->HideWhoisServer); - else - wholine.append(u->server); - - wholine.append(" " + u->nick + " "); - - /* away? */ - if (IS_AWAY(u)) - { - wholine.append("G"); - } - else - { - wholine.append("H"); - } - - /* oper? */ - if (IS_OPER(u)) - { - wholine.push_back('*'); - } - - if (ch) - wholine.append(ch->GetPrefixChar(u)); - - wholine.append(" :0 " + u->fullname); - - FOREACH_MOD(I_OnSendWhoLine, OnSendWhoLine(user, parms, u, wholine)); - - if (!wholine.empty()) - whoresults.push_back(wholine); -} - -CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User *user) -{ - /* - * XXX - RFC says: - * The <name> passed to WHO is matched against users' host, server, real - * name and nickname - * Currently, we support WHO #chan, WHO nick, WHO 0, WHO *, and the addition of a 'o' flag, as per RFC. - */ - - /* WHO options */ - opt_viewopersonly = false; - opt_showrealhost = false; - opt_realname = false; - opt_mode = false; - opt_ident = false; - opt_metadata = false; - opt_port = false; - opt_away = false; - opt_local = false; - opt_far = false; - opt_time = false; - - Channel *ch = NULL; - std::vector<std::string> whoresults; - std::string initial = "352 " + user->nick + " "; - - char matchtext[MAXBUF]; - bool usingwildcards = false; - - /* Change '0' into '*' so the wildcard matcher can grok it */ - if (parameters[0] == "0") - strlcpy(matchtext, "*", MAXBUF); - else - strlcpy(matchtext, parameters[0].c_str(), MAXBUF); - - for (const char* check = matchtext; *check; check++) - { - if (*check == '*' || *check == '?' || *check == '.') - { - usingwildcards = true; - break; - } - } - - if (parameters.size() > 1) - { - /* Fix for bug #444, WHO flags count as a wildcard */ - usingwildcards = true; - - for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter) - { - switch (*iter) - { - case 'o': - opt_viewopersonly = true; - break; - case 'h': - if (user->HasPrivPermission("users/auspex")) - opt_showrealhost = true; - break; - case 'r': - opt_realname = true; - break; - case 'm': - if (user->HasPrivPermission("users/auspex")) - opt_mode = true; - break; - case 'M': - if (user->HasPrivPermission("users/auspex")) - opt_metadata = true; - break; - case 'i': - opt_ident = true; - break; - case 'p': - if (user->HasPrivPermission("users/auspex")) - opt_port = true; - break; - case 'a': - opt_away = true; - break; - case 'l': - if (user->HasPrivPermission("users/auspex") || ServerInstance->Config->HideWhoisServer.empty()) - opt_local = true; - break; - case 'f': - if (user->HasPrivPermission("users/auspex") || ServerInstance->Config->HideWhoisServer.empty()) - opt_far = true; - break; - case 't': - opt_time = true; - break; - } - } - } - - - /* who on a channel? */ - ch = ServerInstance->FindChan(matchtext); - - if (ch) - { - if (CanView(ch,user)) - { - bool inside = ch->HasUser(user); - - /* who on a channel. */ - const UserMembList *cu = ch->GetUsers(); - - for (UserMembCIter i = cu->begin(); i != cu->end(); i++) - { - { - /* opers only, please */ - if (opt_viewopersonly && !IS_OPER(i->first)) - continue; - - /* If we're not inside the channel, hide +i users */ - if (!inside && user != i->first && i->first->IsModeSet('i') && !user->HasPrivPermission("users/auspex")) - continue; - } - - SendWhoLine(user, parameters, initial, ch, i->first, whoresults); - } - } - } - else - { - /* Match against wildcard of nick, server or host */ - if (opt_viewopersonly) - { - /* Showing only opers */ - for (std::list<User*>::iterator i = ServerInstance->Users->all_opers.begin(); i != ServerInstance->Users->all_opers.end(); i++) - { - User* oper = *i; - - if (whomatch(user, oper, matchtext)) - { - if (!user->SharesChannelWith(oper)) - { - if (usingwildcards && (oper->IsModeSet('i')) && (!user->HasPrivPermission("users/auspex"))) - continue; - } - - SendWhoLine(user, parameters, initial, NULL, oper, whoresults); - } - } - } - else - { - for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end(); i++) - { - if (whomatch(user, i->second, matchtext)) - { - if (!user->SharesChannelWith(i->second)) - { - if (usingwildcards && (i->second->IsModeSet('i')) && (!user->HasPrivPermission("users/auspex"))) - continue; - } - - SendWhoLine(user, parameters, initial, NULL, i->second, whoresults); - } - } - } - } - /* Send the results out */ - for (std::vector<std::string>::const_iterator n = whoresults.begin(); n != whoresults.end(); n++) - user->WriteServ(*n); - user->WriteNumeric(315, "%s %s :End of /WHO list.",user->nick.c_str(), *parameters[0].c_str() ? parameters[0].c_str() : "*"); - - // Penalize the user a bit for large queries - // (add one unit of penalty per 200 results) - if (IS_LOCAL(user)) - IS_LOCAL(user)->CommandFloodPenalty += whoresults.size() * 5; - return CMD_SUCCESS; -} - -COMMAND_INIT(CommandWho) diff --git a/src/commands/cmd_whois.cpp b/src/commands/cmd_whois.cpp deleted file mode 100644 index ab0b82fff..000000000 --- a/src/commands/cmd_whois.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/** Handle /WHOIS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandWhois : public Command -{ - public: - /** Constructor for whois. - */ - CommandWhois ( Module* parent) : Command(parent,"WHOIS",1) { Penalty = 2; syntax = "<nick>{,<nick>}"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - - -CmdResult CommandWhois::Handle (const std::vector<std::string>& parameters, User *user) -{ - User *dest; - int userindex = 0; - unsigned long idle = 0, signon = 0; - - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) - return CMD_SUCCESS; - - - /* - * If 2 paramters are specified (/whois nick nick), ignore the first one like spanningtree - * does, and use the second one, otherwise, use the only paramter. -- djGrrr - */ - if (parameters.size() > 1) - userindex = 1; - - if (IS_LOCAL(user)) - dest = ServerInstance->FindNickOnly(parameters[userindex]); - else - dest = ServerInstance->FindNick(parameters[userindex]); - - if ((dest) && (dest->registered == REG_ALL)) - { - /* - * Okay. Umpteenth attempt at doing this, so let's re-comment... - * For local users (/w localuser), we show idletime if hidewhois is disabled - * For local users (/w localuser localuser), we always show idletime, hence parameters.size() > 1 check. - * For remote users (/w remoteuser), we do NOT show idletime - * For remote users (/w remoteuser remoteuser), spanningtree will handle calling do_whois, so we can ignore this case. - * Thanks to djGrrr for not being impatient while I have a crap day coding. :p -- w00t - */ - if (IS_LOCAL(dest) && (ServerInstance->Config->HideWhoisServer.empty() || parameters.size() > 1)) - { - idle = labs((long)((dest->idle_lastmsg)-ServerInstance->Time())); - signon = dest->signon; - } - - ServerInstance->DoWhois(user,dest,signon,idle,parameters[userindex].c_str()); - } - else - { - /* no such nick/channel */ - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), !parameters[userindex].empty() ? parameters[userindex].c_str() : "*"); - user->WriteNumeric(318, "%s %s :End of /WHOIS list.",user->nick.c_str(), !parameters[userindex].empty() ? parameters[userindex].c_str() : "*"); - return CMD_FAILURE; - } - - return CMD_SUCCESS; -} - - - -COMMAND_INIT(CommandWhois) diff --git a/src/commands/cmd_whowas.cpp b/src/commands/cmd_whowas.cpp deleted file mode 100644 index 3a6444b45..000000000 --- a/src/commands/cmd_whowas.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "commands/cmd_whowas.h" - -WhoWasMaintainTimer * timer; - -CommandWhowas::CommandWhowas( Module* parent) : Command(parent, "WHOWAS", 1) -{ - syntax = "<nick>{,<nick>}"; - Penalty = 2; - timer = new WhoWasMaintainTimer(3600); - ServerInstance->Timers->AddTimer(timer); -} - -CmdResult CommandWhowas::Handle (const std::vector<std::string>& parameters, User* user) -{ - /* if whowas disabled in config */ - if (ServerInstance->Config->WhoWasGroupSize == 0 || ServerInstance->Config->WhoWasMaxGroups == 0) - { - user->WriteNumeric(421, "%s %s :This command has been disabled.",user->nick.c_str(),name.c_str()); - return CMD_FAILURE; - } - - whowas_users::iterator i = whowas.find(assign(parameters[0])); - - if (i == whowas.end()) - { - user->WriteNumeric(406, "%s %s :There was no such nickname",user->nick.c_str(),parameters[0].c_str()); - user->WriteNumeric(369, "%s %s :End of WHOWAS",user->nick.c_str(),parameters[0].c_str()); - return CMD_FAILURE; - } - else - { - whowas_set* grp = i->second; - if (grp->size()) - { - for (whowas_set::iterator ux = grp->begin(); ux != grp->end(); ux++) - { - WhoWasGroup* u = *ux; - - user->WriteNumeric(314, "%s %s %s %s * :%s",user->nick.c_str(),parameters[0].c_str(), - u->ident.c_str(),u->dhost.c_str(),u->gecos.c_str()); - - if (user->HasPrivPermission("users/auspex")) - user->WriteNumeric(379, "%s %s :was connecting from *@%s", - user->nick.c_str(), parameters[0].c_str(), u->host.c_str()); - - std::string signon = ServerInstance->TimeString(u->signon); - if (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex")) - user->WriteNumeric(312, "%s %s %s :%s",user->nick.c_str(),parameters[0].c_str(), ServerInstance->Config->HideWhoisServer.c_str(), signon.c_str()); - else - user->WriteNumeric(312, "%s %s %s :%s",user->nick.c_str(),parameters[0].c_str(), u->server.c_str(), signon.c_str()); - } - } - else - { - user->WriteNumeric(406, "%s %s :There was no such nickname",user->nick.c_str(),parameters[0].c_str()); - user->WriteNumeric(369, "%s %s :End of WHOWAS",user->nick.c_str(),parameters[0].c_str()); - return CMD_FAILURE; - } - } - - user->WriteNumeric(369, "%s %s :End of WHOWAS",user->nick.c_str(),parameters[0].c_str()); - return CMD_SUCCESS; -} - -std::string CommandWhowas::GetStats() -{ - int whowas_size = 0; - int whowas_bytes = 0; - whowas_users_fifo::iterator iter; - for (iter = whowas_fifo.begin(); iter != whowas_fifo.end(); iter++) - { - whowas_set* n = (whowas_set*)whowas.find(iter->second)->second; - if (n->size()) - { - whowas_size += n->size(); - whowas_bytes += (sizeof(whowas_set) + ( sizeof(WhoWasGroup) * n->size() ) ); - } - } - return "Whowas entries: " +ConvToStr(whowas_size)+" ("+ConvToStr(whowas_bytes)+" bytes)"; -} - -void CommandWhowas::AddToWhoWas(User* user) -{ - /* if whowas disabled */ - if (ServerInstance->Config->WhoWasGroupSize == 0 || ServerInstance->Config->WhoWasMaxGroups == 0) - { - return; - } - - whowas_users::iterator iter = whowas.find(irc::string(user->nick.c_str())); - - if (iter == whowas.end()) - { - whowas_set* n = new whowas_set; - WhoWasGroup *a = new WhoWasGroup(user); - n->push_back(a); - whowas[user->nick.c_str()] = n; - whowas_fifo.push_back(std::make_pair(ServerInstance->Time(),user->nick.c_str())); - - if ((int)(whowas.size()) > ServerInstance->Config->WhoWasMaxGroups) - { - whowas_users::iterator iter2 = whowas.find(whowas_fifo[0].second); - if (iter2 != whowas.end()) - { - whowas_set* n2 = (whowas_set*)iter2->second; - - if (n2->size()) - { - while (n2->begin() != n2->end()) - { - WhoWasGroup *a2 = *(n2->begin()); - delete a2; - n2->pop_front(); - } - } - - delete n2; - whowas.erase(iter2); - } - whowas_fifo.pop_front(); - } - } - else - { - whowas_set* group = (whowas_set*)iter->second; - WhoWasGroup *a = new WhoWasGroup(user); - group->push_back(a); - - if ((int)(group->size()) > ServerInstance->Config->WhoWasGroupSize) - { - WhoWasGroup *a2 = (WhoWasGroup*)*(group->begin()); - delete a2; - group->pop_front(); - } - } -} - -/* on rehash, refactor maps according to new conf values */ -void CommandWhowas::PruneWhoWas(time_t t) -{ - /* config values */ - int groupsize = ServerInstance->Config->WhoWasGroupSize; - int maxgroups = ServerInstance->Config->WhoWasMaxGroups; - int maxkeep = ServerInstance->Config->WhoWasMaxKeep; - - /* first cut the list to new size (maxgroups) and also prune entries that are timed out. */ - whowas_users::iterator iter; - int fifosize; - while ((fifosize = (int)whowas_fifo.size()) > 0) - { - if (fifosize > maxgroups || whowas_fifo[0].first < t - maxkeep) - { - iter = whowas.find(whowas_fifo[0].second); - - /* hopefully redundant integrity check, but added while debugging r6216 */ - if (iter == whowas.end()) - { - /* this should never happen, if it does maps are corrupt */ - ServerInstance->Logs->Log("WHOWAS",DEFAULT, "BUG: Whowas maps got corrupted! (1)"); - return; - } - - whowas_set* n = (whowas_set*)iter->second; - - if (n->size()) - { - while (n->begin() != n->end()) - { - WhoWasGroup *a = *(n->begin()); - delete a; - n->pop_front(); - } - } - - delete n; - whowas.erase(iter); - whowas_fifo.pop_front(); - } - else - break; - } - - /* Then cut the whowas sets to new size (groupsize) */ - fifosize = (int)whowas_fifo.size(); - for (int i = 0; i < fifosize; i++) - { - iter = whowas.find(whowas_fifo[0].second); - /* hopefully redundant integrity check, but added while debugging r6216 */ - if (iter == whowas.end()) - { - /* this should never happen, if it does maps are corrupt */ - ServerInstance->Logs->Log("WHOWAS",DEFAULT, "BUG: Whowas maps got corrupted! (2)"); - return; - } - whowas_set* n = (whowas_set*)iter->second; - if (n->size()) - { - int nickcount = n->size(); - while (n->begin() != n->end() && nickcount > groupsize) - { - WhoWasGroup *a = *(n->begin()); - delete a; - n->pop_front(); - nickcount--; - } - } - } -} - -/* call maintain once an hour to remove expired nicks */ -void CommandWhowas::MaintainWhoWas(time_t t) -{ - for (whowas_users::iterator iter = whowas.begin(); iter != whowas.end(); iter++) - { - whowas_set* n = (whowas_set*)iter->second; - if (n->size()) - { - while ((n->begin() != n->end()) && ((*n->begin())->signon < t - ServerInstance->Config->WhoWasMaxKeep)) - { - WhoWasGroup *a = *(n->begin()); - delete a; - n->erase(n->begin()); - } - } - } -} - -CommandWhowas::~CommandWhowas() -{ - if (timer) - { - ServerInstance->Timers->DelTimer(timer); - } - - whowas_users::iterator iter; - int fifosize; - while ((fifosize = (int)whowas_fifo.size()) > 0) - { - iter = whowas.find(whowas_fifo[0].second); - - /* hopefully redundant integrity check, but added while debugging r6216 */ - if (iter == whowas.end()) - { - /* this should never happen, if it does maps are corrupt */ - ServerInstance->Logs->Log("WHOWAS",DEFAULT, "BUG: Whowas maps got corrupted! (3)"); - return; - } - - whowas_set* n = (whowas_set*)iter->second; - - if (n->size()) - { - while (n->begin() != n->end()) - { - WhoWasGroup *a = *(n->begin()); - delete a; - n->pop_front(); - } - } - - delete n; - whowas.erase(iter); - whowas_fifo.pop_front(); - } -} - -WhoWasGroup::WhoWasGroup(User* user) : host(user->host), dhost(user->dhost), ident(user->ident), - server(user->server), gecos(user->fullname), signon(user->signon) -{ -} - -WhoWasGroup::~WhoWasGroup() -{ -} - -/* every hour, run this function which removes all entries older than Config->WhoWasMaxKeep */ -void WhoWasMaintainTimer::Tick(time_t) -{ - Module* whowas = ServerInstance->Modules->Find("cmd_whowas.so"); - if (whowas) - { - WhowasRequest(whowas, whowas, WhowasRequest::WHOWAS_MAINTAIN).Send(); - } -} - -class ModuleWhoWas : public Module -{ - CommandWhowas cmd; - public: - ModuleWhoWas() : cmd(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - void OnRequest(Request& request) - { - WhowasRequest& req = static_cast<WhowasRequest&>(request); - switch (req.type) - { - case WhowasRequest::WHOWAS_ADD: - cmd.AddToWhoWas(req.user); - break; - case WhowasRequest::WHOWAS_STATS: - req.value = cmd.GetStats(); - break; - case WhowasRequest::WHOWAS_PRUNE: - cmd.PruneWhoWas(ServerInstance->Time()); - break; - case WhowasRequest::WHOWAS_MAINTAIN: - cmd.MaintainWhoWas(ServerInstance->Time()); - break; - } - } - - Version GetVersion() - { - return Version("WHOWAS Command", VF_VENDOR); - } -}; - -MODULE_INIT(ModuleWhoWas) diff --git a/src/configparser.cpp b/src/configparser.cpp index 409ebdd39..825053044 100644 --- a/src/configparser.cpp +++ b/src/configparser.cpp @@ -91,7 +91,7 @@ struct Parser unget(ch); } - bool kv(std::vector<KeyVal>* items, std::set<std::string>& seen) + bool kv(ConfigItems* items) { std::string key; nextword(key); @@ -119,13 +119,13 @@ struct Parser while (1) { ch = next(); - if (ch == '&' && (flags & FLAG_USE_XML)) + if (ch == '&' && !(flags & FLAG_USE_COMPAT)) { std::string varname; while (1) { ch = next(); - if (isalnum(ch)) + if (isalnum(ch) || (varname.empty() && ch == '#')) varname.push_back(ch); else if (ch == ';') break; @@ -136,12 +136,32 @@ struct Parser throw CoreException("Parse error"); } } - std::map<std::string, std::string>::iterator var = stack.vars.find(varname); - if (var == stack.vars.end()) - throw CoreException("Undefined XML entity reference '&" + varname + ";'"); - value.append(var->second); + if (varname.empty()) + throw CoreException("Empty XML entity reference"); + else if (varname[0] == '#' && (varname.size() == 1 || (varname.size() == 2 && varname[1] == 'x'))) + throw CoreException("Empty numeric character reference"); + else if (varname[0] == '#') + { + const char* cvarname = varname.c_str(); + char* endptr; + unsigned long lvalue; + if (cvarname[1] == 'x') + lvalue = strtoul(cvarname + 2, &endptr, 16); + else + lvalue = strtoul(cvarname + 1, &endptr, 10); + if (*endptr != '\0' || lvalue > 255) + throw CoreException("Invalid numeric character reference '&" + varname + ";'"); + value.push_back(static_cast<char>(lvalue)); + } + else + { + insp::flat_map<std::string, std::string>::iterator var = stack.vars.find(varname); + if (var == stack.vars.end()) + throw CoreException("Undefined XML entity reference '&" + varname + ";'"); + value.append(var->second); + } } - else if (ch == '\\' && !(flags & FLAG_USE_XML)) + else if (ch == '\\' && (flags & FLAG_USE_COMPAT)) { int esc = next(); if (esc == 'n') @@ -153,14 +173,14 @@ struct Parser } else if (ch == '"') break; - else + else if (ch != '\r') value.push_back(ch); } - if (!seen.insert(key).second) + if (items->find(key) != items->end()) throw CoreException("Duplicate key '" + key + "' found"); - items->push_back(KeyVal(key, value)); + (*items)[key] = value; return true; } @@ -179,11 +199,13 @@ struct Parser if (name.empty()) throw CoreException("Empty tag name"); - std::vector<KeyVal>* items; - std::set<std::string> seen; + ConfigItems* items; tag = ConfigTag::create(name, current.filename, current.line, items); - while (kv(items, seen)); + while (kv(items)) + { + // Do nothing here (silences a GCC warning). + } if (name == mandatory_tag) { @@ -191,27 +213,27 @@ struct Parser mandatory_tag.clear(); } - if (name == "include") + if (stdalgo::string::equalsci(name, "include")) { stack.DoInclude(tag, flags); } - else if (name == "files") + else if (stdalgo::string::equalsci(name, "files")) { - for(std::vector<KeyVal>::iterator i = items->begin(); i != items->end(); i++) + for(ConfigItems::iterator i = items->begin(); i != items->end(); i++) { stack.DoReadFile(i->first, i->second, flags, false); } } - else if (name == "execfiles") + else if (stdalgo::string::equalsci(name, "execfiles")) { - for(std::vector<KeyVal>::iterator i = items->begin(); i != items->end(); i++) + for(ConfigItems::iterator i = items->begin(); i != items->end(); i++) { stack.DoReadFile(i->first, i->second, flags, true); } } - else if (name == "define") + else if (stdalgo::string::equalsci(name, "define")) { - if (!(flags & FLAG_USE_XML)) + if (flags & FLAG_USE_COMPAT) throw CoreException("<define> tags may only be used in XML-style config (add <config format=\"xml\">)"); std::string varname = tag->getString("name"); std::string value = tag->getString("value"); @@ -219,13 +241,13 @@ struct Parser throw CoreException("Variable definition must include variable name"); stack.vars[varname] = value; } - else if (name == "config") + else if (stdalgo::string::equalsci(name, "config")) { std::string format = tag->getString("format"); - if (format == "xml") - flags |= FLAG_USE_XML; - else if (format == "compat") - flags &= ~FLAG_USE_XML; + if (stdalgo::string::equalsci(format, "xml")) + flags &= ~FLAG_USE_COMPAT; + else if (stdalgo::string::equalsci(format, "compat")) + flags |= FLAG_USE_COMPAT; else if (!format.empty()) throw CoreException("Unknown configuration format " + format); } @@ -297,7 +319,7 @@ void ParseStack::DoInclude(ConfigTag* tag, int flags) flags |= FLAG_NO_INC; if (tag->getBool("noexec", false)) flags |= FLAG_NO_EXEC; - if (!ParseFile(name, flags, mandatorytag)) + if (!ParseFile(ServerInstance->Config->Paths.PrependConfig(name), flags, mandatorytag)) throw CoreException("Included"); } else if (tag->readString("executable", name)) @@ -308,7 +330,7 @@ void ParseStack::DoInclude(ConfigTag* tag, int flags) flags |= FLAG_NO_INC; if (tag->getBool("noexec", true)) flags |= FLAG_NO_EXEC; - if (!ParseExec(name, flags, mandatorytag)) + if (!ParseFile(name, flags, mandatorytag, true)) throw CoreException("Included"); } } @@ -320,14 +342,15 @@ void ParseStack::DoReadFile(const std::string& key, const std::string& name, int if (exec && (flags & FLAG_NO_EXEC)) throw CoreException("Invalid <execfiles> tag in file included with noexec=\"yes\""); - FileWrapper file(exec ? popen(name.c_str(), "r") : fopen(name.c_str(), "r"), exec); + std::string path = ServerInstance->Config->Paths.PrependConfig(name); + FileWrapper file(exec ? popen(name.c_str(), "r") : fopen(path.c_str(), "r"), exec); if (!file) - throw CoreException("Could not read \"" + name + "\" for \"" + key + "\" file"); + throw CoreException("Could not read \"" + path + "\" for \"" + key + "\" file"); file_cache& cache = FilesOutput[key]; cache.clear(); - char linebuf[MAXBUF*10]; + char linebuf[5120]; while (fgets(linebuf, sizeof(linebuf), file)) { size_t len = strlen(linebuf); @@ -340,76 +363,35 @@ void ParseStack::DoReadFile(const std::string& key, const std::string& name, int } } -bool ParseStack::ParseFile(const std::string& name, int flags, const std::string& mandatory_tag) -{ - ServerInstance->Logs->Log("CONFIG", DEBUG, "Reading file %s", name.c_str()); - for (unsigned int t = 0; t < reading.size(); t++) - { - if (std::string(name) == reading[t]) - { - throw CoreException("File " + name + " is included recursively (looped inclusion)"); - } - } - - /* It's not already included, add it to the list of files we've loaded */ - - FileWrapper file(fopen(name.c_str(), "r")); - if (!file) - throw CoreException("Could not read \"" + name + "\" for include"); - - reading.push_back(name); - Parser p(*this, flags, file, name, mandatory_tag); - bool ok = p.outer_parse(); - reading.pop_back(); - return ok; -} - -bool ParseStack::ParseExec(const std::string& name, int flags, const std::string& mandatory_tag) +bool ParseStack::ParseFile(const std::string& path, int flags, const std::string& mandatory_tag, bool isexec) { - ServerInstance->Logs->Log("CONFIG", DEBUG, "Reading executable %s", name.c_str()); - for (unsigned int t = 0; t < reading.size(); t++) - { - if (std::string(name) == reading[t]) - { - throw CoreException("Executable " + name + " is included recursively (looped inclusion)"); - } - } + ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "Reading (isexec=%d) %s", isexec, path.c_str()); + if (stdalgo::isin(reading, path)) + throw CoreException((isexec ? "Executable " : "File ") + path + " is included recursively (looped inclusion)"); /* It's not already included, add it to the list of files we've loaded */ - FileWrapper file(popen(name.c_str(), "r"), true); + FileWrapper file((isexec ? popen(path.c_str(), "r") : fopen(path.c_str(), "r")), isexec); if (!file) - throw CoreException("Could not open executable \"" + name + "\" for include"); + throw CoreException("Could not read \"" + path + "\" for include"); - reading.push_back(name); - Parser p(*this, flags, file, name, mandatory_tag); + reading.push_back(path); + Parser p(*this, flags, file, path, mandatory_tag); bool ok = p.outer_parse(); reading.pop_back(); return ok; } -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wunknown-pragmas" -# pragma clang diagnostic ignored "-Wundefined-bool-conversion" -#elif defined __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wpragmas" -# pragma GCC diagnostic ignored "-Wnonnull-compare" -#endif bool ConfigTag::readString(const std::string& key, std::string& value, bool allow_lf) { - // TODO: this is undefined behaviour but changing the API is too risky for 2.0. - if (!this) - return false; - for(std::vector<KeyVal>::iterator j = items.begin(); j != items.end(); ++j) + for(ConfigItems::iterator j = items.begin(); j != items.end(); ++j) { if(j->first != key) continue; value = j->second; if (!allow_lf && (value.find('\n') != std::string::npos)) { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() + + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() + " contains a linefeed, and linefeeds in this value are not permitted -- stripped to spaces."); for (std::string::iterator n = value.begin(); n != value.end(); n++) if (*n == '\n') @@ -419,20 +401,100 @@ bool ConfigTag::readString(const std::string& key, std::string& value, bool allo } return false; } -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif -std::string ConfigTag::getString(const std::string& key, const std::string& def) +std::string ConfigTag::getString(const std::string& key, const std::string& def, const TR1NS::function<bool(const std::string&)>& validator) +{ + std::string res; + if (!readString(key, res)) + return def; + + if (!validator(res)) + { + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: The value of <%s:%s> is not valid; value set to %s.", + tag.c_str(), key.c_str(), def.c_str()); + return def; + } + return res; +} + +std::string ConfigTag::getString(const std::string& key, const std::string& def, size_t minlen, size_t maxlen) { - std::string res = def; - readString(key, res); + std::string res; + if (!readString(key, res)) + return def; + + if (res.length() < minlen || res.length() > maxlen) + { + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: The length of <%s:%s> is not between %ld and %ld; value set to %s.", + tag.c_str(), key.c_str(), minlen, maxlen, def.c_str()); + return def; + } return res; } -long ConfigTag::getInt(const std::string &key, long def) +namespace +{ + /** Check for an invalid magnitude specifier. If one is found a warning is logged and the + * value is corrected (set to \p def). + * @param tag The tag name; used in the warning message. + * @param key The key name; used in the warning message. + * @param val The full value set in the config as a string. + * @param num The value to verify and modify if needed. + * @param def The default value, \p res will be set to this if \p tail does not contain a. + * valid magnitude specifier. + * @param tail The location in the config value at which the magnifier is located. + */ + template <typename Numeric> + void CheckMagnitude(const std::string& tag, const std::string& key, const std::string& val, Numeric& num, Numeric def, const char* tail) + { + // If this is NULL then no magnitude specifier was given. + if (!*tail) + return; + + switch (toupper(*tail)) + { + case 'K': + num *= 1024; + return; + + case 'M': + num *= 1024 * 1024; + return; + + case 'G': + num *= 1024 * 1024 * 1024; + return; + } + + const std::string message = "WARNING: <" + tag + ":" + key + "> value of " + val + " contains an invalid magnitude specifier '" + + tail + "'; value set to " + ConvToStr(def) + "."; + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, message); + num = def; + } + + /** Check for an out of range value. If the value falls outside the boundaries a warning is + * logged and the value is corrected (set to \p def). + * @param tag The tag name; used in the warning message. + * @param key The key name; used in the warning message. + * @param num The value to verify and modify if needed. + * @param def The default value, \p res will be set to this if (min <= res <= max) doesn't hold true. + * @param min Minimum accepted value for \p res. + * @param max Maximum accepted value for \p res. + */ + template <typename Numeric> + void CheckRange(const std::string& tag, const std::string& key, Numeric& num, Numeric def, Numeric min, Numeric max) + { + if (num >= min && num <= max) + return; + + const std::string message = "WARNING: <" + tag + ":" + key + "> value of " + ConvToStr(num) + " is not between " + + ConvToStr(min) + " and " + ConvToStr(max) + "; value set to " + ConvToStr(def) + "."; + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, message); + num = def; + } +} + +long ConfigTag::getInt(const std::string &key, long def, long min, long max) { std::string result; if(!readString(key, result)) @@ -443,27 +505,49 @@ long ConfigTag::getInt(const std::string &key, long def) long res = strtol(res_cstr, &res_tail, 0); if (res_tail == res_cstr) return def; - switch (toupper(*res_tail)) - { - case 'K': - res= res* 1024; - break; - case 'M': - res= res* 1024 * 1024; - break; - case 'G': - res= res* 1024 * 1024 * 1024; - break; - } + + CheckMagnitude(tag, key, result, res, def, res_tail); + CheckRange(tag, key, res, def, min, max); return res; } -double ConfigTag::getFloat(const std::string &key, double def) +unsigned long ConfigTag::getUInt(const std::string& key, unsigned long def, unsigned long min, unsigned long max) { std::string result; if (!readString(key, result)) return def; - return strtod(result.c_str(), NULL); + + const char* res_cstr = result.c_str(); + char* res_tail = NULL; + unsigned long res = strtoul(res_cstr, &res_tail, 0); + if (res_tail == res_cstr) + return def; + + CheckMagnitude(tag, key, result, res, def, res_tail); + CheckRange(tag, key, res, def, min, max); + return res; +} + +unsigned long ConfigTag::getDuration(const std::string& key, unsigned long def, unsigned long min, unsigned long max) +{ + std::string duration; + if (!readString(key, duration)) + return def; + + unsigned long ret = InspIRCd::Duration(duration); + CheckRange(tag, key, ret, def, min, max); + return ret; +} + +double ConfigTag::getFloat(const std::string& key, double def, double min, double max) +{ + std::string result; + if (!readString(key, result)) + return def; + + double res = strtod(result.c_str(), NULL); + CheckRange(tag, key, res, def, min, max); + return res; } bool ConfigTag::getBool(const std::string &key, bool def) @@ -477,7 +561,7 @@ bool ConfigTag::getBool(const std::string &key, bool def) if (result == "no" || result == "false" || result == "0" || result == "off") return false; - ServerInstance->Logs->Log("CONFIG",DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() + + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() + " is not valid, ignoring"); return def; } @@ -487,7 +571,7 @@ std::string ConfigTag::getTagLocation() return src_name + ":" + ConvToStr(src_line); } -ConfigTag* ConfigTag::create(const std::string& Tag, const std::string& file, int line, std::vector<KeyVal>*& Items) +ConfigTag* ConfigTag::create(const std::string& Tag, const std::string& file, int line, ConfigItems*& Items) { ConfigTag* rv = new ConfigTag(Tag, file, line); Items = &rv->items; @@ -499,6 +583,11 @@ ConfigTag::ConfigTag(const std::string& Tag, const std::string& file, int line) { } +OperInfo::OperInfo(const std::string& Name) + : name(Name) +{ +} + std::string OperInfo::getConfig(const std::string& key) { std::string rv; diff --git a/src/configreader.cpp b/src/configreader.cpp index 301db14e8..a8e94586d 100644 --- a/src/configreader.cpp +++ b/src/configreader.cpp @@ -23,193 +23,78 @@ #include "inspircd.h" -#include <fstream> #include "xline.h" +#include "listmode.h" #include "exitcodes.h" -#include "commands/cmd_whowas.h" #include "configparser.h" #include <iostream> -#ifdef _WIN32 -#include <Iphlpapi.h> -#pragma comment(lib, "Iphlpapi.lib") -#endif -ServerConfig::ServerConfig() - : NoSnoticeStack(false) -{ - WhoWasGroupSize = WhoWasMaxGroups = WhoWasMaxKeep = 0; - RawLog = NoUserDns = HideBans = HideSplits = UndernetMsgPrefix = false; - WildcardIPv6 = CycleHosts = InvBypassModes = true; - dns_timeout = 5; - MaxTargets = 20; - NetBufferSize = 10240; - SoftLimit = ServerInstance->SE->GetMaxFds(); - MaxConn = SOMAXCONN; - MaxChans = 20; - OperMaxChans = 30; - c_ipv4_range = 32; - c_ipv6_range = 128; - - std::vector<KeyVal>* items; - EmptyTag = ConfigTag::create("empty", "<auto>", 0, items); -} - -ServerConfig::~ServerConfig() +ServerLimits::ServerLimits(ConfigTag* tag) + : NickMax(tag->getUInt("maxnick", 30)) + , ChanMax(tag->getUInt("maxchan", 64)) + , MaxModes(tag->getUInt("maxmodes", 20)) + , IdentMax(tag->getUInt("maxident", 10)) + , MaxQuit(tag->getUInt("maxquit", 255)) + , MaxTopic(tag->getUInt("maxtopic", 307)) + , MaxKick(tag->getUInt("maxkick", 255)) + , MaxReal(tag->getUInt("maxreal", tag->getUInt("maxgecos", 128))) + , MaxAway(tag->getUInt("maxaway", 200)) + , MaxLine(tag->getUInt("maxline", 512)) + , MaxHost(tag->getUInt("maxhost", 64)) { - delete EmptyTag; -} - -void ServerConfig::Update005() -{ - std::stringstream out(data005); - std::vector<std::string> data; - std::string token; - while (out >> token) - data.push_back(token); - sort(data.begin(), data.end()); - - std::string line5; - isupport.clear(); - for(unsigned int i=0; i < data.size(); i++) - { - token = data[i]; - line5 = line5 + token + " "; - if (i % 13 == 12) - { - line5.append(":are supported by this server"); - isupport.push_back(line5); - line5.clear(); - } - } - if (!line5.empty()) - { - line5.append(":are supported by this server"); - isupport.push_back(line5); - } } -void ServerConfig::Send005(User* user) +ServerConfig::ServerPaths::ServerPaths(ConfigTag* tag) + : Config(tag->getString("configdir", INSPIRCD_CONFIG_PATH)) + , Data(tag->getString("datadir", INSPIRCD_DATA_PATH)) + , Log(tag->getString("logdir", INSPIRCD_LOG_PATH)) + , Module(tag->getString("moduledir", INSPIRCD_MODULE_PATH)) { - for (std::vector<std::string>::iterator line = ServerInstance->Config->isupport.begin(); line != ServerInstance->Config->isupport.end(); line++) - user->WriteNumeric(RPL_ISUPPORT, "%s %s", user->nick.c_str(), line->c_str()); } -template<typename T, typename V> -static void range(T& value, V min, V max, V def, const char* msg) +static ConfigTag* CreateEmptyTag() { - if (value >= (T)min && value <= (T)max) - return; - ServerInstance->Logs->Log("CONFIG", DEFAULT, - "WARNING: %s value of %ld is not between %ld and %ld; set to %ld.", - msg, (long)value, (long)min, (long)max, (long)def); - value = def; + ConfigItems* items; + return ConfigTag::create("empty", "<auto>", 0, items); } - -static void ValidIP(const std::string& ip, const std::string& key) +ServerConfig::ServerConfig() + : EmptyTag(CreateEmptyTag()) + , Limits(EmptyTag) + , Paths(EmptyTag) + , RawLog(false) + , NoSnoticeStack(false) { - irc::sockets::sockaddrs dummy; - if (!irc::sockets::aptosa(ip, 0, dummy)) - throw CoreException("The value of "+key+" is not an IP address"); } -static void ValidHost(const std::string& p, const std::string& msg) +ServerConfig::~ServerConfig() { - int num_dots = 0; - if (p.empty() || p[0] == '.') - throw CoreException("The value of "+msg+" is not a valid hostname"); - for (unsigned int i=0;i < p.length();i++) - { - switch (p[i]) - { - case ' ': - throw CoreException("The value of "+msg+" is not a valid hostname"); - case '.': - num_dots++; - break; - } - } - if (num_dots == 0) - throw CoreException("The value of "+msg+" is not a valid hostname"); + delete EmptyTag; } -bool ServerConfig::ApplyDisabledCommands(const std::string& data) +bool ServerConfig::ApplyDisabledCommands() { - std::stringstream dcmds(data); - std::string thiscmd; - - /* Enable everything first */ - for (Commandtable::iterator x = ServerInstance->Parser->cmdlist.begin(); x != ServerInstance->Parser->cmdlist.end(); x++) + // Enable everything first. + const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands(); + for (CommandParser::CommandMap::const_iterator x = commands.begin(); x != commands.end(); ++x) x->second->Disable(false); - /* Now disable all the ones which the user wants disabled */ - while (dcmds >> thiscmd) - { - Commandtable::iterator cm = ServerInstance->Parser->cmdlist.find(thiscmd); - if (cm != ServerInstance->Parser->cmdlist.end()) - { - cm->second->Disable(true); - } - } - return true; -} - -static void FindDNS(std::string& server) -{ - if (!server.empty()) - return; -#ifdef _WIN32 - // attempt to look up their nameserver from the system - ServerInstance->Logs->Log("CONFIG",DEFAULT,"WARNING: <dns:server> not defined, attempting to find a working server in the system settings..."); - - PFIXED_INFO pFixedInfo; - DWORD dwBufferSize = sizeof(FIXED_INFO); - pFixedInfo = (PFIXED_INFO) HeapAlloc(GetProcessHeap(), 0, sizeof(FIXED_INFO)); - - if(pFixedInfo) + // Now disable the commands specified in the config. + std::string command; + irc::spacesepstream commandlist(ConfValue("disabled")->getString("commands")); + while (commandlist.GetToken(command)) { - if (GetNetworkParams(pFixedInfo, &dwBufferSize) == ERROR_BUFFER_OVERFLOW) { - HeapFree(GetProcessHeap(), 0, pFixedInfo); - pFixedInfo = (PFIXED_INFO) HeapAlloc(GetProcessHeap(), 0, dwBufferSize); - } - - if(pFixedInfo) { - if (GetNetworkParams(pFixedInfo, &dwBufferSize) == NO_ERROR) - server = pFixedInfo->DnsServerList.IpAddress.String; - - HeapFree(GetProcessHeap(), 0, pFixedInfo); - } - - if(!server.empty()) + Command* handler = ServerInstance->Parser.GetHandler(command); + if (!handler) { - ServerInstance->Logs->Log("CONFIG",DEFAULT,"<dns:server> set to '%s' as first active resolver in the system settings.", server.c_str()); - return; + ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "Unable to disable the %s command as it does not exist!", command.c_str()); + continue; } - } - - ServerInstance->Logs->Log("CONFIG",DEFAULT,"No viable nameserver found! Defaulting to nameserver '127.0.0.1'!"); -#else - // attempt to look up their nameserver from /etc/resolv.conf - ServerInstance->Logs->Log("CONFIG",DEFAULT,"WARNING: <dns:server> not defined, attempting to find working server in /etc/resolv.conf..."); - - std::ifstream resolv("/etc/resolv.conf"); - while (resolv >> server) - { - if (server == "nameserver") - { - resolv >> server; - if (server.find_first_not_of("0123456789.") == std::string::npos) - { - ServerInstance->Logs->Log("CONFIG",DEFAULT,"<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",server.c_str()); - return; - } - } + ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "The %s command has been disabled", command.c_str()); + handler->Disable(true); } - - ServerInstance->Logs->Log("CONFIG",DEFAULT,"/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!"); -#endif - server = "127.0.0.1"; + return true; } static void ReadXLine(ServerConfig* conf, const std::string& tag, const std::string& key, XLineFactory* make) @@ -223,6 +108,7 @@ static void ReadXLine(ServerConfig* conf, const std::string& tag, const std::str throw CoreException("<"+tag+":"+key+"> missing at " + ctag->getTagLocation()); std::string reason = ctag->getString("reason", "<Config>"); XLine* xl = make->Generate(ServerInstance->Time(), 0, "<Config>", reason, mask); + xl->from_config = true; if (!ServerInstance->XLines->AddLine(xl, NULL)) delete xl; } @@ -250,14 +136,11 @@ void ServerConfig::CrossCheckOperClassType() std::string name = tag->getString("name"); if (name.empty()) throw CoreException("<type:name> is missing from tag at " + tag->getTagLocation()); - if (!ServerInstance->IsNick(name.c_str(), Limits.NickMax)) - throw CoreException("<type:name> is invalid (value '" + name + "')"); - if (oper_blocks.find(" " + name) != oper_blocks.end()) + if (OperTypes.find(name) != OperTypes.end()) throw CoreException("Duplicate type block with name " + name + " at " + tag->getTagLocation()); - OperInfo* ifo = new OperInfo; - oper_blocks[" " + name] = ifo; - ifo->name = name; + OperInfo* ifo = new OperInfo(name); + OperTypes[name] = ifo; ifo->type_block = tag; std::string classname; @@ -281,14 +164,13 @@ void ServerConfig::CrossCheckOperClassType() throw CoreException("<oper:name> missing from tag at " + tag->getTagLocation()); std::string type = tag->getString("type"); - OperIndex::iterator tblk = oper_blocks.find(" " + type); - if (tblk == oper_blocks.end()) + OperIndex::iterator tblk = OperTypes.find(type); + if (tblk == OperTypes.end()) throw CoreException("Oper block " + name + " has missing type " + type); if (oper_blocks.find(name) != oper_blocks.end()) throw CoreException("Duplicate oper block with name " + name + " at " + tag->getTagLocation()); - OperInfo* ifo = new OperInfo; - ifo->name = type; + OperInfo* ifo = new OperInfo(type); ifo->oper_block = tag; ifo->type_block = tblk->second->type_block; ifo->class_blocks.assign(tblk->second->class_blocks.begin(), tblk->second->class_blocks.end()); @@ -305,7 +187,7 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) for(ClassVector::iterator i = current->Classes.begin(); i != current->Classes.end(); ++i) { ConnectClass* c = *i; - if (c->name.substr(0, 8) != "unnamed-") + if (c->name.compare(0, 8, "unnamed-", 8)) { oldBlocksByMask["n" + c->name] = c; } @@ -318,26 +200,26 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) } } - int blk_count = config_data.count("connect"); + size_t blk_count = config_data.count("connect"); if (blk_count == 0) { // No connect blocks found; make a trivial default block - std::vector<KeyVal>* items; + ConfigItems* items; ConfigTag* tag = ConfigTag::create("connect", "<auto>", 0, items); - items->push_back(std::make_pair("allow", "*")); + (*items)["allow"] = "*"; config_data.insert(std::make_pair("connect", tag)); blk_count = 1; } Classes.resize(blk_count); - std::map<std::string, int> names; + std::map<std::string, size_t> names; bool try_again = true; - for(int tries=0; try_again; tries++) + for(size_t tries = 0; try_again; tries++) { try_again = false; ConfigTagList tags = ConfTags("connect"); - int i=0; + size_t i = 0; for(ConfigIter it = tags.first; it != tags.second; ++it, ++i) { ConfigTag* tag = it->second; @@ -348,7 +230,7 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) std::string parentName = tag->getString("parent"); if (!parentName.empty()) { - std::map<std::string,int>::iterator parentIter = names.find(parentName); + std::map<std::string, size_t>::const_iterator parentIter = names.find(parentName); if (parentIter == names.end()) { try_again = true; @@ -404,30 +286,39 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) me->name = name; - me->registration_timeout = tag->getInt("timeout", me->registration_timeout); - me->pingtime = tag->getInt("pingfreq", me->pingtime); + me->registration_timeout = tag->getDuration("timeout", me->registration_timeout); + me->pingtime = tag->getDuration("pingfreq", me->pingtime); std::string sendq; if (tag->readString("sendq", sendq)) { // attempt to guess a good hard/soft sendq from a single value - long value = atol(sendq.c_str()); + unsigned long value = strtoul(sendq.c_str(), NULL, 10); if (value > 16384) me->softsendqmax = value / 16; else me->softsendqmax = value; me->hardsendqmax = value * 8; } - me->softsendqmax = tag->getInt("softsendq", me->softsendqmax); - me->hardsendqmax = tag->getInt("hardsendq", me->hardsendqmax); - me->recvqmax = tag->getInt("recvq", me->recvqmax); - me->penaltythreshold = tag->getInt("threshold", me->penaltythreshold); - me->commandrate = tag->getInt("commandrate", me->commandrate); + me->softsendqmax = tag->getUInt("softsendq", me->softsendqmax); + me->hardsendqmax = tag->getUInt("hardsendq", me->hardsendqmax); + me->recvqmax = tag->getUInt("recvq", me->recvqmax); + me->penaltythreshold = tag->getUInt("threshold", me->penaltythreshold); + me->commandrate = tag->getUInt("commandrate", me->commandrate); me->fakelag = tag->getBool("fakelag", me->fakelag); - me->maxlocal = tag->getInt("localmax", me->maxlocal); - me->maxglobal = tag->getInt("globalmax", me->maxglobal); - me->maxchans = tag->getInt("maxchans", me->maxchans); + me->maxlocal = tag->getUInt("localmax", me->maxlocal); + me->maxglobal = tag->getUInt("globalmax", me->maxglobal); + me->maxchans = tag->getUInt("maxchans", me->maxchans); me->maxconnwarn = tag->getBool("maxconnwarn", me->maxconnwarn); - me->limit = tag->getInt("limit", me->limit); + me->limit = tag->getUInt("limit", me->limit); + me->resolvehostnames = tag->getBool("resolvehostnames", me->resolvehostnames); + + std::string ports = tag->getString("port"); + if (!ports.empty()) + { + irc::portparser portrange(ports, false); + while (int port = portrange.GetToken()) + me->ports.insert(port); + } ClassMap::iterator oldMask = oldBlocksByMask.find(typeMask); if (oldMask != oldBlocksByMask.end()) @@ -445,141 +336,104 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) /** Represents a deprecated configuration tag. */ -struct Deprecated +struct DeprecatedConfig { - /** Tag name - */ - const char* tag; - /** Tag value - */ - const char* value; - /** Reason for deprecation - */ - const char* reason; + /** Tag name. */ + std::string tag; + + /** Attribute key. */ + std::string key; + + /** Attribute value. */ + std::string value; + + /** Reason for deprecation. */ + std::string reason; }; -static const Deprecated ChangedConfig[] = { - {"options", "hidelinks", "has been moved to <security:hidelinks> as of 1.2a3"}, - {"options", "hidewhois", "has been moved to <security:hidewhois> as of 1.2a3"}, - {"options", "userstats", "has been moved to <security:userstats> as of 1.2a3"}, - {"options", "customversion", "has been moved to <security:customversion> as of 1.2a3"}, - {"options", "hidesplits", "has been moved to <security:hidesplits> as of 1.2a3"}, - {"options", "hidebans", "has been moved to <security:hidebans> as of 1.2a3"}, - {"options", "hidekills", "has been moved to <security:hidekills> as of 1.2a3"}, - {"options", "operspywhois", "has been moved to <security:operspywhois> as of 1.2a3"}, - {"options", "announceinvites", "has been moved to <security:announceinvites> as of 1.2a3"}, - {"options", "hidemodes", "has been moved to <security:hidemodes> as of 1.2a3"}, - {"options", "maxtargets", "has been moved to <security:maxtargets> as of 1.2a3"}, - {"options", "nouserdns", "has been moved to <performance:nouserdns> as of 1.2a3"}, - {"options", "maxwho", "has been moved to <performance:maxwho> as of 1.2a3"}, - {"options", "softlimit", "has been moved to <performance:softlimit> as of 1.2a3"}, - {"options", "somaxconn", "has been moved to <performance:somaxconn> as of 1.2a3"}, - {"options", "netbuffersize", "has been moved to <performance:netbuffersize> as of 1.2a3"}, - {"options", "maxwho", "has been moved to <performance:maxwho> as of 1.2a3"}, - {"options", "loglevel", "1.2+ does not use the loglevel value. Please define <log> tags instead."}, - {"die", "value", "you need to reread your config"}, - {"bind", "transport", "has been moved to <bind:ssl> as of 2.0a1"}, - {"link", "transport", "has been moved to <link:ssl> as of 2.0a1"}, - {"link", "autoconnect", "2.0+ does not use the autoconnect value. Please define <autoconnect> tags instead."}, +static const DeprecatedConfig ChangedConfig[] = { + { "bind", "transport", "", "has been moved to <bind:ssl> as of 2.0" }, + { "die", "value", "", "you need to reread your config" }, + { "gnutls", "starttls", "", "has been replaced with m_starttls as of 3.0" }, + { "link", "autoconnect", "", "2.0+ does not use this attribute - define <autoconnect> tags instead" }, + { "link", "transport", "", "has been moved to <link:ssl> as of 2.0" }, + { "module", "name", "m_chanprotect.so", "has been replaced with m_customprefix as of 3.0" }, + { "module", "name", "m_halfop.so", "has been replaced with m_customprefix as of 3.0" }, + { "options", "cyclehosts", "", "has been replaced with m_hostcycle as of 3.0" }, + { "performance", "nouserdns", "", "has been moved to <connect:resolvehostnames> as of 3.0" } }; void ServerConfig::Fill() { ConfigTag* options = ConfValue("options"); ConfigTag* security = ConfValue("security"); + ConfigTag* server = ConfValue("server"); if (sid.empty()) { - ServerName = ConfValue("server")->getString("name"); - sid = ConfValue("server")->getString("id"); - ValidHost(ServerName, "<server:name>"); - if (!sid.empty() && !ServerInstance->IsSID(sid)) + ServerName = server->getString("name", "irc.example.com", InspIRCd::IsHost); + + sid = server->getString("id"); + if (!sid.empty() && !InspIRCd::IsSID(sid)) throw CoreException(sid + " is not a valid server ID. A server ID must be 3 characters long, with the first character a digit and the next two characters a digit or letter."); + + CaseMapping = options->getString("casemapping", "rfc1459"); + if (CaseMapping == "ascii") + national_case_insensitive_map = ascii_case_insensitive_map; + else if (CaseMapping == "rfc1459") + national_case_insensitive_map = rfc_case_insensitive_map; + else + throw CoreException("<options:casemapping> must be set to 'ascii', or 'rfc1459'"); } else { - if (ServerName != ConfValue("server")->getString("name")) - throw CoreException("You must restart to change the server name or SID"); - std::string nsid = ConfValue("server")->getString("id"); + std::string name = server->getString("name"); + if (!name.empty() && name != ServerName) + throw CoreException("You must restart to change the server name"); + + std::string nsid = server->getString("id"); if (!nsid.empty() && nsid != sid) - throw CoreException("You must restart to change the server name or SID"); + throw CoreException("You must restart to change the server id"); + + std::string casemapping = options->getString("casemapping"); + if (!casemapping.empty() && casemapping != CaseMapping) + throw CoreException("You must restart to change the server casemapping"); + } - diepass = ConfValue("power")->getString("diepass"); - restartpass = ConfValue("power")->getString("restartpass"); - powerhash = ConfValue("power")->getString("hash"); - PrefixQuit = options->getString("prefixquit"); - SuffixQuit = options->getString("suffixquit"); - FixedQuit = options->getString("fixedquit"); - PrefixPart = options->getString("prefixpart"); - SuffixPart = options->getString("suffixpart"); - FixedPart = options->getString("fixedpart"); - SoftLimit = ConfValue("performance")->getInt("softlimit", ServerInstance->SE->GetMaxFds()); - MaxConn = ConfValue("performance")->getInt("somaxconn", SOMAXCONN); - MoronBanner = options->getString("moronbanner", "You're banned!"); - ServerDesc = ConfValue("server")->getString("description", "Configure Me"); - Network = ConfValue("server")->getString("network", "Network"); - AdminName = ConfValue("admin")->getString("name", ""); - AdminEmail = ConfValue("admin")->getString("email", "null@example.com"); - AdminNick = ConfValue("admin")->getString("nick", "admin"); - ModPath = ConfValue("path")->getString("moduledir", MOD_PATH); - NetBufferSize = ConfValue("performance")->getInt("netbuffersize", 10240); - dns_timeout = ConfValue("dns")->getInt("timeout", 5); - DisabledCommands = ConfValue("disabled")->getString("commands", ""); + SoftLimit = ConfValue("performance")->getUInt("softlimit", (SocketEngine::GetMaxFds() > 0 ? SocketEngine::GetMaxFds() : LONG_MAX), 10); + CCOnConnect = ConfValue("performance")->getBool("clonesonconnect", true); + MaxConn = ConfValue("performance")->getUInt("somaxconn", SOMAXCONN); + XLineMessage = options->getString("xlinemessage", options->getString("moronbanner", "You're banned!")); + ServerDesc = server->getString("description", "Configure Me"); + Network = server->getString("network", "Network"); + NetBufferSize = ConfValue("performance")->getInt("netbuffersize", 10240, 1024, 65534); DisabledDontExist = ConfValue("disabled")->getBool("fakenonexistant"); UserStats = security->getString("userstats"); - CustomVersion = security->getString("customversion", Network + " IRCd"); + CustomVersion = security->getString("customversion"); HideSplits = security->getBool("hidesplits"); HideBans = security->getBool("hidebans"); - HideWhoisServer = security->getString("hidewhois"); + HideServer = security->getString("hideserver", security->getString("hidewhois")); HideKillsServer = security->getString("hidekills"); HideULineKills = security->getBool("hideulinekills"); - RestrictBannedUsers = security->getBool("restrictbannedusers", true); GenericOper = security->getBool("genericoper"); - NoUserDns = ConfValue("performance")->getBool("nouserdns"); SyntaxHints = options->getBool("syntaxhints"); - CycleHosts = options->getBool("cyclehosts"); - CycleHostsFromUser = options->getBool("cyclehostsfromuser"); - UndernetMsgPrefix = options->getBool("ircumsgprefix"); FullHostInTopic = options->getBool("hostintopic"); - MaxTargets = security->getInt("maxtargets", 20); - DefaultModes = options->getString("defaultmodes", "nt"); + MaxTargets = security->getUInt("maxtargets", 20, 1, 31); + DefaultModes = options->getString("defaultmodes", "not"); PID = ConfValue("pid")->getString("file"); - WhoWasGroupSize = ConfValue("whowas")->getInt("groupsize"); - WhoWasMaxGroups = ConfValue("whowas")->getInt("maxgroups"); - WhoWasMaxKeep = ServerInstance->Duration(ConfValue("whowas")->getString("maxkeep")); - MaxChans = ConfValue("channels")->getInt("users", 20); - OperMaxChans = ConfValue("channels")->getInt("opers", 60); - c_ipv4_range = ConfValue("cidr")->getInt("ipv4clone", 32); - c_ipv6_range = ConfValue("cidr")->getInt("ipv6clone", 128); - Limits.NickMax = ConfValue("limits")->getInt("maxnick", 32); - Limits.ChanMax = ConfValue("limits")->getInt("maxchan", 64); - Limits.MaxModes = ConfValue("limits")->getInt("maxmodes", 20); - Limits.IdentMax = ConfValue("limits")->getInt("maxident", 11); - Limits.MaxQuit = ConfValue("limits")->getInt("maxquit", 255); - Limits.MaxTopic = ConfValue("limits")->getInt("maxtopic", 307); - Limits.MaxKick = ConfValue("limits")->getInt("maxkick", 255); - Limits.MaxGecos = ConfValue("limits")->getInt("maxgecos", 128); - Limits.MaxAway = ConfValue("limits")->getInt("maxaway", 200); - InvBypassModes = options->getBool("invitebypassmodes", true); + MaxChans = ConfValue("channels")->getUInt("users", 20); + OperMaxChans = ConfValue("channels")->getUInt("opers", 0); + c_ipv4_range = ConfValue("cidr")->getUInt("ipv4clone", 32, 1, 32); + c_ipv6_range = ConfValue("cidr")->getUInt("ipv6clone", 128, 1, 128); + Limits = ServerLimits(ConfValue("limits")); + Paths = ServerPaths(ConfValue("path")); NoSnoticeStack = options->getBool("nosnoticestack", false); - WelcomeNotice = options->getBool("welcomenotice", true); - - range(SoftLimit, 10, ServerInstance->SE->GetMaxFds(), ServerInstance->SE->GetMaxFds(), "<performance:softlimit>"); - if (ConfValue("performance")->getBool("limitsomaxconn", true)) - range(MaxConn, 0, SOMAXCONN, SOMAXCONN, "<performance:somaxconn>"); - range(MaxTargets, 1, 31, 20, "<security:maxtargets>"); - range(NetBufferSize, 1024, 65534, 10240, "<performance:netbuffersize>"); - range(WhoWasGroupSize, 0, 10000, 10, "<whowas:groupsize>"); - range(WhoWasMaxGroups, 0, 1000000, 10240, "<whowas:maxgroups>"); - range(WhoWasMaxKeep, 3600, INT_MAX, 3600, "<whowas:maxkeep>"); - - ValidIP(DNSServer, "<dns:server>"); std::string defbind = options->getString("defaultbind"); - if (assign(defbind) == "ipv4") + if (stdalgo::string::equalsci(defbind, "ipv4")) { WildcardIPv6 = false; } - else if (assign(defbind) == "ipv6") + else if (stdalgo::string::equalsci(defbind, "ipv6")) { WildcardIPv6 = true; } @@ -590,75 +444,43 @@ void ServerConfig::Fill() if (socktest < 0) WildcardIPv6 = false; else - ServerInstance->SE->Close(socktest); - } - ConfigTagList tags = ConfTags("uline"); - for(ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - std::string server; - if (!tag->readString("server", server)) - throw CoreException("<uline> tag missing server at " + tag->getTagLocation()); - ulines[assign(server)] = tag->getBool("silent"); - } - - tags = ConfTags("banlist"); - for(ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - std::string chan; - if (!tag->readString("chan", chan)) - throw CoreException("<banlist> tag missing chan at " + tag->getTagLocation()); - maxbans[chan] = tag->getInt("limit"); + SocketEngine::Close(socktest); } + ServerInstance->XLines->ClearConfigLines(); ReadXLine(this, "badip", "ipmask", ServerInstance->XLines->GetFactory("Z")); ReadXLine(this, "badnick", "nick", ServerInstance->XLines->GetFactory("Q")); ReadXLine(this, "badhost", "host", ServerInstance->XLines->GetFactory("K")); ReadXLine(this, "exception", "host", ServerInstance->XLines->GetFactory("E")); - memset(DisabledUModes, 0, sizeof(DisabledUModes)); + const std::string restrictbannedusers = options->getString("restrictbannedusers", "yes"); + if (stdalgo::string::equalsci(restrictbannedusers, "no")) + RestrictBannedUsers = ServerConfig::BUT_NORMAL; + else if (stdalgo::string::equalsci(restrictbannedusers, "silent")) + RestrictBannedUsers = ServerConfig::BUT_RESTRICT_SILENT; + else if (stdalgo::string::equalsci(restrictbannedusers, "yes")) + RestrictBannedUsers = ServerConfig::BUT_RESTRICT_NOTIFY; + else + throw CoreException(restrictbannedusers + " is an invalid <options:restrictbannedusers> value, at " + options->getTagLocation()); + + DisabledUModes.reset(); std::string modes = ConfValue("disabled")->getString("usermodes"); for (std::string::const_iterator p = modes.begin(); p != modes.end(); ++p) { - // Complain when the character is not a-z or A-Z - if ((*p < 'A') || (*p > 'z') || ((*p < 'a') && (*p > 'Z'))) + // Complain when the character is not a valid mode character. + if (!ModeParser::IsModeChar(*p)) throw CoreException("Invalid usermode " + std::string(1, *p) + " was found."); - DisabledUModes[*p - 'A'] = 1; + DisabledUModes.set(*p - 'A'); } - memset(DisabledCModes, 0, sizeof(DisabledCModes)); + DisabledCModes.reset(); modes = ConfValue("disabled")->getString("chanmodes"); for (std::string::const_iterator p = modes.begin(); p != modes.end(); ++p) { - if ((*p < 'A') || (*p > 'z') || ((*p < 'a') && (*p > 'Z'))) + if (!ModeParser::IsModeChar(*p)) throw CoreException("Invalid chanmode " + std::string(1, *p) + " was found."); - DisabledCModes[*p - 'A'] = 1; + DisabledCModes.set(*p - 'A'); } - - memset(HideModeLists, 0, sizeof(HideModeLists)); - modes = ConfValue("security")->getString("hidemodes"); - for (std::string::const_iterator p = modes.begin(); p != modes.end(); ++p) - HideModeLists[(unsigned char) *p] = true; - - std::string v = security->getString("announceinvites"); - - if (v == "ops") - AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_OPS; - else if (v == "all") - AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_ALL; - else if (v == "dynamic") - AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_DYNAMIC; - else - AnnounceInvites = ServerConfig::INVITE_ANNOUNCE_NONE; - - v = security->getString("operspywhois"); - if (v == "splitmsg") - OperSpyWhois = SPYWHOIS_SPLITMSG; - else if (v == "on" || v == "yes") - OperSpyWhois = SPYWHOIS_SINGLEMSG; - else - OperSpyWhois = SPYWHOIS_NONE; } // WARNING: it is not safe to use most of the codebase in this function, as it @@ -675,12 +497,7 @@ void ServerConfig::Read() catch (CoreException& err) { valid = false; - errstr << err.GetReason(); - } - if (valid) - { - DNSServer = ConfValue("dns")->getString("server"); - FindDNS(DNSServer); + errstr << err.GetReason() << std::endl; } } @@ -692,6 +509,7 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) /* * These values can only be set on boot. Keep their old values. Do it before we send messages so we actually have a servername. */ + this->CaseMapping = old->CaseMapping; this->ServerName = old->ServerName; this->sid = old->sid; this->cmdline = old->cmdline; @@ -700,16 +518,26 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) /* The stuff in here may throw CoreException, be sure we're in a position to catch it. */ try { - for (int Index = 0; Index * sizeof(Deprecated) < sizeof(ChangedConfig); Index++) + for (unsigned long index = 0; index * sizeof(DeprecatedConfig) < sizeof(ChangedConfig); index++) { - std::string dummy; - ConfigTagList tags = ConfTags(ChangedConfig[Index].tag); + std::string value; + ConfigTagList tags = ConfTags(ChangedConfig[index].tag); for(ConfigIter i = tags.first; i != tags.second; ++i) { - if (i->second->readString(ChangedConfig[Index].value, dummy, true)) - errstr << "Your configuration contains a deprecated value: <" - << ChangedConfig[Index].tag << ":" << ChangedConfig[Index].value << "> - " << ChangedConfig[Index].reason - << " (at " << i->second->getTagLocation() << ")\n"; + if (i->second->readString(ChangedConfig[index].key, value, true) + && (ChangedConfig[index].value.empty() || value == ChangedConfig[index].value)) + { + errstr << "Your configuration contains a deprecated value: <" << ChangedConfig[index].tag; + if (ChangedConfig[index].value.empty()) + { + errstr << ':' << ChangedConfig[index].key; + } + else + { + errstr << ' ' << ChangedConfig[index].key << "=\"" << ChangedConfig[index].value << "\""; + } + errstr << "> - " << ChangedConfig[index].reason << " (at " << i->second->getTagLocation() << ")" << std::endl; + } } } @@ -721,7 +549,7 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) } catch (CoreException &ce) { - errstr << ce.GetReason(); + errstr << ce.GetReason() << std::endl; } // Check errors before dealing with failed binds, since continuing on failed bind is wanted in some circumstances. @@ -731,6 +559,11 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) if (valid) ServerInstance->WritePID(this->PID, !old); + ConfigTagList binds = ConfTags("bind"); + if (binds.first == binds.second) + errstr << "Possible configuration error: you have not defined any <bind> blocks." << std::endl + << "You will need to do this if you want clients to be able to connect!" << std::endl; + if (old && valid) { // On first run, ports are bound later on @@ -738,14 +571,13 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) ServerInstance->BindPorts(pl); if (pl.size()) { - errstr << "Not all your client ports could be bound.\nThe following port(s) failed to bind:\n"; + errstr << "Not all your client ports could be bound." << std::endl + << "The following port(s) failed to bind:" << std::endl; int j = 1; for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++) { - char buf[MAXBUF]; - snprintf(buf, MAXBUF, "%d. Address: %s Reason: %s\n", j, i->first.empty() ? "<all>" : i->first.c_str(), i->second.c_str()); - errstr << buf; + errstr << j << ".\tAddress: " << i->first.str() << "\tReason: " << strerror(i->second) << std::endl; } } } @@ -754,7 +586,7 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) if (!valid) { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "There were errors in your configuration file:"); + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "There were errors in your configuration file:"); Classes.clear(); } @@ -769,7 +601,7 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) std::cout << line << std::endl; // If a user is rehashing, tell them directly if (user) - user->SendText(":%s NOTICE %s :*** %s", ServerInstance->Config->ServerName.c_str(), user->nick.c_str(), line.c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** %s", line.c_str())); // Also tell opers ServerInstance->SNO->WriteGlobalSno('a', line); } @@ -781,17 +613,10 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) for (ClassVector::const_iterator it = this->Classes.begin(), it_end = this->Classes.end(); it != it_end; ++it) { ConfigTag *tag = (*it)->config; - // Make sure our connection class allows motd colors - if(!tag->getBool("allowmotdcolors")) - continue; ConfigFileCache::iterator file = this->Files.find(tag->getString("motd", "motd")); if (file != this->Files.end()) - InspIRCd::ProcessColors(file->second); - - file = this->Files.find(tag->getString("rules", "rules")); - if (file != this->Files.end()) - InspIRCd::ProcessColors(file->second); + InspIRCd::ProcessColors(file->second); } /* No old configuration -> initial boot, nothing more to do here */ @@ -813,20 +638,14 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) ApplyModules(user); if (user) - user->SendText(":%s NOTICE %s :*** Successfully rehashed server.", - ServerInstance->Config->ServerName.c_str(), user->nick.c_str()); + user->WriteRemoteNotice("*** Successfully rehashed server."); ServerInstance->SNO->WriteGlobalSno('a', "*** Successfully rehashed server."); } void ServerConfig::ApplyModules(User* user) { - Module* whowas = ServerInstance->Modules->Find("cmd_whowas.so"); - if (whowas) - WhowasRequest(NULL, whowas, WhowasRequest::WHOWAS_PRUNE).Send(); - - const std::vector<std::string> v = ServerInstance->Modules->GetAllModuleNames(0); std::vector<std::string> added_modules; - std::set<std::string> removed_modules(v.begin(), v.end()); + ModuleManager::ModuleMap removed_modules = ServerInstance->Modules->GetModules(); ConfigTagList tags = ConfTags("module"); for(ConfigIter i = tags.first; i != tags.second; ++i) @@ -835,6 +654,7 @@ void ServerConfig::ApplyModules(User* user) std::string name; if (tag->readString("name", name)) { + name = ModuleManager::ExpandModName(name); // if this module is already loaded, the erase will succeed, so we need do nothing // otherwise, we need to add the module (which will be done later) if (removed_modules.erase(name) == 0) @@ -842,58 +662,54 @@ void ServerConfig::ApplyModules(User* user) } } - if (ConfValue("options")->getBool("allowhalfop") && removed_modules.erase("m_halfop.so") == 0) - added_modules.push_back("m_halfop.so"); - - for (std::set<std::string>::iterator removing = removed_modules.begin(); removing != removed_modules.end(); removing++) + for (ModuleManager::ModuleMap::iterator i = removed_modules.begin(); i != removed_modules.end(); ++i) { - // Don't remove cmd_*.so, just remove m_*.so - if (removing->c_str()[0] == 'c') + const std::string& modname = i->first; + // Don't remove core_*.so, just remove m_*.so + if (InspIRCd::Match(modname, "core_*.so", ascii_case_insensitive_map)) continue; - Module* m = ServerInstance->Modules->Find(*removing); - if (m && ServerInstance->Modules->Unload(m)) + if (ServerInstance->Modules->Unload(i->second)) { - ServerInstance->SNO->WriteGlobalSno('a', "*** REHASH UNLOADED MODULE: %s",removing->c_str()); + ServerInstance->SNO->WriteGlobalSno('a', "*** REHASH UNLOADED MODULE: %s", modname.c_str()); if (user) - user->WriteNumeric(RPL_UNLOADEDMODULE, "%s %s :Module %s successfully unloaded.",user->nick.c_str(), removing->c_str(), removing->c_str()); + user->WriteNumeric(RPL_UNLOADEDMODULE, modname, InspIRCd::Format("Module %s successfully unloaded.", modname.c_str())); else - ServerInstance->SNO->WriteGlobalSno('a', "Module %s successfully unloaded.", removing->c_str()); + ServerInstance->SNO->WriteGlobalSno('a', "Module %s successfully unloaded.", modname.c_str()); } else { if (user) - user->WriteNumeric(ERR_CANTUNLOADMODULE, "%s %s :Failed to unload module %s: %s",user->nick.c_str(), removing->c_str(), removing->c_str(), ServerInstance->Modules->LastError().c_str()); + user->WriteNumeric(ERR_CANTUNLOADMODULE, modname, InspIRCd::Format("Failed to unload module %s: %s", modname.c_str(), ServerInstance->Modules->LastError().c_str())); else - ServerInstance->SNO->WriteGlobalSno('a', "Failed to unload module %s: %s", removing->c_str(), ServerInstance->Modules->LastError().c_str()); + ServerInstance->SNO->WriteGlobalSno('a', "Failed to unload module %s: %s", modname.c_str(), ServerInstance->Modules->LastError().c_str()); } } for (std::vector<std::string>::iterator adding = added_modules.begin(); adding != added_modules.end(); adding++) { - if (ServerInstance->Modules->Load(adding->c_str())) + // Skip modules which are already loaded. + if (ServerInstance->Modules->Find(*adding)) + continue; + + if (ServerInstance->Modules->Load(*adding)) { ServerInstance->SNO->WriteGlobalSno('a', "*** REHASH LOADED MODULE: %s",adding->c_str()); if (user) - user->WriteNumeric(RPL_LOADEDMODULE, "%s %s :Module %s successfully loaded.",user->nick.c_str(), adding->c_str(), adding->c_str()); + user->WriteNumeric(RPL_LOADEDMODULE, *adding, InspIRCd::Format("Module %s successfully loaded.", adding->c_str())); else ServerInstance->SNO->WriteGlobalSno('a', "Module %s successfully loaded.", adding->c_str()); } else { if (user) - user->WriteNumeric(ERR_CANTLOADMODULE, "%s %s :Failed to load module %s: %s",user->nick.c_str(), adding->c_str(), adding->c_str(), ServerInstance->Modules->LastError().c_str()); + user->WriteNumeric(ERR_CANTLOADMODULE, *adding, InspIRCd::Format("Failed to load module %s: %s", adding->c_str(), ServerInstance->Modules->LastError().c_str())); else ServerInstance->SNO->WriteGlobalSno('a', "Failed to load module %s: %s", adding->c_str(), ServerInstance->Modules->LastError().c_str()); } } } -bool ServerConfig::StartsWithWindowsDriveLetter(const std::string &path) -{ - return (path.length() > 2 && isalpha(path[0]) && path[1] == ':'); -} - ConfigTag* ServerConfig::ConfValue(const std::string &tag) { ConfigTagList found = config_data.equal_range(tag); @@ -902,7 +718,7 @@ ConfigTag* ServerConfig::ConfValue(const std::string &tag) ConfigTag* rv = found.first->second; found.first++; if (found.first != found.second) - ServerInstance->Logs->Log("CONFIG",DEFAULT, "Multiple <" + tag + "> tags found; only first will be used " + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Multiple <" + tag + "> tags found; only first will be used " "(first at " + rv->getTagLocation() + "; second at " + found.first->second->getTagLocation() + ")"); return rv; } @@ -912,35 +728,28 @@ ConfigTagList ServerConfig::ConfTags(const std::string& tag) return config_data.equal_range(tag); } -bool ServerConfig::FileExists(const char* file) +std::string ServerConfig::Escape(const std::string& str, bool xml) { - struct stat sb; - if (stat(file, &sb) == -1) - return false; - - if ((sb.st_mode & S_IFDIR) > 0) - return false; - - FILE *input = fopen(file, "r"); - if (input == NULL) - return false; - else + std::string escaped; + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) { - fclose(input); - return true; + switch (*it) + { + case '"': + escaped += xml ? """ : "\""; + break; + case '&': + escaped += xml ? "&" : "&"; + break; + case '\\': + escaped += xml ? "\\" : "\\\\"; + break; + default: + escaped += *it; + break; + } } -} - -const char* ServerConfig::CleanFilename(const char* name) -{ - const char* p = name + strlen(name); - while ((p != name) && (*p != '/') && (*p != '\\')) p--; - return (p != name ? ++p : p); -} - -const std::string& ServerConfig::GetSID() -{ - return sid; + return escaped; } void ConfigReaderThread::Run() @@ -952,7 +761,7 @@ void ConfigReaderThread::Run() void ConfigReaderThread::Finish() { ServerConfig* old = ServerInstance->Config; - ServerInstance->Logs->Log("CONFIG",DEBUG,"Switching to new configuration..."); + ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "Switching to new configuration..."); ServerInstance->Config = this->Config; Config->Apply(old, TheUserUID); @@ -964,15 +773,33 @@ void ConfigReaderThread::Finish() * XXX: The order of these is IMPORTANT, do not reorder them without testing * thoroughly!!! */ - ServerInstance->Users->RehashCloneCounts(); + ServerInstance->Users.RehashCloneCounts(); ServerInstance->XLines->CheckELines(); ServerInstance->XLines->ApplyLines(); - ServerInstance->Res->Rehash(); - ServerInstance->ResetMaxBans(); - Config->ApplyDisabledCommands(Config->DisabledCommands); + Config->ApplyDisabledCommands(); User* user = ServerInstance->FindNick(TheUserUID); - FOREACH_MOD(I_OnRehash, OnRehash(user)); - ServerInstance->BuildISupport(); + + ConfigStatus status(user); + const ModuleManager::ModuleMap& mods = ServerInstance->Modules->GetModules(); + for (ModuleManager::ModuleMap::const_iterator i = mods.begin(); i != mods.end(); ++i) + { + try + { + ServerInstance->Logs->Log("MODULE", LOG_DEBUG, "Rehashing " + i->first); + i->second->ReadConfig(status); + } + catch (CoreException& modex) + { + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "Exception caught: " + modex.GetReason()); + if (user) + user->WriteNotice(i->first + ": " + modex.GetReason()); + } + } + + // The description of this server may have changed - update it for WHOIS etc. + ServerInstance->FakeClient->server->description = Config->ServerDesc; + + ServerInstance->ISupport.Build(); ServerInstance->Logs->CloseLogs(); ServerInstance->Logs->OpenFileLogs(); diff --git a/src/coremods/core_channel/cmd_invite.cpp b/src/coremods/core_channel/cmd_invite.cpp new file mode 100644 index 000000000..1b480aa20 --- /dev/null +++ b/src/coremods/core_channel/cmd_invite.cpp @@ -0,0 +1,185 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_channel.h" +#include "invite.h" + +CommandInvite::CommandInvite(Module* parent, Invite::APIImpl& invapiimpl) + : Command(parent, "INVITE", 0, 0) + , invapi(invapiimpl) +{ + Penalty = 4; + syntax = "[<nick> <channel>]"; +} + +/** Handle /INVITE + */ +CmdResult CommandInvite::Handle(User* user, const Params& parameters) +{ + ModResult MOD_RESULT; + + if (parameters.size() >= 2) + { + User* u; + if (IS_LOCAL(user)) + u = ServerInstance->FindNickOnly(parameters[0]); + else + u = ServerInstance->FindNick(parameters[0]); + + Channel* c = ServerInstance->FindChan(parameters[1]); + time_t timeout = 0; + if (parameters.size() >= 3) + { + if (IS_LOCAL(user)) + timeout = ServerInstance->Time() + InspIRCd::Duration(parameters[2]); + else if (parameters.size() > 3) + timeout = ConvToInt(parameters[3]); + } + + if (!c) + { + user->WriteNumeric(Numerics::NoSuchChannel(parameters[1])); + return CMD_FAILURE; + } + if ((!u) || (u->registered != REG_ALL)) + { + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + return CMD_FAILURE; + } + + // Verify channel timestamp if the INVITE is coming from a remote server + if (!IS_LOCAL(user)) + { + // Remote INVITE commands must carry a channel timestamp + if (parameters.size() < 3) + return CMD_INVALID; + + // Drop the invite if our channel TS is lower + time_t RemoteTS = ConvToInt(parameters[2]); + if (c->age < RemoteTS) + return CMD_FAILURE; + } + + if ((IS_LOCAL(user)) && (!c->HasUser(user))) + { + user->WriteNumeric(ERR_NOTONCHANNEL, c->name, "You're not on that channel!"); + return CMD_FAILURE; + } + + if (c->HasUser(u)) + { + user->WriteNumeric(ERR_USERONCHANNEL, u->nick, c->name, "is already on channel"); + return CMD_FAILURE; + } + + FIRST_MOD_RESULT(OnUserPreInvite, MOD_RESULT, (user,u,c,timeout)); + + if (MOD_RESULT == MOD_RES_DENY) + { + return CMD_FAILURE; + } + else if (MOD_RESULT == MOD_RES_PASSTHRU) + { + if (IS_LOCAL(user)) + { + unsigned int rank = c->GetPrefixValue(user); + if (rank < HALFOP_VALUE) + { + // Check whether halfop mode is available and phrase error message accordingly + ModeHandler* mh = ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, InspIRCd::Format("You must be a channel %soperator", + (mh && mh->name == "halfop" ? "half-" : ""))); + return CMD_FAILURE; + } + } + } + + LocalUser* const localtargetuser = IS_LOCAL(u); + if (localtargetuser) + { + invapi.Create(localtargetuser, c, timeout); + ClientProtocol::Messages::Invite invitemsg(user, localtargetuser, c); + localtargetuser->Send(ServerInstance->GetRFCEvents().invite, invitemsg); + } + + if (IS_LOCAL(user)) + { + user->WriteNumeric(RPL_INVITING, u->nick, c->name); + if (u->IsAway()) + user->WriteNumeric(RPL_AWAY, u->nick, u->awaymsg); + } + + char prefix = 0; + unsigned int minrank = 0; + switch (announceinvites) + { + case Invite::ANNOUNCE_OPS: + { + prefix = '@'; + minrank = OP_VALUE; + break; + } + case Invite::ANNOUNCE_DYNAMIC: + { + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h'); + if ((mh) && (mh->name == "halfop")) + { + prefix = mh->GetPrefix(); + minrank = mh->GetPrefixRank(); + } + break; + } + default: + { + } + } + + CUList excepts; + FOREACH_MOD(OnUserInvite, (user, u, c, timeout, minrank, excepts)); + + if (announceinvites != Invite::ANNOUNCE_NONE) + { + excepts.insert(user); + ClientProtocol::Messages::Privmsg privmsg(ServerInstance->FakeClient, c, InspIRCd::Format("*** %s invited %s into the channel", user->nick.c_str(), u->nick.c_str()), MSG_NOTICE); + c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, prefix, excepts); + } + } + else if (IS_LOCAL(user)) + { + // pinched from ircu - invite with not enough parameters shows channels + // youve been invited to but haven't joined yet. + const Invite::List* list = invapi.GetList(IS_LOCAL(user)); + if (list) + { + for (Invite::List::const_iterator i = list->begin(); i != list->end(); ++i) + user->WriteNumeric(RPL_INVITELIST, (*i)->chan->name); + } + user->WriteNumeric(RPL_ENDOFINVITELIST, "End of INVITE list"); + } + return CMD_SUCCESS; +} + +RouteDescriptor CommandInvite::GetRouting(User* user, const Params& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} diff --git a/src/coremods/core_channel/cmd_join.cpp b/src/coremods/core_channel/cmd_join.cpp new file mode 100644 index 000000000..2caed9dc6 --- /dev/null +++ b/src/coremods/core_channel/cmd_join.cpp @@ -0,0 +1,60 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_channel.h" + +CommandJoin::CommandJoin(Module* parent) + : SplitCommand(parent, "JOIN", 1, 2) +{ + syntax = "<channel>{,<channel>} {<key>{,<key>}}"; + Penalty = 2; +} + +/** Handle /JOIN + */ +CmdResult CommandJoin::HandleLocal(LocalUser* user, const Params& parameters) +{ + if (parameters.size() > 1) + { + if (CommandParser::LoopCall(user, this, parameters, 0, 1, false)) + return CMD_SUCCESS; + + if (ServerInstance->IsChannel(parameters[0])) + { + Channel::JoinUser(user, parameters[0], false, parameters[1]); + return CMD_SUCCESS; + } + } + else + { + if (CommandParser::LoopCall(user, this, parameters, 0, -1, false)) + return CMD_SUCCESS; + + if (ServerInstance->IsChannel(parameters[0])) + { + Channel::JoinUser(user, parameters[0]); + return CMD_SUCCESS; + } + } + + user->WriteNumeric(ERR_BADCHANMASK, parameters[0], "Invalid channel name"); + return CMD_FAILURE; +} diff --git a/src/coremods/core_channel/cmd_kick.cpp b/src/coremods/core_channel/cmd_kick.cpp new file mode 100644 index 000000000..755c6613b --- /dev/null +++ b/src/coremods/core_channel/cmd_kick.cpp @@ -0,0 +1,133 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_channel.h" + +CommandKick::CommandKick(Module* parent) + : Command(parent, "KICK", 2, 3) +{ + syntax = "<channel> <nick>{,<nick>} [<reason>]"; +} + +/** Handle /KICK + */ +CmdResult CommandKick::Handle(User* user, const Params& parameters) +{ + Channel* c = ServerInstance->FindChan(parameters[0]); + User* u; + + if (CommandParser::LoopCall(user, this, parameters, 1)) + return CMD_SUCCESS; + + if (IS_LOCAL(user)) + u = ServerInstance->FindNickOnly(parameters[1]); + else + u = ServerInstance->FindNick(parameters[1]); + + if (!c) + { + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); + return CMD_FAILURE; + } + if ((!u) || (u->registered != REG_ALL)) + { + user->WriteNumeric(Numerics::NoSuchNick(parameters[1])); + return CMD_FAILURE; + } + + Membership* srcmemb = NULL; + if (IS_LOCAL(user)) + { + srcmemb = c->GetUser(user); + if (!srcmemb) + { + user->WriteNumeric(ERR_NOTONCHANNEL, parameters[0], "You're not on that channel!"); + return CMD_FAILURE; + } + + if (u->server->IsULine()) + { + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, "You may not kick a u-lined client"); + return CMD_FAILURE; + } + } + + const Channel::MemberMap::iterator victimiter = c->userlist.find(u); + if (victimiter == c->userlist.end()) + { + user->WriteNumeric(ERR_USERNOTINCHANNEL, u->nick, c->name, "They are not on that channel"); + return CMD_FAILURE; + } + Membership* const memb = victimiter->second; + + // KICKs coming from servers can carry a membership id + if ((!IS_LOCAL(user)) && (parameters.size() > 3)) + { + // If the current membership id is not equal to the one in the message then the user rejoined + if (memb->id != Membership::IdFromString(parameters[2])) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Dropped KICK due to membership id mismatch: " + ConvToStr(memb->id) + " != " + parameters[2]); + return CMD_FAILURE; + } + } + + const bool has_reason = (parameters.size() > 2); + const std::string reason((has_reason ? parameters.back() : user->nick), 0, ServerInstance->Config->Limits.MaxKick); + + // Do the following checks only if the KICK is done by a local user; + // each server enforces its own rules. + if (srcmemb) + { + // Modules are allowed to explicitly allow or deny kicks done by local users + ModResult res; + FIRST_MOD_RESULT(OnUserPreKick, res, (user, memb, reason)); + if (res == MOD_RES_DENY) + return CMD_FAILURE; + + if (res == MOD_RES_PASSTHRU) + { + unsigned int them = srcmemb->getRank(); + unsigned int req = HALFOP_VALUE; + for (std::string::size_type i = 0; i < memb->modes.length(); i++) + { + ModeHandler* mh = ServerInstance->Modes->FindMode(memb->modes[i], MODETYPE_CHANNEL); + if (mh && mh->GetLevelRequired(true) > req) + req = mh->GetLevelRequired(true); + } + + if (them < req) + { + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, InspIRCd::Format("You must be a channel %soperator", + req > HALFOP_VALUE ? "" : "half-")); + return CMD_FAILURE; + } + } + } + + c->KickUser(user, victimiter, reason); + + return CMD_SUCCESS; +} + +RouteDescriptor CommandKick::GetRouting(User* user, const Params& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} diff --git a/src/coremods/core_channel/cmd_names.cpp b/src/coremods/core_channel/cmd_names.cpp new file mode 100644 index 000000000..28273c903 --- /dev/null +++ b/src/coremods/core_channel/cmd_names.cpp @@ -0,0 +1,114 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_channel.h" + +CommandNames::CommandNames(Module* parent) + : SplitCommand(parent, "NAMES", 0, 0) + , secretmode(parent, "secret") + , privatemode(parent, "private") + , invisiblemode(parent, "invisible") +{ + syntax = "{<channel>{,<channel>}}"; +} + +/** Handle /NAMES + */ +CmdResult CommandNames::HandleLocal(LocalUser* user, const Params& parameters) +{ + Channel* c; + + if (parameters.empty()) + { + user->WriteNumeric(RPL_ENDOFNAMES, '*', "End of /NAMES list."); + return CMD_SUCCESS; + } + + if (CommandParser::LoopCall(user, this, parameters, 0)) + return CMD_SUCCESS; + + c = ServerInstance->FindChan(parameters[0]); + if (c) + { + // Show the NAMES list if one of the following is true: + // - the channel is not secret + // - the user doing the /NAMES is inside the channel + // - the user doing the /NAMES has the channels/auspex privilege + + // If the user is inside the channel or has privs, instruct SendNames() to show invisible (+i) members + bool show_invisible = ((c->HasUser(user)) || (user->HasPrivPermission("channels/auspex"))); + if ((show_invisible) || (!c->IsModeSet(secretmode))) + { + SendNames(user, c, show_invisible); + return CMD_SUCCESS; + } + } + + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); + return CMD_FAILURE; +} + +void CommandNames::SendNames(LocalUser* user, Channel* chan, bool show_invisible) +{ + Numeric::Builder<' '> reply(user, RPL_NAMREPLY, false, chan->name.size() + 3); + Numeric::Numeric& numeric = reply.GetNumeric(); + if (chan->IsModeSet(secretmode)) + numeric.push(std::string(1, '@')); + else if (chan->IsModeSet(privatemode)) + numeric.push(std::string(1, '*')); + else + numeric.push(std::string(1, '=')); + + numeric.push(chan->name); + numeric.push(std::string()); + + std::string prefixlist; + std::string nick; + const Channel::MemberMap& members = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = members.begin(); i != members.end(); ++i) + { + if ((!show_invisible) && (i->first->IsModeSet(invisiblemode))) + { + // Member is invisible and we are not supposed to show them + continue; + } + + Membership* const memb = i->second; + + prefixlist.clear(); + char prefix = memb->GetPrefixChar(); + if (prefix) + prefixlist.push_back(prefix); + nick = i->first->nick; + + ModResult res; + FIRST_MOD_RESULT(OnNamesListItem, res, (user, memb, prefixlist, nick)); + + // See if a module wants us to exclude this user from NAMES + if (res == MOD_RES_DENY) + continue; + + reply.Add(prefixlist, nick); + } + + reply.Flush(); + user->WriteNumeric(RPL_ENDOFNAMES, chan->name, "End of /NAMES list."); +} diff --git a/src/coremods/core_channel/cmd_topic.cpp b/src/coremods/core_channel/cmd_topic.cpp new file mode 100644 index 000000000..0417f1a0c --- /dev/null +++ b/src/coremods/core_channel/cmd_topic.cpp @@ -0,0 +1,102 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_channel.h" + +CommandTopic::CommandTopic(Module* parent) + : SplitCommand(parent, "TOPIC", 1, 2) + , exemptionprov(parent) + , secretmode(parent, "secret") + , topiclockmode(parent, "topiclock") +{ + syntax = "<channel> [<topic>]"; + Penalty = 2; +} + +CmdResult CommandTopic::HandleLocal(LocalUser* user, const Params& parameters) +{ + Channel* c = ServerInstance->FindChan(parameters[0]); + if (!c) + { + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); + return CMD_FAILURE; + } + + if (parameters.size() == 1) + { + if ((c->IsModeSet(secretmode)) && (!c->HasUser(user))) + { + user->WriteNumeric(Numerics::NoSuchChannel(c->name)); + return CMD_FAILURE; + } + + if (c->topic.length()) + { + Topic::ShowTopic(user, c); + } + else + { + user->WriteNumeric(RPL_NOTOPICSET, c->name, "No topic is set."); + } + return CMD_SUCCESS; + } + + std::string t = parameters[1]; // needed, in case a module wants to change it + ModResult res; + FIRST_MOD_RESULT(OnPreTopicChange, res, (user,c,t)); + + if (res == MOD_RES_DENY) + return CMD_FAILURE; + if (res != MOD_RES_ALLOW) + { + if (!c->HasUser(user)) + { + user->WriteNumeric(ERR_NOTONCHANNEL, c->name, "You're not on that channel!"); + return CMD_FAILURE; + } + if (c->IsModeSet(topiclockmode)) + { + ModResult MOD_RESULT = CheckExemption::Call(exemptionprov, user, c, "topiclock"); + if (!MOD_RESULT.check(c->GetPrefixValue(user) >= HALFOP_VALUE)) + { + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, "You do not have access to change the topic on this channel"); + return CMD_FAILURE; + } + } + } + + // Make sure the topic is not longer than the limit in the config + if (t.length() > ServerInstance->Config->Limits.MaxTopic) + t.erase(ServerInstance->Config->Limits.MaxTopic); + + // Only change if the new topic is different than the current one + if (c->topic != t) + c->SetTopic(user, t, ServerInstance->Time()); + return CMD_SUCCESS; +} + +void Topic::ShowTopic(LocalUser* user, Channel* chan) +{ + user->WriteNumeric(RPL_TOPIC, chan->name, chan->topic); + user->WriteNumeric(RPL_TOPICTIME, chan->name, chan->setby, (unsigned long)chan->topicset); +} diff --git a/src/modes/cmode_k.cpp b/src/coremods/core_channel/cmode_k.cpp index 400333fce..4fc29e04c 100644 --- a/src/modes/cmode_k.cpp +++ b/src/coremods/core_channel/cmode_k.cpp @@ -21,79 +21,69 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/cmode_k.h" +#include "core_channel.h" -ModeChannelKey::ModeChannelKey() : ModeHandler(NULL, "key", 'k', PARAM_ALWAYS, MODETYPE_CHANNEL) -{ -} - -void ModeChannelKey::RemoveMode(Channel* channel, irc::modestacker* stack) -{ - /** +k needs a parameter when being removed, - * so we have a special-case RemoveMode here for it - */ - - if (channel->IsModeSet('k')) - { - if (stack) - { - stack->Push('k', channel->GetModeParameter('k')); - } - else - { - std::vector<std::string> parameters; - parameters.push_back(channel->name); - parameters.push_back("-k"); - parameters.push_back(channel->GetModeParameter('k')); - ServerInstance->SendMode(parameters, ServerInstance->FakeClient); - } - } -} +const std::string::size_type ModeChannelKey::maxkeylen = 32; -void ModeChannelKey::RemoveMode(User*, irc::modestacker* stack) +ModeChannelKey::ModeChannelKey(Module* Creator) + : ParamMode<ModeChannelKey, LocalStringExt>(Creator, "key", 'k', PARAM_ALWAYS) { } ModeAction ModeChannelKey::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) { - bool exists = channel->IsModeSet('k'); + const std::string* key = ext.get(channel); + bool exists = (key != NULL); if (IS_LOCAL(source)) { if (exists == adding) return MODEACTION_DENY; - if (exists && (parameter != channel->GetModeParameter('k'))) + if (exists && (parameter != *key)) { /* Key is currently set and the correct key wasnt given */ return MODEACTION_DENY; } } else { - if (exists && adding && parameter == channel->GetModeParameter('k')) + if (exists && adding && parameter == *key) { /* no-op, don't show */ return MODEACTION_DENY; } } - /* invalid keys */ - if (!parameter.length()) - return MODEACTION_DENY; - - if (parameter.rfind(' ') != std::string::npos) - return MODEACTION_DENY; - if (adding) { - std::string ckey; - ckey.assign(parameter, 0, 32); - parameter = ckey; - channel->SetModeParam('k', parameter); + // When joining a channel multiple keys are delimited with a comma so we strip + // them out here to avoid creating channels that are unjoinable. + size_t commapos; + while ((commapos = parameter.find(',')) != std::string::npos) + parameter.erase(commapos, 1); + + // Truncate the parameter to the maximum key length. + if (parameter.length() > maxkeylen) + parameter.erase(maxkeylen); + + // If the password is empty here then it only consisted of commas. This is not + // acceptable so we reject the mode change. + if (parameter.empty()) + return MODEACTION_DENY; + + ext.set(channel, parameter); } else - { - channel->SetModeParam('k', ""); - } + ext.unset(channel); + + channel->SetMode(this, adding); return MODEACTION_ALLOW; } + +void ModeChannelKey::SerializeParam(Channel* chan, const std::string* key, std::string& out) +{ + out += *key; +} + +ModeAction ModeChannelKey::OnSet(User* source, Channel* chan, std::string& param) +{ + // Dummy function, never called + return MODEACTION_DENY; +} diff --git a/src/modes/cmode_l.cpp b/src/coremods/core_channel/cmode_l.cpp index 001d058bb..e71eb500e 100644 --- a/src/modes/cmode_l.cpp +++ b/src/coremods/core_channel/cmode_l.cpp @@ -20,12 +20,11 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/cmode_l.h" +#include "core_channel.h" -ModeChannelLimit::ModeChannelLimit() : ParamChannelModeHandler(NULL, "limit", 'l') +ModeChannelLimit::ModeChannelLimit(Module* Creator) + : ParamMode<ModeChannelLimit, LocalIntExt>(Creator, "limit", 'l') + , minlimit(0) { } @@ -35,13 +34,17 @@ bool ModeChannelLimit::ResolveModeConflict(std::string &their_param, const std:: return (atoi(their_param.c_str()) < atoi(our_param.c_str())); } -bool ModeChannelLimit::ParamValidate(std::string ¶meter) +ModeAction ModeChannelLimit::OnSet(User* user, Channel* chan, std::string& parameter) { - int limit = atoi(parameter.c_str()); + size_t limit = ConvToNum<size_t>(parameter); + if (limit < minlimit) + return MODEACTION_DENY; - if (limit < 0) - return false; + ext.set(chan, limit); + return MODEACTION_ALLOW; +} - parameter = ConvToStr(limit); - return true; +void ModeChannelLimit::SerializeParam(Channel* chan, intptr_t n, std::string& out) +{ + out += ConvToStr(n); } diff --git a/src/coremods/core_channel/core_channel.cpp b/src/coremods/core_channel/core_channel.cpp new file mode 100644 index 000000000..4e49ba2b4 --- /dev/null +++ b/src/coremods/core_channel/core_channel.cpp @@ -0,0 +1,352 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014-2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_channel.h" +#include "invite.h" +#include "listmode.h" + +namespace +{ +/** Hook that sends a MODE after a JOIN if the user in the JOIN has some modes prefix set. + * This happens e.g. when modules such as operprefix explicitly set prefix modes on the joining + * user, or when a member with prefix modes does a host cycle. + */ +class JoinHook : public ClientProtocol::EventHook +{ + ClientProtocol::Messages::Mode modemsg; + Modes::ChangeList modechangelist; + const User* joininguser; + + public: + /** If true, MODE changes after JOIN will be sourced from the user, rather than the server + */ + bool modefromuser; + + JoinHook(Module* mod) + : ClientProtocol::EventHook(mod, "JOIN") + { + } + + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE + { + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + const Membership& memb = *join.GetMember(); + + modechangelist.clear(); + for (std::string::const_iterator i = memb.modes.begin(); i != memb.modes.end(); ++i) + { + PrefixMode* const pm = ServerInstance->Modes.FindPrefixMode(*i); + if (!pm) + continue; // Shouldn't happen + modechangelist.push_add(pm, memb.user->nick); + } + + if (modechangelist.empty()) + { + // Member got no modes on join + joininguser = NULL; + return; + } + + joininguser = memb.user; + + // Prepare a mode protocol event that we can append to the message list in OnPreEventSend() + modemsg.SetParams(memb.chan, NULL, modechangelist); + if (modefromuser) + modemsg.SetSource(join); + else + modemsg.SetSourceUser(ServerInstance->FakeClient); + } + + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE + { + // If joininguser is NULL then they didn't get any modes on join, skip. + // Also don't show their own modes to them, they get that in the NAMES list not via MODE. + if ((joininguser) && (user != joininguser)) + messagelist.push_back(&modemsg); + return MOD_RES_PASSTHRU; + } +}; + +} + +class CoreModChannel : public Module, public CheckExemption::EventListener +{ + Invite::APIImpl invapi; + CommandInvite cmdinvite; + CommandJoin cmdjoin; + CommandKick cmdkick; + CommandNames cmdnames; + CommandTopic cmdtopic; + Events::ModuleEventProvider evprov; + JoinHook joinhook; + + ModeChannelBan banmode; + SimpleChannelModeHandler inviteonlymode; + ModeChannelKey keymode; + ModeChannelLimit limitmode; + SimpleChannelModeHandler moderatedmode; + SimpleChannelModeHandler noextmsgmode; + ModeChannelOp opmode; + SimpleChannelModeHandler privatemode; + SimpleChannelModeHandler secretmode; + SimpleChannelModeHandler topiclockmode; + ModeChannelVoice voicemode; + + insp::flat_map<std::string, char> exemptions; + + ModResult IsInvited(User* user, Channel* chan) + { + LocalUser* localuser = IS_LOCAL(user); + if ((localuser) && (invapi.IsInvited(localuser, chan))) + return MOD_RES_ALLOW; + return MOD_RES_PASSTHRU; + } + + public: + CoreModChannel() + : CheckExemption::EventListener(this) + , invapi(this) + , cmdinvite(this, invapi) + , cmdjoin(this) + , cmdkick(this) + , cmdnames(this) + , cmdtopic(this) + , evprov(this, "event/channel") + , joinhook(this) + , banmode(this) + , inviteonlymode(this, "inviteonly", 'i') + , keymode(this) + , limitmode(this) + , moderatedmode(this, "moderated", 'm') + , noextmsgmode(this, "noextmsg", 'n') + , opmode(this) + , privatemode(this, "private", 'p') + , secretmode(this, "secret", 's') + , topiclockmode(this, "topiclock", 't') + , voicemode(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* optionstag = ServerInstance->Config->ConfValue("options"); + Implementation events[] = { I_OnCheckKey, I_OnCheckLimit, I_OnCheckChannelBan }; + if (optionstag->getBool("invitebypassmodes", true)) + ServerInstance->Modules.Attach(events, this, sizeof(events)/sizeof(Implementation)); + else + { + for (unsigned int i = 0; i < sizeof(events)/sizeof(Implementation); i++) + ServerInstance->Modules.Detach(events[i], this); + } + + joinhook.modefromuser = optionstag->getBool("cyclehostsfromuser"); + + std::string current; + irc::spacesepstream defaultstream(optionstag->getString("exemptchanops")); + insp::flat_map<std::string, char> exempts; + while (defaultstream.GetToken(current)) + { + std::string::size_type pos = current.find(':'); + if (pos == std::string::npos || (pos + 2) > current.size()) + throw ModuleException("Invalid exemptchanops value '" + current + "' at " + optionstag->getTagLocation()); + + const std::string restriction = current.substr(0, pos); + const char prefix = current[pos + 1]; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Exempting prefix %c from %s", prefix, restriction.c_str()); + exempts[restriction] = prefix; + } + exemptions.swap(exempts); + + ConfigTag* securitytag = ServerInstance->Config->ConfValue("security"); + const std::string announceinvites = securitytag->getString("announceinvites", "dynamic"); + if (stdalgo::string::equalsci(announceinvites, "none")) + cmdinvite.announceinvites = Invite::ANNOUNCE_NONE; + else if (stdalgo::string::equalsci(announceinvites, "all")) + cmdinvite.announceinvites = Invite::ANNOUNCE_ALL; + else if (stdalgo::string::equalsci(announceinvites, "ops")) + cmdinvite.announceinvites = Invite::ANNOUNCE_OPS; + else if (stdalgo::string::equalsci(announceinvites, "dynamic")) + cmdinvite.announceinvites = Invite::ANNOUNCE_DYNAMIC; + else + throw ModuleException(announceinvites + " is an invalid <security:announceinvites> value, at " + securitytag->getTagLocation()); + + // In 2.0 we allowed limits of 0 to be set. This is non-standard behaviour + // and will be removed in the next major release. + limitmode.minlimit = optionstag->getBool("allowzerolimit", true) ? 0 : 1; + + banmode.DoRehash(); + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + tokens["KEYLEN"] = ConvToStr(ModeChannelKey::maxkeylen); + + // Build a map of limits to their mode character. + insp::flat_map<int, std::string> limits; + const ModeParser::ListModeList& listmodes = ServerInstance->Modes->GetListModes(); + for (ModeParser::ListModeList::const_iterator iter = listmodes.begin(); iter != listmodes.end(); ++iter) + { + const unsigned int limit = (*iter)->GetLowerLimit(); + limits[limit].push_back((*iter)->GetModeChar()); + } + + // Generate the MAXLIST token from the limits map. + std::string& buffer = tokens["MAXLIST"]; + for (insp::flat_map<int, std::string>::const_iterator iter = limits.begin(); iter != limits.end(); ++iter) + { + if (!buffer.empty()) + buffer.push_back(','); + + buffer.append(iter->second); + buffer.push_back(':'); + buffer.append(ConvToStr(iter->first)); + } + } + + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string&, std::string&, const std::string& keygiven) CXX11_OVERRIDE + { + if (!chan) + return MOD_RES_PASSTHRU; + + // Check whether the channel key is correct. + const std::string ckey = chan->GetModeParameter(&keymode); + if (!ckey.empty()) + { + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnCheckKey, MOD_RESULT, (user, chan, keygiven)); + if (!MOD_RESULT.check(InspIRCd::TimingSafeCompare(ckey, keygiven))) + { + // If no key provided, or key is not the right one, and can't bypass +k (not invited or option not enabled) + user->WriteNumeric(ERR_BADCHANNELKEY, chan->name, "Cannot join channel (Incorrect channel key)"); + return MOD_RES_DENY; + } + } + + // Check whether the invite only mode is set. + if (chan->IsModeSet(inviteonlymode)) + { + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnCheckInvite, MOD_RESULT, (user, chan)); + if (MOD_RESULT != MOD_RES_ALLOW) + { + user->WriteNumeric(ERR_INVITEONLYCHAN, chan->name, "Cannot join channel (Invite only)"); + return MOD_RES_DENY; + } + } + + // Check whether the limit would be exceeded by this user joining. + if (chan->IsModeSet(limitmode)) + { + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnCheckLimit, MOD_RESULT, (user, chan)); + if (!MOD_RESULT.check(chan->GetUserCounter() < static_cast<size_t>(limitmode.ext.get(chan)))) + { + user->WriteNumeric(ERR_CHANNELISFULL, chan->name, "Cannot join channel (Channel is full)"); + return MOD_RES_DENY; + } + } + + // Everything looks okay. + return MOD_RES_PASSTHRU; + } + + void OnPostJoin(Membership* memb) CXX11_OVERRIDE + { + Channel* const chan = memb->chan; + LocalUser* const localuser = IS_LOCAL(memb->user); + if (localuser) + { + // Remove existing invite, if any + invapi.Remove(localuser, chan); + + if (chan->topic.length()) + Topic::ShowTopic(localuser, chan); + + // Show all members of the channel, including invisible (+i) users + cmdnames.SendNames(localuser, chan, true); + } + } + + ModResult OnCheckKey(User* user, Channel* chan, const std::string& keygiven) CXX11_OVERRIDE + { + // Hook only runs when being invited bypasses +bkl + return IsInvited(user, chan); + } + + ModResult OnCheckChannelBan(User* user, Channel* chan) CXX11_OVERRIDE + { + // Hook only runs when being invited bypasses +bkl + return IsInvited(user, chan); + } + + ModResult OnCheckLimit(User* user, Channel* chan) CXX11_OVERRIDE + { + // Hook only runs when being invited bypasses +bkl + return IsInvited(user, chan); + } + + ModResult OnCheckInvite(User* user, Channel* chan) CXX11_OVERRIDE + { + // Hook always runs + return IsInvited(user, chan); + } + + void OnUserDisconnect(LocalUser* user) CXX11_OVERRIDE + { + invapi.RemoveAll(user); + } + + void OnChannelDelete(Channel* chan) CXX11_OVERRIDE + { + // Make sure the channel won't appear in invite lists from now on, don't wait for cull to unset the ext + invapi.RemoveAll(chan); + } + + ModResult OnCheckExemption(User* user, Channel* chan, const std::string& restriction) CXX11_OVERRIDE + { + if (!exemptions.count(restriction)) + return MOD_RES_PASSTHRU; + + unsigned int mypfx = chan->GetPrefixValue(user); + char minmode = exemptions[restriction]; + + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode(minmode); + if (mh && mypfx >= mh->GetPrefixRank()) + return MOD_RES_ALLOW; + if (mh || minmode == '*') + return MOD_RES_DENY; + return MOD_RES_PASSTHRU; + } + + void Prioritize() CXX11_OVERRIDE + { + ServerInstance->Modules.SetPriority(this, I_OnPostJoin, PRIORITY_FIRST); + ServerInstance->Modules.SetPriority(this, I_OnUserPreJoin, PRIORITY_LAST); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the INVITE, JOIN, KICK, NAMES, and TOPIC commands", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModChannel) diff --git a/src/coremods/core_channel/core_channel.h b/src/coremods/core_channel/core_channel.h new file mode 100644 index 000000000..59a417790 --- /dev/null +++ b/src/coremods/core_channel/core_channel.h @@ -0,0 +1,219 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "inspircd.h" +#include "listmode.h" +#include "modules/exemption.h" + +namespace Topic +{ + void ShowTopic(LocalUser* user, Channel* chan); +} + +namespace Invite +{ + class APIImpl; + + /** Used to indicate who we announce invites to on a channel. */ + enum AnnounceState + { + /** Don't send invite announcements. */ + ANNOUNCE_NONE, + + /** Send invite announcements to all users. */ + ANNOUNCE_ALL, + + /** Send invite announcements to channel operators and higher. */ + ANNOUNCE_OPS, + + /** Send invite announcements to channel half-operators (if available) and higher. */ + ANNOUNCE_DYNAMIC + }; +} + +/** Handle /INVITE. + */ +class CommandInvite : public Command +{ + Invite::APIImpl& invapi; + + public: + Invite::AnnounceState announceinvites; + + /** Constructor for invite. + */ + CommandInvite(Module* parent, Invite::APIImpl& invapiimpl); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /JOIN. + */ +class CommandJoin : public SplitCommand +{ + public: + /** Constructor for join. + */ + CommandJoin(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /TOPIC. + */ +class CommandTopic : public SplitCommand +{ + CheckExemption::EventProvider exemptionprov; + ChanModeReference secretmode; + ChanModeReference topiclockmode; + + public: + /** Constructor for topic. + */ + CommandTopic(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /NAMES. + */ +class CommandNames : public SplitCommand +{ + ChanModeReference secretmode; + ChanModeReference privatemode; + UserModeReference invisiblemode; + + public: + /** Constructor for names. + */ + CommandNames(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; + + /** Spool the NAMES list for a given channel to the given user + * @param user User to spool the NAMES list to + * @param chan Channel whose nicklist to send + * @param show_invisible True to show invisible (+i) members to the user, false to omit them from the list + */ + void SendNames(LocalUser* user, Channel* chan, bool show_invisible); +}; + +/** Handle /KICK. + */ +class CommandKick : public Command +{ + public: + /** Constructor for kick. + */ + CommandKick(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Channel mode +b + */ +class ModeChannelBan : public ListModeBase +{ + public: + ModeChannelBan(Module* Creator) + : ListModeBase(Creator, "ban", 'b', "End of channel ban list", 367, 368, true, "maxbans") + { + } +}; + +/** Channel mode +k + */ +class ModeChannelKey : public ParamMode<ModeChannelKey, LocalStringExt> +{ + public: + static const std::string::size_type maxkeylen; + ModeChannelKey(Module* Creator); + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE; + void SerializeParam(Channel* chan, const std::string* key, std::string& out) ; + ModeAction OnSet(User* source, Channel* chan, std::string& param) CXX11_OVERRIDE; +}; + +/** Channel mode +l + */ +class ModeChannelLimit : public ParamMode<ModeChannelLimit, LocalIntExt> +{ + public: + size_t minlimit; + ModeChannelLimit(Module* Creator); + bool ResolveModeConflict(std::string& their_param, const std::string& our_param, Channel* channel) CXX11_OVERRIDE; + void SerializeParam(Channel* chan, intptr_t n, std::string& out); + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE; +}; + +/** Channel mode +o + */ +class ModeChannelOp : public PrefixMode +{ + public: + ModeChannelOp(Module* Creator) + : PrefixMode(Creator, "op", 'o', OP_VALUE, '@') + { + ranktoset = ranktounset = OP_VALUE; + } +}; + +/** Channel mode +v + */ +class ModeChannelVoice : public PrefixMode +{ + public: + ModeChannelVoice(Module* Creator) + : PrefixMode(Creator, "voice", 'v', VOICE_VALUE, '+') + { + selfremove = false; + ranktoset = ranktounset = HALFOP_VALUE; + } +}; diff --git a/src/coremods/core_channel/invite.cpp b/src/coremods/core_channel/invite.cpp new file mode 100644 index 000000000..7ac662edc --- /dev/null +++ b/src/coremods/core_channel/invite.cpp @@ -0,0 +1,208 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2012, 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "invite.h" + +class InviteExpireTimer : public Timer +{ + Invite::Invite* const inv; + + bool Tick(time_t currtime) CXX11_OVERRIDE; + + public: + InviteExpireTimer(Invite::Invite* invite, time_t timeout); +}; + +static Invite::APIImpl* apiimpl; + +void RemoveInvite(Invite::Invite* inv, bool remove_user, bool remove_chan) +{ + apiimpl->Destruct(inv, remove_user, remove_chan); +} + +void UnserializeInvite(LocalUser* user, const std::string& str) +{ + apiimpl->Unserialize(user, str); +} + +Invite::APIBase::APIBase(Module* parent) + : DataProvider(parent, "core_channel_invite") +{ +} + +Invite::APIImpl::APIImpl(Module* parent) + : APIBase(parent) + , userext(parent, "invite_user") + , chanext(parent, "invite_chan") +{ + apiimpl = this; +} + +void Invite::APIImpl::Destruct(Invite* inv, bool remove_user, bool remove_chan) +{ + Store<LocalUser>* ustore = userext.get(inv->user); + if (ustore) + { + ustore->invites.erase(inv); + if ((remove_user) && (ustore->invites.empty())) + userext.unset(inv->user); + } + + Store<Channel>* cstore = chanext.get(inv->chan); + if (cstore) + { + cstore->invites.erase(inv); + if ((remove_chan) && (cstore->invites.empty())) + chanext.unset(inv->chan); + } + + delete inv; +} + +bool Invite::APIImpl::Remove(LocalUser* user, Channel* chan) +{ + Invite* inv = Find(user, chan); + if (inv) + { + Destruct(inv); + return true; + } + return false; +} + +void Invite::APIImpl::Create(LocalUser* user, Channel* chan, time_t timeout) +{ + if ((timeout != 0) && (ServerInstance->Time() >= timeout)) + // Expired, don't bother + return; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::APIImpl::Create(): user=%s chan=%s timeout=%lu", user->uuid.c_str(), chan->name.c_str(), (unsigned long)timeout); + + Invite* inv = Find(user, chan); + if (inv) + { + // We only ever extend invites, so nothing to do if the existing one is not timed + if (!inv->IsTimed()) + return; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::APIImpl::Create(): changing expiration in %p", (void*) inv); + if (timeout == 0) + { + // Convert timed invite to non-expiring + delete inv->expiretimer; + inv->expiretimer = NULL; + } + else if (inv->expiretimer->GetTrigger() >= ServerInstance->Time() + timeout) + { + // New expiration time is further than the current, extend the expiration + inv->expiretimer->SetInterval(timeout - ServerInstance->Time()); + } + } + else + { + inv = new Invite(user, chan); + if (timeout) + { + inv->expiretimer = new InviteExpireTimer(inv, timeout - ServerInstance->Time()); + ServerInstance->Timers.AddTimer(inv->expiretimer); + } + + userext.get(user, true)->invites.push_front(inv); + chanext.get(chan, true)->invites.push_front(inv); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::APIImpl::Create(): created new Invite %p", (void*) inv); + } +} + +Invite::Invite* Invite::APIImpl::Find(LocalUser* user, Channel* chan) +{ + const List* list = APIImpl::GetList(user); + if (!list) + return NULL; + + for (List::iterator i = list->begin(); i != list->end(); ++i) + { + Invite* inv = *i; + if (inv->chan == chan) + return inv; + } + + return NULL; +} + +const Invite::List* Invite::APIImpl::GetList(LocalUser* user) +{ + Store<LocalUser>* list = userext.get(user); + if (list) + return &list->invites; + return NULL; +} + +void Invite::APIImpl::Unserialize(LocalUser* user, const std::string& value) +{ + irc::spacesepstream ss(value); + for (std::string channame, exptime; (ss.GetToken(channame) && ss.GetToken(exptime)); ) + { + Channel* chan = ServerInstance->FindChan(channame); + if (chan) + Create(user, chan, ConvToInt(exptime)); + } +} + +Invite::Invite::Invite(LocalUser* u, Channel* c) + : user(u) + , chan(c) + , expiretimer(NULL) +{ +} + +Invite::Invite::~Invite() +{ + delete expiretimer; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Invite::~ %p", (void*) this); +} + +void Invite::Invite::Serialize(SerializeFormat format, bool show_chans, std::string& out) +{ + if (show_chans) + out.append(this->chan->name); + else + out.append((format == FORMAT_USER) ? user->nick : user->uuid); + out.push_back(' '); + + if (expiretimer) + out.append(ConvToStr(expiretimer->GetTrigger())); + else + out.push_back('0'); + out.push_back(' '); +} + +InviteExpireTimer::InviteExpireTimer(Invite::Invite* invite, time_t timeout) + : Timer(timeout) + , inv(invite) +{ +} + +bool InviteExpireTimer::Tick(time_t currtime) +{ + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "InviteExpireTimer::Tick(): expired %p", (void*) inv); + apiimpl->Destruct(inv); + return false; +} diff --git a/src/coremods/core_channel/invite.h b/src/coremods/core_channel/invite.h new file mode 100644 index 000000000..19e3861f8 --- /dev/null +++ b/src/coremods/core_channel/invite.h @@ -0,0 +1,127 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "modules/invite.h" + +namespace Invite +{ + template<typename T> + struct Store + { + typedef insp::intrusive_list<Invite, T> List; + + /** List of pending Invites + */ + List invites; + }; + + template<typename T, ExtensionItem::ExtensibleType ExtType> + class ExtItem; + + class APIImpl; +} + +extern void RemoveInvite(Invite::Invite* inv, bool remove_user, bool remove_chan); +extern void UnserializeInvite(LocalUser* user, const std::string& value); + +template<typename T, ExtensionItem::ExtensibleType ExtType> +class Invite::ExtItem : public ExtensionItem +{ + public: + ExtItem(Module* owner, const char* extname) + : ExtensionItem(extname, ExtType, owner) + { + } + + Store<T>* get(Extensible* ext, bool create = false) + { + Store<T>* store = static_cast<Store<T>*>(get_raw(ext)); + if ((create) && (!store)) + { + store = new Store<T>; + set_raw(ext, store); + } + return store; + } + + void unset(Extensible* ext) + { + void* store = unset_raw(ext); + if (store) + free(ext, store); + } + + void free(Extensible* container, void* item) CXX11_OVERRIDE + { + Store<T>* store = static_cast<Store<T>*>(item); + for (typename Store<T>::List::iterator i = store->invites.begin(); i != store->invites.end(); ) + { + Invite* inv = *i; + // Destructing the Invite invalidates the iterator, so move it now + ++i; + RemoveInvite(inv, (ExtType != ExtensionItem::EXT_USER), (ExtType == ExtensionItem::EXT_USER)); + } + + delete store; + } + + std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE + { + if (format == FORMAT_NETWORK) + return std::string(); + + std::string ret; + Store<T>* store = static_cast<Store<T>*>(item); + for (typename insp::intrusive_list<Invite, T>::iterator i = store->invites.begin(); i != store->invites.end(); ++i) + { + Invite* inv = *i; + inv->Serialize(format, (ExtType == ExtensionItem::EXT_USER), ret); + } + if (!ret.empty()) + ret.erase(ret.length()-1); + return ret; + } + + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE + { + if ((ExtType != ExtensionItem::EXT_CHANNEL) && (format != FORMAT_NETWORK)) + UnserializeInvite(static_cast<LocalUser*>(container), value); + } +}; + +class Invite::APIImpl : public APIBase +{ + ExtItem<LocalUser, ExtensionItem::EXT_USER> userext; + ExtItem<Channel, ExtensionItem::EXT_CHANNEL> chanext; + + public: + APIImpl(Module* owner); + + void Create(LocalUser* user, Channel* chan, time_t timeout) CXX11_OVERRIDE; + Invite* Find(LocalUser* user, Channel* chan) CXX11_OVERRIDE; + bool Remove(LocalUser* user, Channel* chan) CXX11_OVERRIDE; + const List* GetList(LocalUser* user) CXX11_OVERRIDE; + + void RemoveAll(LocalUser* user) { userext.unset(user); } + void RemoveAll(Channel* chan) { chanext.unset(chan); } + void Destruct(Invite* inv, bool remove_chan = true, bool remove_user = true); + void Unserialize(LocalUser* user, const std::string& value); +}; diff --git a/src/coremods/core_dns.cpp b/src/coremods/core_dns.cpp new file mode 100644 index 000000000..002686bc2 --- /dev/null +++ b/src/coremods/core_dns.cpp @@ -0,0 +1,850 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Adam <Adam@anope.org> + * Copyright (C) 2003-2013 Anope Team <team@anope.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "inspircd.h" +#include "modules/dns.h" +#include <iostream> +#include <fstream> + +#ifdef _WIN32 +#include <Iphlpapi.h> +#pragma comment(lib, "Iphlpapi.lib") +#endif + +namespace DNS +{ + /** Maximum value of a dns request id, 16 bits wide, 0xFFFF. + */ + const unsigned int MAX_REQUEST_ID = 0xFFFF; +} + +using namespace DNS; + +/** A full packet sent or recieved to/from the nameserver + */ +class Packet : public Query +{ + void PackName(unsigned char* output, unsigned short output_size, unsigned short& pos, const std::string& name) + { + if (pos + name.length() + 2 > output_size) + throw Exception("Unable to pack name"); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Packing name " + name); + + irc::sepstream sep(name, '.'); + std::string token; + + while (sep.GetToken(token)) + { + output[pos++] = token.length(); + memcpy(&output[pos], token.data(), token.length()); + pos += token.length(); + } + + output[pos++] = 0; + } + + std::string UnpackName(const unsigned char* input, unsigned short input_size, unsigned short& pos) + { + std::string name; + unsigned short pos_ptr = pos, lowest_ptr = input_size; + bool compressed = false; + + if (pos_ptr >= input_size) + throw Exception("Unable to unpack name - no input"); + + while (input[pos_ptr] > 0) + { + unsigned short offset = input[pos_ptr]; + + if (offset & POINTER) + { + if ((offset & POINTER) != POINTER) + throw Exception("Unable to unpack name - bogus compression header"); + if (pos_ptr + 1 >= input_size) + throw Exception("Unable to unpack name - bogus compression header"); + + /* Place pos at the second byte of the first (farthest) compression pointer */ + if (compressed == false) + { + ++pos; + compressed = true; + } + + pos_ptr = (offset & LABEL) << 8 | input[pos_ptr + 1]; + + /* Pointers can only go back */ + if (pos_ptr >= lowest_ptr) + throw Exception("Unable to unpack name - bogus compression pointer"); + lowest_ptr = pos_ptr; + } + else + { + if (pos_ptr + offset + 1 >= input_size) + throw Exception("Unable to unpack name - offset too large"); + if (!name.empty()) + name += "."; + for (unsigned i = 1; i <= offset; ++i) + name += input[pos_ptr + i]; + + pos_ptr += offset + 1; + if (compressed == false) + /* Move up pos */ + pos = pos_ptr; + } + } + + /* +1 pos either to one byte after the compression pointer or one byte after the ending \0 */ + ++pos; + + if (name.empty()) + throw Exception("Unable to unpack name - no name"); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unpack name " + name); + + return name; + } + + Question UnpackQuestion(const unsigned char* input, unsigned short input_size, unsigned short& pos) + { + Question q; + + q.name = this->UnpackName(input, input_size, pos); + + if (pos + 4 > input_size) + throw Exception("Unable to unpack question"); + + q.type = static_cast<QueryType>(input[pos] << 8 | input[pos + 1]); + pos += 2; + + // Skip over query class code + pos += 2; + + return q; + } + + ResourceRecord UnpackResourceRecord(const unsigned char* input, unsigned short input_size, unsigned short& pos) + { + ResourceRecord record = static_cast<ResourceRecord>(this->UnpackQuestion(input, input_size, pos)); + + if (pos + 6 > input_size) + throw Exception("Unable to unpack resource record"); + + record.ttl = (input[pos] << 24) | (input[pos + 1] << 16) | (input[pos + 2] << 8) | input[pos + 3]; + pos += 4; + + uint16_t rdlength = input[pos] << 8 | input[pos + 1]; + pos += 2; + + switch (record.type) + { + case QUERY_A: + { + if (pos + 4 > input_size) + throw Exception("Unable to unpack resource record"); + + irc::sockets::sockaddrs addrs; + memset(&addrs, 0, sizeof(addrs)); + + addrs.in4.sin_family = AF_INET; + addrs.in4.sin_addr.s_addr = input[pos] | (input[pos + 1] << 8) | (input[pos + 2] << 16) | (input[pos + 3] << 24); + pos += 4; + + record.rdata = addrs.addr(); + break; + } + case QUERY_AAAA: + { + if (pos + 16 > input_size) + throw Exception("Unable to unpack resource record"); + + irc::sockets::sockaddrs addrs; + memset(&addrs, 0, sizeof(addrs)); + + addrs.in6.sin6_family = AF_INET6; + for (int j = 0; j < 16; ++j) + addrs.in6.sin6_addr.s6_addr[j] = input[pos + j]; + pos += 16; + + record.rdata = addrs.addr(); + + break; + } + case QUERY_CNAME: + case QUERY_PTR: + { + record.rdata = this->UnpackName(input, input_size, pos); + if (!InspIRCd::IsHost(record.rdata)) + throw Exception("Invalid name"); // XXX: Causes the request to time out + + break; + } + case QUERY_TXT: + { + if (pos + rdlength > input_size) + throw Exception("Unable to unpack txt resource record"); + + record.rdata = std::string(reinterpret_cast<const char *>(input + pos), rdlength); + pos += rdlength; + + if (record.rdata.find_first_of("\r\n\0", 0, 3) != std::string::npos) + throw Exception("Invalid character in txt record"); + + break; + } + default: + break; + } + + if (!record.name.empty() && !record.rdata.empty()) + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, record.name + " -> " + record.rdata); + + return record; + } + + public: + static const int POINTER = 0xC0; + static const int LABEL = 0x3F; + static const int HEADER_LENGTH = 12; + + /* ID for this packet */ + RequestId id; + /* Flags on the packet */ + unsigned short flags; + + Packet() : id(0), flags(0) + { + } + + void Fill(const unsigned char* input, const unsigned short len) + { + if (len < HEADER_LENGTH) + throw Exception("Unable to fill packet"); + + unsigned short packet_pos = 0; + + this->id = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + this->flags = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short qdcount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short ancount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short nscount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + unsigned short arcount = (input[packet_pos] << 8) | input[packet_pos + 1]; + packet_pos += 2; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "qdcount: " + ConvToStr(qdcount) + " ancount: " + ConvToStr(ancount) + " nscount: " + ConvToStr(nscount) + " arcount: " + ConvToStr(arcount)); + + if (qdcount != 1) + throw Exception("Question count != 1 in incoming packet"); + + this->question = this->UnpackQuestion(input, len, packet_pos); + + for (unsigned i = 0; i < ancount; ++i) + this->answers.push_back(this->UnpackResourceRecord(input, len, packet_pos)); + } + + unsigned short Pack(unsigned char* output, unsigned short output_size) + { + if (output_size < HEADER_LENGTH) + throw Exception("Unable to pack packet"); + + unsigned short pos = 0; + + output[pos++] = this->id >> 8; + output[pos++] = this->id & 0xFF; + output[pos++] = this->flags >> 8; + output[pos++] = this->flags & 0xFF; + output[pos++] = 0; // Question count, high byte + output[pos++] = 1; // Question count, low byte + output[pos++] = 0; // Answer count, high byte + output[pos++] = 0; // Answer count, low byte + output[pos++] = 0; + output[pos++] = 0; + output[pos++] = 0; + output[pos++] = 0; + + { + Question& q = this->question; + + if (q.type == QUERY_PTR) + { + irc::sockets::sockaddrs ip; + irc::sockets::aptosa(q.name, 0, ip); + + if (q.name.find(':') != std::string::npos) + { + static const char* const hex = "0123456789abcdef"; + char reverse_ip[128]; + unsigned reverse_ip_count = 0; + for (int j = 15; j >= 0; --j) + { + reverse_ip[reverse_ip_count++] = hex[ip.in6.sin6_addr.s6_addr[j] & 0xF]; + reverse_ip[reverse_ip_count++] = '.'; + reverse_ip[reverse_ip_count++] = hex[ip.in6.sin6_addr.s6_addr[j] >> 4]; + reverse_ip[reverse_ip_count++] = '.'; + } + reverse_ip[reverse_ip_count++] = 0; + + q.name = reverse_ip; + q.name += "ip6.arpa"; + } + else + { + unsigned long forward = ip.in4.sin_addr.s_addr; + ip.in4.sin_addr.s_addr = forward << 24 | (forward & 0xFF00) << 8 | (forward & 0xFF0000) >> 8 | forward >> 24; + + q.name = ip.addr() + ".in-addr.arpa"; + } + } + + this->PackName(output, output_size, pos, q.name); + + if (pos + 4 >= output_size) + throw Exception("Unable to pack packet"); + + short s = htons(q.type); + memcpy(&output[pos], &s, 2); + pos += 2; + + // Query class, always IN + output[pos++] = 0; + output[pos++] = 1; + } + + return pos; + } +}; + +class MyManager : public Manager, public Timer, public EventHandler +{ + typedef TR1NS::unordered_map<Question, Query, Question::hash> cache_map; + cache_map cache; + + irc::sockets::sockaddrs myserver; + bool unloading; + + /** Maximum number of entries in cache + */ + static const unsigned int MAX_CACHE_SIZE = 1000; + + static bool IsExpired(const Query& record, time_t now = ServerInstance->Time()) + { + const ResourceRecord& req = record.answers[0]; + return (req.created + static_cast<time_t>(req.ttl) < now); + } + + /** Check the DNS cache to see if request can be handled by a cached result + * @return true if a cached result was found. + */ + bool CheckCache(DNS::Request* req, const DNS::Question& question) + { + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "cache: Checking cache for " + question.name); + + cache_map::iterator it = this->cache.find(question); + if (it == this->cache.end()) + return false; + + Query& record = it->second; + if (IsExpired(record)) + { + this->cache.erase(it); + return false; + } + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: Using cached result for " + question.name); + record.cached = true; + req->OnLookupComplete(&record); + return true; + } + + /** Add a record to the dns cache + * @param r The record + */ + void AddCache(Query& r) + { + if (cache.size() >= MAX_CACHE_SIZE) + cache.clear(); + + // Determine the lowest TTL value and use that as the TTL of the cache entry + unsigned int cachettl = UINT_MAX; + for (std::vector<ResourceRecord>::const_iterator i = r.answers.begin(); i != r.answers.end(); ++i) + { + const ResourceRecord& rr = *i; + if (rr.ttl < cachettl) + cachettl = rr.ttl; + } + + cachettl = std::min(cachettl, (unsigned int)5*60); + ResourceRecord& rr = r.answers.front(); + // Set TTL to what we've determined to be the lowest + rr.ttl = cachettl; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: added cache for " + rr.name + " -> " + rr.rdata + " ttl: " + ConvToStr(rr.ttl)); + this->cache[r.question] = r; + } + + public: + DNS::Request* requests[MAX_REQUEST_ID+1]; + + MyManager(Module* c) : Manager(c), Timer(5*60, true) + , unloading(false) + { + for (unsigned int i = 0; i <= MAX_REQUEST_ID; ++i) + requests[i] = NULL; + ServerInstance->Timers.AddTimer(this); + } + + ~MyManager() + { + // Ensure Process() will fail for new requests + unloading = true; + + for (unsigned int i = 0; i <= MAX_REQUEST_ID; ++i) + { + DNS::Request* request = requests[i]; + if (!request) + continue; + + Query rr(request->question); + rr.error = ERROR_UNKNOWN; + request->OnError(&rr); + + delete request; + } + } + + void Process(DNS::Request* req) CXX11_OVERRIDE + { + if ((unloading) || (req->creator->dying)) + throw Exception("Module is being unloaded"); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Processing request to lookup " + req->question.name + " of type " + ConvToStr(req->question.type) + " to " + this->myserver.addr()); + + /* Create an id */ + unsigned int tries = 0; + int id; + do + { + id = ServerInstance->GenRandomInt(DNS::MAX_REQUEST_ID+1); + + if (++tries == DNS::MAX_REQUEST_ID*5) + { + // If we couldn't find an empty slot this many times, do a sequential scan as a last + // resort. If an empty slot is found that way, go on, otherwise throw an exception + id = -1; + for (unsigned int i = 0; i <= DNS::MAX_REQUEST_ID; i++) + { + if (!this->requests[i]) + { + id = i; + break; + } + } + + if (id == -1) + throw Exception("DNS: All ids are in use"); + + break; + } + } + while (this->requests[id]); + + req->id = id; + this->requests[req->id] = req; + + Packet p; + p.flags = QUERYFLAGS_RD; + p.id = req->id; + p.question = req->question; + + unsigned char buffer[524]; + unsigned short len = p.Pack(buffer, sizeof(buffer)); + + /* Note that calling Pack() above can actually change the contents of p.question.name, if the query is a PTR, + * to contain the value that would be in the DNS cache, which is why this is here. + */ + if (req->use_cache && this->CheckCache(req, p.question)) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Using cached result"); + delete req; + return; + } + + // Update name in the original request so question checking works for PTR queries + req->question.name = p.question.name; + + if (SocketEngine::SendTo(this, buffer, len, 0, this->myserver) != len) + throw Exception("DNS: Unable to send query"); + + // Add timer for timeout + ServerInstance->Timers.AddTimer(req); + } + + void RemoveRequest(DNS::Request* req) CXX11_OVERRIDE + { + if (requests[req->id] == req) + requests[req->id] = NULL; + } + + std::string GetErrorStr(Error e) CXX11_OVERRIDE + { + switch (e) + { + case ERROR_UNLOADED: + return "Module is unloading"; + case ERROR_TIMEDOUT: + return "Request timed out"; + case ERROR_NOT_AN_ANSWER: + case ERROR_NONSTANDARD_QUERY: + case ERROR_FORMAT_ERROR: + case ERROR_MALFORMED: + return "Malformed answer"; + case ERROR_SERVER_FAILURE: + case ERROR_NOT_IMPLEMENTED: + case ERROR_REFUSED: + case ERROR_INVALIDTYPE: + return "Nameserver failure"; + case ERROR_DOMAIN_NOT_FOUND: + case ERROR_NO_RECORDS: + return "Domain not found"; + case ERROR_NONE: + case ERROR_UNKNOWN: + default: + return "Unknown error"; + } + } + + void OnEventHandlerError(int errcode) CXX11_OVERRIDE + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "UDP socket got an error event"); + } + + void OnEventHandlerRead() CXX11_OVERRIDE + { + unsigned char buffer[524]; + irc::sockets::sockaddrs from; + socklen_t x = sizeof(from); + + int length = SocketEngine::RecvFrom(this, buffer, sizeof(buffer), 0, &from.sa, &x); + + if (length < Packet::HEADER_LENGTH) + return; + + if (myserver != from) + { + std::string server1 = from.str(); + std::string server2 = myserver.str(); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Got a result from the wrong server! Bad NAT or DNS forging attempt? '%s' != '%s'", + server1.c_str(), server2.c_str()); + return; + } + + Packet recv_packet; + bool valid = false; + + try + { + recv_packet.Fill(buffer, length); + valid = true; + } + catch (Exception& ex) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, ex.GetReason()); + } + + // recv_packet.id must be filled in here + DNS::Request* request = this->requests[recv_packet.id]; + if (request == NULL) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received an answer for something we didn't request"); + return; + } + + if (request->question != recv_packet.question) + { + // This can happen under high latency, drop it silently, do not fail the request + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received an answer that isn't for a question we asked"); + return; + } + + if (!valid) + { + ServerInstance->stats.DnsBad++; + recv_packet.error = ERROR_MALFORMED; + request->OnError(&recv_packet); + } + else if (recv_packet.flags & QUERYFLAGS_OPCODE) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received a nonstandard query"); + ServerInstance->stats.DnsBad++; + recv_packet.error = ERROR_NONSTANDARD_QUERY; + request->OnError(&recv_packet); + } + else if (!(recv_packet.flags & QUERYFLAGS_QR) || (recv_packet.flags & QUERYFLAGS_RCODE)) + { + Error error = ERROR_UNKNOWN; + + switch (recv_packet.flags & QUERYFLAGS_RCODE) + { + case 1: + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "format error"); + error = ERROR_FORMAT_ERROR; + break; + case 2: + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "server error"); + error = ERROR_SERVER_FAILURE; + break; + case 3: + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "domain not found"); + error = ERROR_DOMAIN_NOT_FOUND; + break; + case 4: + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "not implemented"); + error = ERROR_NOT_IMPLEMENTED; + break; + case 5: + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "refused"); + error = ERROR_REFUSED; + break; + default: + break; + } + + ServerInstance->stats.DnsBad++; + recv_packet.error = error; + request->OnError(&recv_packet); + } + else if (recv_packet.answers.empty()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "No resource records returned"); + ServerInstance->stats.DnsBad++; + recv_packet.error = ERROR_NO_RECORDS; + request->OnError(&recv_packet); + } + else + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Lookup complete for " + request->question.name); + ServerInstance->stats.DnsGood++; + request->OnLookupComplete(&recv_packet); + this->AddCache(recv_packet); + } + + ServerInstance->stats.Dns++; + + /* Request's destructor removes it from the request map */ + delete request; + } + + bool Tick(time_t now) CXX11_OVERRIDE + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "cache: purging DNS cache"); + + for (cache_map::iterator it = this->cache.begin(); it != this->cache.end(); ) + { + const Query& query = it->second; + if (IsExpired(query, now)) + this->cache.erase(it++); + else + ++it; + } + return true; + } + + void Rehash(const std::string& dnsserver, std::string sourceaddr, unsigned int sourceport) + { + if (this->GetFd() > -1) + { + SocketEngine::Shutdown(this, 2); + SocketEngine::Close(this); + + /* Remove expired entries from the cache */ + this->Tick(ServerInstance->Time()); + } + + irc::sockets::aptosa(dnsserver, DNS::PORT, myserver); + + /* Initialize mastersocket */ + int s = socket(myserver.family(), SOCK_DGRAM, 0); + this->SetFd(s); + + /* Have we got a socket? */ + if (this->GetFd() != -1) + { + SocketEngine::SetReuse(s); + SocketEngine::NonBlocking(s); + + irc::sockets::sockaddrs bindto; + if (sourceaddr.empty()) + { + // set a sourceaddr for irc::sockets::aptosa() based on the servers af type + if (myserver.family() == AF_INET) + sourceaddr = "0.0.0.0"; + else if (myserver.family() == AF_INET6) + sourceaddr = "::"; + } + irc::sockets::aptosa(sourceaddr, sourceport, bindto); + + if (SocketEngine::Bind(this->GetFd(), bindto) < 0) + { + /* Failed to bind */ + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Error binding dns socket - hostnames will NOT resolve"); + SocketEngine::Close(this->GetFd()); + this->SetFd(-1); + } + else if (!SocketEngine::AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE)) + { + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Internal error starting DNS - hostnames will NOT resolve."); + SocketEngine::Close(this->GetFd()); + this->SetFd(-1); + } + + if (bindto.family() != myserver.family()) + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Nameserver address family differs from source address family - hostnames might not resolve"); + } + else + { + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Error creating DNS socket - hostnames will NOT resolve"); + } + } +}; + +class ModuleDNS : public Module +{ + MyManager manager; + std::string DNSServer; + std::string SourceIP; + unsigned int SourcePort; + + void FindDNSServer() + { +#ifdef _WIN32 + // attempt to look up their nameserver from the system + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: <dns:server> not defined, attempting to find a working server in the system settings..."); + + PFIXED_INFO pFixedInfo; + DWORD dwBufferSize = sizeof(FIXED_INFO); + pFixedInfo = (PFIXED_INFO) HeapAlloc(GetProcessHeap(), 0, sizeof(FIXED_INFO)); + + if (pFixedInfo) + { + if (GetNetworkParams(pFixedInfo, &dwBufferSize) == ERROR_BUFFER_OVERFLOW) + { + HeapFree(GetProcessHeap(), 0, pFixedInfo); + pFixedInfo = (PFIXED_INFO) HeapAlloc(GetProcessHeap(), 0, dwBufferSize); + } + + if (pFixedInfo) + { + if (GetNetworkParams(pFixedInfo, &dwBufferSize) == NO_ERROR) + DNSServer = pFixedInfo->DnsServerList.IpAddress.String; + + HeapFree(GetProcessHeap(), 0, pFixedInfo); + } + + if (!DNSServer.empty()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "<dns:server> set to '%s' as first active resolver in the system settings.", DNSServer.c_str()); + return; + } + } + + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No viable nameserver found! Defaulting to nameserver '127.0.0.1'!"); +#else + // attempt to look up their nameserver from /etc/resolv.conf + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: <dns:server> not defined, attempting to find working server in /etc/resolv.conf..."); + + std::ifstream resolv("/etc/resolv.conf"); + + while (resolv >> DNSServer) + { + if (DNSServer == "nameserver") + { + resolv >> DNSServer; + if (DNSServer.find_first_not_of("0123456789.") == std::string::npos || DNSServer.find_first_not_of("0123456789ABCDEFabcdef:") == std::string::npos) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "<dns:server> set to '%s' as first resolver in /etc/resolv.conf.",DNSServer.c_str()); + return; + } + } + } + + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!"); +#endif + DNSServer = "127.0.0.1"; + } + + public: + ModuleDNS() : manager(this) + , SourcePort(0) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + std::string oldserver = DNSServer; + const std::string oldip = SourceIP; + const unsigned int oldport = SourcePort; + + ConfigTag* tag = ServerInstance->Config->ConfValue("dns"); + DNSServer = tag->getString("server"); + SourceIP = tag->getString("sourceip"); + SourcePort = tag->getUInt("sourceport", 0, 0, UINT16_MAX); + + if (DNSServer.empty()) + FindDNSServer(); + + if (oldserver != DNSServer || oldip != SourceIP || oldport != SourcePort) + this->manager.Rehash(DNSServer, SourceIP, SourcePort); + } + + void OnUnloadModule(Module* mod) CXX11_OVERRIDE + { + for (unsigned int i = 0; i <= MAX_REQUEST_ID; ++i) + { + DNS::Request* req = this->manager.requests[i]; + if (!req) + continue; + + if (req->creator == mod) + { + Query rr(req->question); + rr.error = ERROR_UNLOADED; + req->OnError(&rr); + + delete req; + } + } + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("DNS support", VF_CORE|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleDNS) + diff --git a/src/coremods/core_hostname_lookup.cpp b/src/coremods/core_hostname_lookup.cpp new file mode 100644 index 000000000..ec93732b1 --- /dev/null +++ b/src/coremods/core_hostname_lookup.cpp @@ -0,0 +1,239 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013-2016 Adam <Adam@anope.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/dns.h" + +namespace +{ + LocalIntExt* dl; + LocalStringExt* ph; +} + +/** Derived from Resolver, and performs user forward/reverse lookups. + */ +class UserResolver : public DNS::Request +{ + /** UUID we are looking up */ + const std::string uuid; + + /** True if the lookup is forward, false if is a reverse lookup + */ + const bool fwd; + + public: + /** Create a resolver. + * @param mgr DNS Manager + * @param me this module + * @param user The user to begin lookup on + * @param to_resolve The IP or host to resolve + * @param qt The query type + */ + UserResolver(DNS::Manager* mgr, Module* me, LocalUser* user, const std::string& to_resolve, DNS::QueryType qt) + : DNS::Request(mgr, me, to_resolve, qt) + , uuid(user->uuid) + , fwd(qt == DNS::QUERY_A || qt == DNS::QUERY_AAAA) + { + } + + /** Called on successful lookup + * if a previous result has already come back. + * @param r The finished query + */ + void OnLookupComplete(const DNS::Query* r) CXX11_OVERRIDE + { + LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid); + if (!bound_user) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Resolution finished for user '%s' who is gone", uuid.c_str()); + return; + } + + const DNS::ResourceRecord* ans_record = r->FindAnswerOfType(this->question.type); + if (ans_record == NULL) + { + OnError(r); + return; + } + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "DNS result for %s: '%s' -> '%s'", uuid.c_str(), ans_record->name.c_str(), ans_record->rdata.c_str()); + + if (!fwd) + { + // first half of resolution is done. We now need to verify that the host matches. + ph->set(bound_user, ans_record->rdata); + + UserResolver* res_forward; + if (bound_user->client_sa.family() == AF_INET6) + { + /* IPV6 forward lookup */ + res_forward = new UserResolver(this->manager, this->creator, bound_user, ans_record->rdata, DNS::QUERY_AAAA); + } + else + { + /* IPV4 lookup */ + res_forward = new UserResolver(this->manager, this->creator, bound_user, ans_record->rdata, DNS::QUERY_A); + } + try + { + this->manager->Process(res_forward); + } + catch (DNS::Exception& e) + { + delete res_forward; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error in resolver: " + e.GetReason()); + + bound_user->WriteNotice("*** There was an internal error resolving your host, using your IP address (" + bound_user->GetIPString() + ") instead."); + dl->set(bound_user, 0); + } + } + else + { + /* Both lookups completed */ + + irc::sockets::sockaddrs* user_ip = &bound_user->client_sa; + bool rev_match = false; + if (user_ip->family() == AF_INET6) + { + struct in6_addr res_bin; + if (inet_pton(AF_INET6, ans_record->rdata.c_str(), &res_bin)) + { + rev_match = !memcmp(&user_ip->in6.sin6_addr, &res_bin, sizeof(res_bin)); + } + } + else + { + struct in_addr res_bin; + if (inet_pton(AF_INET, ans_record->rdata.c_str(), &res_bin)) + { + rev_match = !memcmp(&user_ip->in4.sin_addr, &res_bin, sizeof(res_bin)); + } + } + + dl->set(bound_user, 0); + + if (rev_match) + { + std::string* hostname = ph->get(bound_user); + + if (hostname == NULL) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: User has no hostname attached when doing a forward lookup"); + bound_user->WriteNotice("*** There was an internal error resolving your host, using your IP address (" + bound_user->GetIPString() + ") instead."); + return; + } + else if (hostname->length() <= ServerInstance->Config->Limits.MaxHost) + { + /* Hostnames starting with : are not a good thing (tm) */ + if ((*hostname)[0] == ':') + hostname->insert(0, "0"); + + bound_user->WriteNotice("*** Found your hostname (" + *hostname + (r->cached ? ") -- cached" : ")")); + bound_user->ChangeRealHost(hostname->substr(0, ServerInstance->Config->Limits.MaxHost), true); + } + else + { + bound_user->WriteNotice("*** Your hostname is longer than the maximum of " + ConvToStr(ServerInstance->Config->Limits.MaxHost) + " characters, using your IP address (" + bound_user->GetIPString() + ") instead."); + } + + ph->unset(bound_user); + } + else + { + bound_user->WriteNotice("*** Your hostname does not match up with your IP address. Sorry, using your IP address (" + bound_user->GetIPString() + ") instead."); + } + } + } + + /** Called on failed lookup + * @param query The errored query + */ + void OnError(const DNS::Query* query) CXX11_OVERRIDE + { + LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid); + if (bound_user) + { + bound_user->WriteNotice("*** Could not resolve your hostname: " + this->manager->GetErrorStr(query->error) + "; using your IP address (" + bound_user->GetIPString() + ") instead."); + dl->set(bound_user, 0); + } + } +}; + +class ModuleHostnameLookup : public Module +{ + LocalIntExt dnsLookup; + LocalStringExt ptrHosts; + dynamic_reference<DNS::Manager> DNS; + + public: + ModuleHostnameLookup() + : dnsLookup("dnsLookup", ExtensionItem::EXT_USER, this) + , ptrHosts("ptrHosts", ExtensionItem::EXT_USER, this) + , DNS(this, "DNS") + { + dl = &dnsLookup; + ph = &ptrHosts; + } + + void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE + { + if (!DNS || !user->MyClass->resolvehostnames) + { + user->WriteNotice("*** Skipping host resolution (disabled by server administrator)"); + return; + } + + // Clients can't have a DNS hostname if they aren't connected via IPv4 or IPv6. + if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6) + { + user->WriteNotice("*** Skipping host resolution (connected via a non-IP socket)"); + return; + } + + user->WriteNotice("*** Looking up your hostname..."); + + UserResolver* res_reverse = new UserResolver(*this->DNS, this, user, user->GetIPString(), DNS::QUERY_PTR); + try + { + /* If both the reverse and forward queries are cached, the user will be able to pass DNS completely + * before Process() completes, which is why dnsLookup.set() is here, before Process() + */ + this->dnsLookup.set(user, 1); + this->DNS->Process(res_reverse); + } + catch (DNS::Exception& e) + { + this->dnsLookup.set(user, 0); + delete res_reverse; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error in resolver: " + e.GetReason()); + } + } + + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE + { + return this->dnsLookup.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for DNS lookups on connecting clients", VF_CORE|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHostnameLookup) diff --git a/src/coremods/core_info/cmd_admin.cpp b/src/coremods/core_info/cmd_admin.cpp new file mode 100644 index 000000000..44827555d --- /dev/null +++ b/src/coremods/core_info/cmd_admin.cpp @@ -0,0 +1,43 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_info.h" + +CommandAdmin::CommandAdmin(Module* parent) + : ServerTargetCommand(parent, "ADMIN") +{ + Penalty = 2; + syntax = "[<servername>]"; +} + +/** Handle /ADMIN + */ +CmdResult CommandAdmin::Handle(User* user, const Params& parameters) +{ + if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) + return CMD_SUCCESS; + user->WriteRemoteNumeric(RPL_ADMINME, InspIRCd::Format("Administrative info for %s", ServerInstance->Config->ServerName.c_str())); + if (!AdminName.empty()) + user->WriteRemoteNumeric(RPL_ADMINLOC1, InspIRCd::Format("Name - %s", AdminName.c_str())); + user->WriteRemoteNumeric(RPL_ADMINLOC2, InspIRCd::Format("Nickname - %s", AdminNick.c_str())); + user->WriteRemoteNumeric(RPL_ADMINEMAIL, InspIRCd::Format("E-Mail - %s", AdminEmail.c_str())); + return CMD_SUCCESS; +} diff --git a/src/coremods/core_info/cmd_commands.cpp b/src/coremods/core_info/cmd_commands.cpp new file mode 100644 index 000000000..0a3efdf23 --- /dev/null +++ b/src/coremods/core_info/cmd_commands.cpp @@ -0,0 +1,59 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_info.h" + +enum +{ + // InspIRCd-specific. + RPL_COMMANDS = 700, + RPL_COMMANDSEND = 701 +}; + +CommandCommands::CommandCommands(Module* parent) + : Command(parent, "COMMANDS", 0, 0) +{ + Penalty = 3; +} + +/** Handle /COMMANDS + */ +CmdResult CommandCommands::Handle(User* user, const Params& parameters) +{ + const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands(); + std::vector<std::string> list; + list.reserve(commands.size()); + for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i) + { + // Don't show S2S commands to users + if (i->second->flags_needed == FLAG_SERVERONLY) + continue; + + Module* src = i->second->creator; + list.push_back(InspIRCd::Format("%s %s %d %d", i->second->name.c_str(), src->ModuleSourceFile.c_str(), + i->second->min_params, i->second->Penalty)); + } + std::sort(list.begin(), list.end()); + for(unsigned int i=0; i < list.size(); i++) + user->WriteNumeric(RPL_COMMANDS, list[i]); + user->WriteNumeric(RPL_COMMANDSEND, "End of COMMANDS list"); + return CMD_SUCCESS; +} diff --git a/src/commands/cmd_info.cpp b/src/coremods/core_info/cmd_info.cpp index 76e414b19..54aa7e16e 100644 --- a/src/commands/cmd_info.cpp +++ b/src/coremods/core_info/cmd_info.cpp @@ -3,7 +3,7 @@ * * Copyright (C) 2011 Jackmcbarn <jackmcbarn@jackmcbarn.no-ip.org> * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2007-2015 Robin Burchell <robin+git@viroteck.net> * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> * * This file is part of InspIRCd. InspIRCd is free software: you can @@ -21,46 +21,24 @@ #include "inspircd.h" +#include "core_info.h" -/** Handle /INFO. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandInfo : public Command +CommandInfo::CommandInfo(Module* parent) + : ServerTargetCommand(parent, "INFO") { - public: - /** Constructor for info. - */ - CommandInfo(Module* parent) : Command(parent,"INFO") - { - Penalty = 4; - syntax = "[<servername>]"; - } - - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - if (parameters.size() > 0) - return ROUTE_UNICAST(parameters[0]); - return ROUTE_LOCALONLY; - } -}; + Penalty = 4; + syntax = "[<servername>]"; +} static const char* const lines[] = { " -/\\- \2InspIRCd\2 -\\/-", " November 2002 - Present", " ", "\2Core Developers\2:", - " Craig Edwards, Brain, <brain@inspircd.org>", - " Craig McLure, Craig, <craig@inspircd.org>", - " Robin Burchell, w00t, <w00t@inspircd.org>", + " Attila Molnar, Attila, <attilamolnar@hush.com>", + " Peter Powell, SaberUK, <petpow@saberuk.com>", + " ", + "\2Former Developers\2:", " Oliver Lupton, Om, <om@inspircd.org>", " John Brooks, Special, <special@inspircd.org>", " Dennis Friis, peavey, <peavey@inspircd.org>", @@ -68,14 +46,14 @@ static const char* const lines[] = { " Uli Schlachter, psychon, <psychon@inspircd.org>", " Matt Smith, dz, <dz@inspircd.org>", " Daniel De Graaf, danieldg, <danieldg@inspircd.org>", - " jackmcbarn, <jackmcbarn@inspircd.org>", - " Attila Molnar, Attila, <attilamolnar@hush.com>", " ", - "\2Regular Contributors\2:", - " Adam SaberUK", + "\2Founding Developers\2:", + " Craig Edwards, Brain, <brain@inspircd.org>", + " Craig McLure, Craig, <craig@inspircd.org>", + " Robin Burchell, w00t, <w00t@inspircd.org>", " ", - "\2Other Contributors\2:", - " ChrisTX Shawn Shutter", + "\2Active Contributors\2:", + " Adam", " ", "\2Former Contributors\2:", " dmb Zaba skenmy GreenReaper", @@ -87,9 +65,11 @@ static const char* const lines[] = { " Stskeeps ThaPrince BuildSmart Thunderhacker", " Skip LeaChim Majic MacGyver", " Namegduf Ankit Phoenix Taros", + " jackmcbarn ChrisTX Shawn Shutter", " ", "\2Thanks To\2:", - " searchirc.com irc-junkie.org Brik fraggeln", + " Asmo Brik fraggeln genius3000", + " Sheogorath", " ", " Best experienced with: \2An IRC client\2", NULL @@ -97,17 +77,15 @@ static const char* const lines[] = { /** Handle /INFO */ -CmdResult CommandInfo::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandInfo::Handle(User* user, const Params& parameters) { if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) return CMD_SUCCESS; int i=0; while (lines[i]) - user->SendText(":%s %03d %s :%s", ServerInstance->Config->ServerName.c_str(), RPL_INFO, user->nick.c_str(), lines[i++]); - FOREACH_MOD(I_OnInfo,OnInfo(user)); - user->SendText(":%s %03d %s :End of /INFO list", ServerInstance->Config->ServerName.c_str(), RPL_ENDOFINFO, user->nick.c_str()); + user->WriteRemoteNumeric(RPL_INFO, lines[i++]); + + user->WriteRemoteNumeric(RPL_ENDOFINFO, "End of /INFO list"); return CMD_SUCCESS; } - -COMMAND_INIT(CommandInfo) diff --git a/src/coremods/core_info/cmd_modules.cpp b/src/coremods/core_info/cmd_modules.cpp new file mode 100644 index 000000000..7212e9525 --- /dev/null +++ b/src/coremods/core_info/cmd_modules.cpp @@ -0,0 +1,85 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_info.h" + +enum +{ + // From ircd-ratbox with an InspIRCd-specific format. + RPL_MODLIST = 702, + RPL_ENDOFMODLIST = 703 +}; + +CommandModules::CommandModules(Module* parent) + : ServerTargetCommand(parent, "MODULES") +{ + Penalty = 4; + syntax = "[<servername>]"; +} + +/** Handle /MODULES + */ +CmdResult CommandModules::Handle(User* user, const Params& parameters) +{ + // Don't ask remote servers about their modules unless the local user asking is an oper + // 2.0 asks anyway, so let's handle that the same way + bool for_us = (parameters.empty() || parameters[0] == ServerInstance->Config->ServerName); + if ((!for_us) || (!IS_LOCAL(user))) + { + if (!user->IsOper()) + { + user->WriteNotice("*** You cannot check what modules other servers have loaded."); + return CMD_FAILURE; + } + + // From an oper and not for us, forward + if (!for_us) + return CMD_SUCCESS; + } + + const ModuleManager::ModuleMap& mods = ServerInstance->Modules->GetModules(); + + for (ModuleManager::ModuleMap::const_iterator i = mods.begin(); i != mods.end(); ++i) + { + Module* m = i->second; + Version V = m->GetVersion(); + + if (IS_LOCAL(user) && user->HasPrivPermission("servers/auspex")) + { + std::string flags("VCO"); + size_t pos = 0; + for (int mult = 2; mult <= VF_OPTCOMMON; mult *= 2, ++pos) + if (!(V.Flags & mult)) + flags[pos] = '-'; + + std::string srcrev = m->ModuleDLLManager->GetVersion(); + user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, srcrev.empty() ? "*" : srcrev, flags, V.description); + } + else + { + user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, '*', '*', V.description); + } + } + user->WriteRemoteNumeric(RPL_ENDOFMODLIST, "End of MODULES list"); + + return CMD_SUCCESS; +} diff --git a/src/coremods/core_info/cmd_motd.cpp b/src/coremods/core_info/cmd_motd.cpp new file mode 100644 index 000000000..cc770b157 --- /dev/null +++ b/src/coremods/core_info/cmd_motd.cpp @@ -0,0 +1,63 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_info.h" + +CommandMotd::CommandMotd(Module* parent) + : ServerTargetCommand(parent, "MOTD") +{ + syntax = "[<servername>]"; +} + +/** Handle /MOTD + */ +CmdResult CommandMotd::Handle(User* user, const Params& parameters) +{ + if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) + { + // Give extra penalty if a non-oper queries the /MOTD of a remote server + LocalUser* localuser = IS_LOCAL(user); + if ((localuser) && (!user->IsOper())) + localuser->CommandFloodPenalty += 2000; + return CMD_SUCCESS; + } + + ConfigTag* tag = ServerInstance->Config->EmptyTag; + LocalUser* localuser = IS_LOCAL(user); + if (localuser) + tag = localuser->GetClass()->config; + std::string motd_name = tag->getString("motd", "motd"); + ConfigFileCache::iterator motd = ServerInstance->Config->Files.find(motd_name); + if (motd == ServerInstance->Config->Files.end()) + { + user->WriteRemoteNumeric(ERR_NOMOTD, "Message of the day file is missing."); + return CMD_SUCCESS; + } + + user->WriteRemoteNumeric(RPL_MOTDSTART, InspIRCd::Format("%s message of the day", ServerInstance->Config->ServerName.c_str())); + + for (file_cache::iterator i = motd->second.begin(); i != motd->second.end(); i++) + user->WriteRemoteNumeric(RPL_MOTD, InspIRCd::Format("- %s", i->c_str())); + + user->WriteRemoteNumeric(RPL_ENDOFMOTD, "End of message of the day."); + + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/operquit.cpp b/src/coremods/core_info/cmd_time.cpp index af2e04ebc..73987cf01 100644 --- a/src/modules/m_spanningtree/operquit.cpp +++ b/src/coremods/core_info/cmd_time.cpp @@ -1,7 +1,8 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -18,28 +19,20 @@ #include "inspircd.h" -#include "xline.h" - -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "core_info.h" +CommandTime::CommandTime(Module* parent) + : ServerTargetCommand(parent, "TIME") +{ + syntax = "[<servername>]"; +} -bool TreeSocket::OperQuit(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandTime::Handle(User* user, const Params& parameters) { - if (params.size() < 1) - return true; + if (parameters.size() > 0 && parameters[0] != ServerInstance->Config->ServerName) + return CMD_SUCCESS; - User* u = ServerInstance->FindUUID(prefix); + user->WriteRemoteNumeric(RPL_TIME, ServerInstance->Config->ServerName, InspIRCd::TimeString(ServerInstance->Time())); - if ((u) && (!IS_SERVER(u))) - { - ServerInstance->OperQuit.set(u, params[0]); - params[0] = ":" + params[0]; - Utils->DoOneToAllButSender(prefix,"OPERQUIT",params,prefix); - } - return true; + return CMD_SUCCESS; } - diff --git a/src/modules/spanningtree.h b/src/coremods/core_info/cmd_version.cpp index 212f35ff3..29d96169e 100644 --- a/src/modules/spanningtree.h +++ b/src/coremods/core_info/cmd_version.cpp @@ -2,6 +2,7 @@ * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -17,27 +18,23 @@ */ -#ifndef SPANNINGTREE_H -#define SPANNINGTREE_H +#include "inspircd.h" +#include "core_info.h" -struct AddServerEvent : public Event +CommandVersion::CommandVersion(Module* parent) + : Command(parent, "VERSION", 0, 0) { - const std::string servername; - AddServerEvent(Module* me, const std::string& name) - : Event(me, "new_server"), servername(name) - { - Send(); - } -}; + syntax = "[<servername>]"; +} -struct DelServerEvent : public Event +CmdResult CommandVersion::Handle(User* user, const Params& parameters) { - const std::string servername; - DelServerEvent(Module* me, const std::string& name) - : Event(me, "lost_server"), servername(name) + std::string version = ServerInstance->GetVersionString((user->IsOper())); + user->WriteNumeric(RPL_VERSION, version); + LocalUser *lu = IS_LOCAL(user); + if (lu != NULL) { - Send(); + ServerInstance->ISupport.SendTo(lu); } -}; - -#endif + return CMD_SUCCESS; +} diff --git a/src/coremods/core_info/core_info.cpp b/src/coremods/core_info/core_info.cpp new file mode 100644 index 000000000..2d7a89475 --- /dev/null +++ b/src/coremods/core_info/core_info.cpp @@ -0,0 +1,159 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_info.h" + +enum +{ + // From RFC 2812. + RPL_WELCOME = 1, + RPL_YOURHOST = 2, + RPL_CREATED = 3, + RPL_MYINFO = 4 +}; + +RouteDescriptor ServerTargetCommand::GetRouting(User* user, const Params& parameters) +{ + // Parameter must be a server name, not a nickname or uuid + if ((!parameters.empty()) && (parameters[0].find('.') != std::string::npos)) + return ROUTE_UNICAST(parameters[0]); + return ROUTE_LOCALONLY; +} + +class CoreModInfo : public Module +{ + CommandAdmin cmdadmin; + CommandCommands cmdcommands; + CommandInfo cmdinfo; + CommandModules cmdmodules; + CommandMotd cmdmotd; + CommandTime cmdtime; + CommandVersion cmdversion; + Numeric::Numeric numeric004; + + /** Returns a list of user or channel mode characters. + * Used for constructing the parts of the mode list in the 004 numeric. + * @param mt Controls whether to list user modes or channel modes + * @param needparam Return modes only if they require a parameter to be set + * @return The available mode letters that satisfy the given conditions + */ + static std::string CreateModeList(ModeType mt, bool needparam = false) + { + std::string modestr; + for (unsigned char mode = 'A'; mode <= 'z'; mode++) + { + ModeHandler* mh = ServerInstance->Modes.FindMode(mode, mt); + if ((mh) && ((!needparam) || (mh->NeedsParam(true)))) + modestr.push_back(mode); + } + return modestr; + } + + void OnServiceChange(const ServiceProvider& prov) + { + if (prov.service != SERVICE_MODE) + return; + + std::vector<std::string>& params = numeric004.GetParams(); + params.erase(params.begin()+2, params.end()); + + // Create lists of modes + // 1. User modes + // 2. Channel modes + // 3. Channel modes that require a parameter when set + numeric004.push(CreateModeList(MODETYPE_USER)); + numeric004.push(CreateModeList(MODETYPE_CHANNEL)); + numeric004.push(CreateModeList(MODETYPE_CHANNEL, true)); + } + public: + CoreModInfo() + : cmdadmin(this) + , cmdcommands(this) + , cmdinfo(this) + , cmdmodules(this) + , cmdmotd(this) + , cmdtime(this) + , cmdversion(this) + , numeric004(RPL_MYINFO) + { + numeric004.push(ServerInstance->Config->ServerName); + numeric004.push(INSPIRCD_BRANCH); + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("admin"); + cmdadmin.AdminName = tag->getString("name"); + cmdadmin.AdminEmail = tag->getString("email", "null@example.com"); + cmdadmin.AdminNick = tag->getString("nick", "admin"); + } + + void OnUserConnect(LocalUser* user) CXX11_OVERRIDE + { + user->WriteNumeric(RPL_WELCOME, InspIRCd::Format("Welcome to the %s IRC Network %s", ServerInstance->Config->Network.c_str(), user->GetFullRealHost().c_str())); + user->WriteNumeric(RPL_YOURHOST, InspIRCd::Format("Your host is %s, running version %s", ServerInstance->Config->ServerName.c_str(), INSPIRCD_BRANCH)); + user->WriteNumeric(RPL_CREATED, InspIRCd::TimeString(ServerInstance->startup_time, "This server was created %H:%M:%S %b %d %Y")); + user->WriteNumeric(numeric004); + + ServerInstance->ISupport.SendTo(user); + + /* Trigger MOTD and LUSERS output, give modules a chance too */ + ModResult MOD_RESULT; + std::string command("LUSERS"); + CommandBase::Params parameters; + FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, user, true)); + if (!MOD_RESULT) + ServerInstance->Parser.CallHandler(command, parameters, user); + + MOD_RESULT = MOD_RES_PASSTHRU; + command = "MOTD"; + FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, user, true)); + if (!MOD_RESULT) + ServerInstance->Parser.CallHandler(command, parameters, user); + + if (ServerInstance->Config->RawLog) + { + ClientProtocol::Messages::Privmsg rawlogmsg(ServerInstance->FakeClient, user, "*** Raw I/O logging is enabled on user server. All messages, passwords, and commands are being recorded."); + user->Send(ServerInstance->GetRFCEvents().privmsg, rawlogmsg); + } + } + + void OnServiceAdd(ServiceProvider& service) CXX11_OVERRIDE + { + OnServiceChange(service); + } + + void OnServiceDel(ServiceProvider& service) CXX11_OVERRIDE + { + OnServiceChange(service); + } + + void Prioritize() CXX11_OVERRIDE + { + ServerInstance->Modules.SetPriority(this, I_OnUserConnect, PRIORITY_FIRST); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the ADMIN, COMMANDS, INFO, MODULES, MOTD, TIME and VERSION commands", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModInfo) diff --git a/src/coremods/core_info/core_info.h b/src/coremods/core_info/core_info.h new file mode 100644 index 000000000..127041b46 --- /dev/null +++ b/src/coremods/core_info/core_info.h @@ -0,0 +1,169 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "inspircd.h" + +/** These commands require no parameters, but if there is a parameter it is a server name where the command will be routed to. + */ +class ServerTargetCommand : public Command +{ + public: + ServerTargetCommand(Module* mod, const std::string& Name) + : Command(mod, Name) + { + } + + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /ADMIN. + */ +class CommandAdmin : public ServerTargetCommand +{ + public: + /** Holds the admin's name, for output in + * the /ADMIN command. + */ + std::string AdminName; + + /** Holds the email address of the admin, + * for output in the /ADMIN command. + */ + std::string AdminEmail; + + /** Holds the admin's nickname, for output + * in the /ADMIN command + */ + std::string AdminNick; + + /** Constructor for admin. + */ + CommandAdmin(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /COMMANDS. + */ +class CommandCommands : public Command +{ + public: + /** Constructor for commands. + */ + CommandCommands(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /INFO. + */ +class CommandInfo : public ServerTargetCommand +{ + public: + /** Constructor for info. + */ + CommandInfo(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /MODULES. + */ +class CommandModules : public ServerTargetCommand +{ + public: + /** Constructor for modules. + */ + CommandModules(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /MOTD. + */ +class CommandMotd : public ServerTargetCommand +{ + public: + /** Constructor for motd. + */ + CommandMotd(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /TIME. + */ +class CommandTime : public ServerTargetCommand +{ + public: + /** Constructor for time. + */ + CommandTime(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /VERSION. + */ +class CommandVersion : public Command +{ + public: + /** Constructor for version. + */ + CommandVersion(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; diff --git a/src/coremods/core_ison.cpp b/src/coremods/core_ison.cpp new file mode 100644 index 000000000..3f6b1ac74 --- /dev/null +++ b/src/coremods/core_ison.cpp @@ -0,0 +1,97 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +/** Handle /ISON. + */ +class CommandIson : public SplitCommand +{ + public: + /** Constructor for ison. + */ + CommandIson(Module* parent) + : SplitCommand(parent, "ISON", 1) + { + syntax = "<nick> {nick}"; + } + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; +}; + +class IsonReplyBuilder : public Numeric::Builder<' ', true> +{ + public: + IsonReplyBuilder(LocalUser* user) + : Numeric::Builder<' ', true>(user, RPL_ISON) + { + } + + void AddNick(const std::string& nickname) + { + User* const user = ServerInstance->FindNickOnly(nickname); + if ((user) && (user->registered == REG_ALL)) + Add(user->nick); + } +}; + +/** Handle /ISON + */ +CmdResult CommandIson::HandleLocal(LocalUser* user, const Params& parameters) +{ + IsonReplyBuilder reply(user); + + for (std::vector<std::string>::const_iterator i = parameters.begin(); i != parameters.end()-1; ++i) + { + const std::string& targetstr = *i; + reply.AddNick(targetstr); + } + + // Last parameter can be a space separated list + irc::spacesepstream ss(parameters.back()); + for (std::string token; ss.GetToken(token); ) + reply.AddNick(token); + + reply.Flush(); + return CMD_SUCCESS; +} + +class CoreModIson : public Module +{ + private: + CommandIson cmd; + + public: + CoreModIson() + : cmd(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the ISON command", VF_CORE | VF_VENDOR); + } +}; + +MODULE_INIT(CoreModIson) diff --git a/src/coremods/core_list.cpp b/src/coremods/core_list.cpp new file mode 100644 index 000000000..f03fbbe5e --- /dev/null +++ b/src/coremods/core_list.cpp @@ -0,0 +1,215 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +/** Handle /LIST. + */ +class CommandList : public Command +{ + private: + ChanModeReference secretmode; + ChanModeReference privatemode; + + /** Parses the creation time or topic set time out of a LIST parameter. + * @param value The parameter containing a minute count. + * @return The UNIX time at \p value minutes ago. + */ + time_t ParseMinutes(const std::string& value) + { + time_t minutes = ConvToNum<time_t>(value.c_str() + 2); + if (!minutes) + return 0; + return ServerInstance->Time() - (minutes * 60); + } + + public: + /** Constructor for list. + */ + CommandList(Module* parent) + : Command(parent,"LIST", 0, 0) + , secretmode(creator, "secret") + , privatemode(creator, "private") + { + Penalty = 5; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + + +/** Handle /LIST + */ +CmdResult CommandList::Handle(User* user, const Params& parameters) +{ + // C: Searching based on creation time, via the "C<val" and "C>val" modifiers + // to search for a channel creation time that is lower or higher than val + // respectively. + time_t mincreationtime = 0; + time_t maxcreationtime = 0; + + // M: Searching based on mask. + // N: Searching based on !mask. + bool match_name_topic = false; + bool match_inverted = false; + const char* match = NULL; + + // T: Searching based on topic time, via the "T<val" and "T>val" modifiers to + // search for a topic time that is lower or higher than val respectively. + time_t mintopictime = 0; + time_t maxtopictime = 0; + + // U: Searching based on user count within the channel, via the "<val" and + // ">val" modifiers to search for a channel that has less than or more than + // val users respectively. + size_t minusers = 0; + size_t maxusers = 0; + + if ((parameters.size() == 1) && (!parameters[0].empty())) + { + if (parameters[0][0] == '<') + { + maxusers = ConvToNum<size_t>(parameters[0].c_str() + 1); + } + else if (parameters[0][0] == '>') + { + minusers = ConvToNum<size_t>(parameters[0].c_str() + 1); + } + else if (!parameters[0].compare(0, 2, "C<", 2)) + { + mincreationtime = ParseMinutes(parameters[0]); + } + else if (!parameters[0].compare(0, 2, "C>", 2)) + { + maxcreationtime = ParseMinutes(parameters[0]); + } + else if (!parameters[0].compare(0, 2, "T<", 2)) + { + mintopictime = ParseMinutes(parameters[0]); + } + else if (!parameters[0].compare(0, 2, "T>", 2)) + { + maxtopictime = ParseMinutes(parameters[0]); + } + else + { + // If the glob is prefixed with ! it is inverted. + match = parameters[0].c_str(); + if (match[0] == '!') + { + match_inverted = true; + match += 1; + } + + // Ensure that the user didn't just run "LIST !". + if (match[0]) + match_name_topic = true; + } + } + + const bool has_privs = user->HasPrivPermission("channels/auspex"); + + user->WriteNumeric(RPL_LISTSTART, "Channel", "Users Name"); + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) + { + Channel* const chan = i->second; + + // Check the user count if a search has been specified. + const size_t users = chan->GetUserCounter(); + if ((minusers && users <= minusers) || (maxusers && users >= maxusers)) + continue; + + // Check the creation ts if a search has been specified. + const time_t creationtime = chan->age; + if ((mincreationtime && creationtime <= mincreationtime) || (maxcreationtime && creationtime >= maxcreationtime)) + continue; + + // Check the topic ts if a search has been specified. + const time_t topictime = chan->topicset; + if ((mintopictime && (!topictime || topictime <= mintopictime)) || (maxtopictime && (!topictime || topictime >= maxtopictime))) + continue; + + // Attempt to match a glob pattern. + if (match_name_topic) + { + bool matches = InspIRCd::Match(chan->name, match) || InspIRCd::Match(chan->topic, match); + + // The user specified an match that we did not match. + if (!matches && !match_inverted) + continue; + + // The user specified an inverted match that we did match. + if (matches && match_inverted) + continue; + } + + // if the channel is not private/secret, OR the user is on the channel anyway + bool n = (has_privs || chan->HasUser(user)); + + // If we're not in the channel and +s is set on it, we want to ignore it + if ((n) || (!chan->IsModeSet(secretmode))) + { + if ((!n) && (chan->IsModeSet(privatemode))) + { + // Channel is private (+p) and user is outside/not privileged + user->WriteNumeric(RPL_LIST, '*', users, ""); + } + else + { + /* User is in the channel/privileged, channel is not +s */ + user->WriteNumeric(RPL_LIST, chan->name, users, InspIRCd::Format("[+%s] %s", chan->ChanModes(n), chan->topic.c_str())); + } + } + } + user->WriteNumeric(RPL_LISTEND, "End of channel list."); + + return CMD_SUCCESS; +} + +class CoreModList : public Module +{ + private: + CommandList cmd; + + public: + CoreModList() + : cmd(this) + { + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + tokens["ELIST"] = "CMNTU"; + tokens["SAFELIST"]; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the LIST command", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModList) diff --git a/src/coremods/core_loadmodule.cpp b/src/coremods/core_loadmodule.cpp new file mode 100644 index 000000000..69c6bbcef --- /dev/null +++ b/src/coremods/core_loadmodule.cpp @@ -0,0 +1,125 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +/** Handle /LOADMODULE. + */ +class CommandLoadmodule : public Command +{ + public: + /** Constructor for loadmodule. + */ + CommandLoadmodule ( Module* parent) : Command(parent,"LOADMODULE",1,1) { flags_needed='o'; syntax = "<modulename>"; } + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /LOADMODULE + */ +CmdResult CommandLoadmodule::Handle(User* user, const Params& parameters) +{ + if (ServerInstance->Modules->Load(parameters[0])) + { + ServerInstance->SNO->WriteGlobalSno('a', "NEW MODULE: %s loaded %s",user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "Module successfully loaded."); + return CMD_SUCCESS; + } + else + { + user->WriteNumeric(ERR_CANTLOADMODULE, parameters[0], ServerInstance->Modules->LastError()); + return CMD_FAILURE; + } +} + +/** Handle /UNLOADMODULE. + */ +class CommandUnloadmodule : public Command +{ + public: + /** Constructor for unloadmodule. + */ + CommandUnloadmodule(Module* parent) + : Command(parent,"UNLOADMODULE", 1) + { + flags_needed = 'o'; + syntax = "<modulename>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +CmdResult CommandUnloadmodule::Handle(User* user, const Params& parameters) +{ + if (!ServerInstance->Config->ConfValue("security")->getBool("allowcoreunload") && + InspIRCd::Match(parameters[0], "core_*.so", ascii_case_insensitive_map)) + { + user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], "You cannot unload core commands!"); + return CMD_FAILURE; + } + + Module* m = ServerInstance->Modules->Find(parameters[0]); + if (m == creator) + { + user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], "You cannot unload module loading commands!"); + return CMD_FAILURE; + } + + if (m && ServerInstance->Modules->Unload(m)) + { + ServerInstance->SNO->WriteGlobalSno('a', "MODULE UNLOADED: %s unloaded %s", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(RPL_UNLOADEDMODULE, parameters[0], "Module successfully unloaded."); + } + else + { + user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], (m ? ServerInstance->Modules->LastError() : "No such module")); + return CMD_FAILURE; + } + + return CMD_SUCCESS; +} + +class CoreModLoadModule : public Module +{ + CommandLoadmodule cmdloadmod; + CommandUnloadmodule cmdunloadmod; + + public: + CoreModLoadModule() + : cmdloadmod(this), cmdunloadmod(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the LOADMODULE and UNLOADMODULE commands", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModLoadModule) diff --git a/src/commands/cmd_lusers.cpp b/src/coremods/core_lusers.cpp index 91a718090..39a5dac7f 100644 --- a/src/commands/cmd_lusers.cpp +++ b/src/coremods/core_lusers.cpp @@ -26,10 +26,10 @@ struct LusersCounters unsigned int max_global; unsigned int invisible; - LusersCounters() + LusersCounters(unsigned int inv) : max_local(ServerInstance->Users->LocalUserCount()) , max_global(ServerInstance->Users->RegisteredUserCount()) - , invisible(ServerInstance->Users->ModeCount('i')) + , invisible(inv) { } @@ -45,10 +45,7 @@ struct LusersCounters } }; -/** Handle /LUSERS. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. +/** Handle /LUSERS. */ class CommandLusers : public Command { @@ -60,24 +57,23 @@ class CommandLusers : public Command : Command(parent,"LUSERS",0,0), counters(Counters) { } /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command + * @param parameters The parameters to the command * @param user The user issuing the command * @return A value from CmdResult to indicate command success or failure. */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; }; /** Handle /LUSERS */ -CmdResult CommandLusers::Handle (const std::vector<std::string>&, User *user) +CmdResult CommandLusers::Handle(User* user, const Params& parameters) { unsigned int n_users = ServerInstance->Users->RegisteredUserCount(); - ProtoServerList serverlist; + ProtocolInterface::ServerList serverlist; ServerInstance->PI->GetServerList(serverlist); unsigned int n_serv = serverlist.size(); unsigned int n_local_servs = 0; - for(ProtoServerList::iterator i = serverlist.begin(); i != serverlist.end(); ++i) + for (ProtocolInterface::ServerList::const_iterator i = serverlist.begin(); i != serverlist.end(); ++i) { if (i->parentname == ServerInstance->Config->ServerName) n_local_servs++; @@ -88,19 +84,19 @@ CmdResult CommandLusers::Handle (const std::vector<std::string>&, User *user) counters.UpdateMaxUsers(); - user->WriteNumeric(251, "%s :There are %d users and %d invisible on %d servers",user->nick.c_str(), - n_users - counters.invisible, counters.invisible, n_serv); + user->WriteNumeric(RPL_LUSERCLIENT, InspIRCd::Format("There are %d users and %d invisible on %d servers", + n_users - counters.invisible, counters.invisible, n_serv)); if (ServerInstance->Users->OperCount()) - user->WriteNumeric(252, "%s %d :operator(s) online",user->nick.c_str(),ServerInstance->Users->OperCount()); + user->WriteNumeric(RPL_LUSEROP, ServerInstance->Users.OperCount(), "operator(s) online"); if (ServerInstance->Users->UnregisteredUserCount()) - user->WriteNumeric(253, "%s %d :unknown connections",user->nick.c_str(),ServerInstance->Users->UnregisteredUserCount()); + user->WriteNumeric(RPL_LUSERUNKNOWN, ServerInstance->Users.UnregisteredUserCount(), "unknown connections"); - user->WriteNumeric(254, "%s %ld :channels formed",user->nick.c_str(),ServerInstance->ChannelCount()); - user->WriteNumeric(255, "%s :I have %d clients and %d servers",user->nick.c_str(),ServerInstance->Users->LocalUserCount(),n_local_servs); - user->WriteNumeric(265, "%s :Current Local Users: %d Max: %d", user->nick.c_str(), ServerInstance->Users->LocalUserCount(), counters.max_local); - user->WriteNumeric(266, "%s :Current Global Users: %d Max: %d", user->nick.c_str(), n_users, counters.max_global); + user->WriteNumeric(RPL_LUSERCHANNELS, ServerInstance->GetChans().size(), "channels formed"); + user->WriteNumeric(RPL_LUSERME, InspIRCd::Format("I have %d clients and %d servers", ServerInstance->Users.LocalUserCount(), n_local_servs)); + user->WriteNumeric(RPL_LOCALUSERS, InspIRCd::Format("Current local users: %d Max: %d", ServerInstance->Users.LocalUserCount(), counters.max_local)); + user->WriteNumeric(RPL_GLOBALUSERS, InspIRCd::Format("Current global users: %d Max: %d", n_users, counters.max_global)); return CMD_SUCCESS; } @@ -110,11 +106,11 @@ class InvisibleWatcher : public ModeWatcher unsigned int& invisible; public: InvisibleWatcher(Module* mod, unsigned int& Invisible) - : ModeWatcher(mod, 'i', MODETYPE_USER), invisible(Invisible) + : ModeWatcher(mod, "invisible", MODETYPE_USER), invisible(Invisible) { } - void AfterMode(User* source, User* dest, Channel* channel, const std::string& parameter, bool adding, ModeType type) + void AfterMode(User* source, User* dest, Channel* channel, const std::string& parameter, bool adding) CXX11_OVERRIDE { if (dest->registered != REG_ALL) return; @@ -128,43 +124,47 @@ public: class ModuleLusers : public Module { + UserModeReference invisiblemode; LusersCounters counters; CommandLusers cmd; InvisibleWatcher mw; - public: - ModuleLusers() - : cmd(this, counters), mw(this, counters.invisible) + unsigned int CountInvisible() { + unsigned int c = 0; + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i) + { + User* u = i->second; + if (u->IsModeSet(invisiblemode)) + c++; + } + return c; } - void init() + public: + ModuleLusers() + : invisiblemode(this, "invisible") + , counters(CountInvisible()) + , cmd(this, counters) + , mw(this, counters.invisible) { - ServerInstance->Modules->AddService(cmd); - Implementation events[] = { I_OnPostConnect, I_OnUserQuit }; - ServerInstance->Modules->Attach(events, this, sizeof(events)/sizeof(Implementation)); - ServerInstance->Modes->AddModeWatcher(&mw); } - void OnPostConnect(User* user) + void OnPostConnect(User* user) CXX11_OVERRIDE { counters.UpdateMaxUsers(); - if (user->IsModeSet('i')) + if (user->IsModeSet(invisiblemode)) counters.invisible++; } - void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) + void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE { - if (user->IsModeSet('i')) + if (user->IsModeSet(invisiblemode)) counters.invisible--; } - ~ModuleLusers() - { - ServerInstance->Modes->DelModeWatcher(&mw); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("LUSERS", VF_VENDOR | VF_CORE); } diff --git a/src/coremods/core_oper/cmd_die.cpp b/src/coremods/core_oper/cmd_die.cpp new file mode 100644 index 000000000..b25fe2407 --- /dev/null +++ b/src/coremods/core_oper/cmd_die.cpp @@ -0,0 +1,82 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "exitcodes.h" +#include "core_oper.h" + +CommandDie::CommandDie(Module* parent) + : Command(parent, "DIE", 1) +{ + flags_needed = 'o'; + syntax = "<server>"; +} + +static void QuitAll() +{ + const std::string quitmsg = "Server shutdown"; + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + while (!list.empty()) + ServerInstance->Users.QuitUser(list.front(), quitmsg); +} + +void DieRestart::SendError(const std::string& message) +{ + ClientProtocol::Messages::Error errormsg(message); + ClientProtocol::Event errorevent(ServerInstance->GetRFCEvents().error, errormsg); + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* user = *i; + if (user->registered == REG_ALL) + { + user->WriteNotice(message); + } + else + { + // Unregistered connections receive ERROR, not a NOTICE + user->Send(errorevent); + } + } +} + +/** Handle /DIE + */ +CmdResult CommandDie::Handle(User* user, const Params& parameters) +{ + if (DieRestart::CheckPass(user, parameters[0], "diepass")) + { + { + std::string diebuf = "*** DIE command from " + user->GetFullHost() + ". Terminating."; + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, diebuf); + DieRestart::SendError(diebuf); + } + + QuitAll(); + ServerInstance->Exit(EXIT_STATUS_DIE); + } + else + { + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "Failed /DIE command from %s", user->GetFullRealHost().c_str()); + ServerInstance->SNO->WriteGlobalSno('a', "Failed DIE Command from %s.", user->GetFullRealHost().c_str()); + return CMD_FAILURE; + } + return CMD_SUCCESS; +} diff --git a/src/coremods/core_oper/cmd_kill.cpp b/src/coremods/core_oper/cmd_kill.cpp new file mode 100644 index 000000000..5572e5789 --- /dev/null +++ b/src/coremods/core_oper/cmd_kill.cpp @@ -0,0 +1,154 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_oper.h" + +CommandKill::CommandKill(Module* parent) + : Command(parent, "KILL", 2, 2) + , protoev(parent, name) +{ + flags_needed = 'o'; + syntax = "<nickname> <reason>"; + TRANSLATE2(TR_CUSTOM, TR_CUSTOM); +} + +class KillMessage : public ClientProtocol::Message +{ + public: + KillMessage(ClientProtocol::EventProvider& protoev, User* user, LocalUser* target, const std::string& text) + : ClientProtocol::Message("KILL", NULL) + { + if (ServerInstance->Config->HideKillsServer.empty()) + SetSourceUser(user); + else + SetSource(ServerInstance->Config->HideKillsServer); + + PushParamRef(target->nick); + PushParamRef(text); + } +}; + +/** Handle /KILL + */ +CmdResult CommandKill::Handle(User* user, const Params& parameters) +{ + /* Allow comma seperated lists of users for /KILL (thanks w00t) */ + if (CommandParser::LoopCall(user, this, parameters, 0)) + { + // If we got a colon delimited list of nicks then the handler ran for each nick, + // and KILL commands were broadcast for remote targets. + return CMD_FAILURE; + } + + User* target = ServerInstance->FindNick(parameters[0]); + if (!target) + { + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + return CMD_FAILURE; + } + + /* + * Here, we need to decide how to munge kill messages. Whether to hide killer, what to show opers, etc. + * We only do this when the command is being issued LOCALLY, for remote KILL, we just copy the message we got. + * + * This conditional is so that we only append the "Killed (" prefix ONCE. If killer is remote, then the kill + * just gets processed and passed on, otherwise, if they are local, it gets prefixed. Makes sense :-) -- w00t + */ + + if (IS_LOCAL(user)) + { + /* + * Moved this event inside the IS_LOCAL check also, we don't want half the network killing a user + * and the other half not. This would be a bad thing. ;p -- w00t + */ + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnKill, MOD_RESULT, (user, target, parameters[1])); + + if (MOD_RESULT == MOD_RES_DENY) + return CMD_FAILURE; + + killreason = "Killed ("; + if (!ServerInstance->Config->HideKillsServer.empty()) + { + // hidekills is on, use it + killreason += ServerInstance->Config->HideKillsServer; + } + else + { + // hidekills is off, do nothing + killreason += user->nick; + } + + killreason += " (" + parameters[1] + "))"; + } + else + { + /* Leave it alone, remote server has already formatted it */ + killreason.assign(parameters[1], 0, ServerInstance->Config->Limits.MaxQuit); + } + + if ((!ServerInstance->Config->HideULineKills) || (!user->server->IsULine())) + { + if (IS_LOCAL(user) && IS_LOCAL(target)) + ServerInstance->SNO->WriteGlobalSno('k', "Local kill by %s: %s (%s)", user->nick.c_str(), target->GetFullRealHost().c_str(), parameters[1].c_str()); + else + ServerInstance->SNO->WriteToSnoMask('K', "Remote kill by %s: %s (%s)", user->nick.c_str(), target->GetFullRealHost().c_str(), parameters[1].c_str()); + } + + if (IS_LOCAL(target)) + { + LocalUser* localu = IS_LOCAL(target); + KillMessage msg(protoev, user, localu, killreason); + ClientProtocol::Event killevent(protoev, msg); + localu->Send(killevent); + + this->lastuuid.clear(); + } + else + { + this->lastuuid = target->uuid; + } + + // send the quit out + ServerInstance->Users->QuitUser(target, killreason); + + return CMD_SUCCESS; +} + +RouteDescriptor CommandKill::GetRouting(User* user, const Params& parameters) +{ + // FindNick() doesn't work here because we quit the target user in Handle() which + // removes it from the nicklist, so we check lastuuid: if it's empty then this KILL + // was for a local user, otherwise it contains the uuid of the user who was killed. + if (lastuuid.empty()) + return ROUTE_LOCALONLY; + return ROUTE_BROADCAST; +} + + +void CommandKill::EncodeParameter(std::string& param, unsigned int index) +{ + // Manually translate the nick -> uuid (see above), and also the reason (params[1]) + // because we decorate it if the oper is local and want remote servers to see the + // decorated reason not the original. + param = ((index == 0) ? lastuuid : killreason); +} diff --git a/src/coremods/core_oper/cmd_oper.cpp b/src/coremods/core_oper/cmd_oper.cpp new file mode 100644 index 000000000..8c3c86adc --- /dev/null +++ b/src/coremods/core_oper/cmd_oper.cpp @@ -0,0 +1,71 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_oper.h" + +CommandOper::CommandOper(Module* parent) + : SplitCommand(parent, "OPER", 2, 2) +{ + syntax = "<username> <password>"; +} + +CmdResult CommandOper::HandleLocal(LocalUser* user, const Params& parameters) +{ + bool match_login = false; + bool match_pass = false; + bool match_hosts = false; + + const std::string userHost = user->ident + "@" + user->GetRealHost(); + const std::string userIP = user->ident + "@" + user->GetIPString(); + + ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]); + if (i != ServerInstance->Config->oper_blocks.end()) + { + OperInfo* ifo = i->second; + ConfigTag* tag = ifo->oper_block; + match_login = true; + match_pass = ServerInstance->PassCompare(user, tag->getString("password"), parameters[1], tag->getString("hash")); + match_hosts = InspIRCd::MatchMask(tag->getString("host"), userHost, userIP); + + if (match_pass && match_hosts) + { + /* found this oper's opertype */ + user->Oper(ifo); + return CMD_SUCCESS; + } + } + + std::string fields; + if (!match_login) + fields.append("login "); + if (!match_pass) + fields.append("password "); + if (!match_hosts) + fields.append("hosts"); + + // tell them they suck, and lag them up to help prevent brute-force attacks + user->WriteNumeric(ERR_NOOPERHOST, "Invalid oper credentials"); + user->CommandFloodPenalty += 10000; + + ServerInstance->SNO->WriteGlobalSno('o', "WARNING! Failed oper attempt by %s using login '%s': The following fields do not match: %s", user->GetFullRealHost().c_str(), parameters[0].c_str(), fields.c_str()); + return CMD_FAILURE; +} diff --git a/src/commands/cmd_rehash.cpp b/src/coremods/core_oper/cmd_rehash.cpp index abf0b7876..e234e54b4 100644 --- a/src/commands/cmd_rehash.cpp +++ b/src/coremods/core_oper/cmd_rehash.cpp @@ -20,32 +20,21 @@ #include "inspircd.h" +#include "core_oper.h" -/** Handle /REHASH. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandRehash : public Command +CommandRehash::CommandRehash(Module* parent) + : Command(parent, "REHASH", 0) { - public: - /** Constructor for rehash. - */ - CommandRehash ( Module* parent) : Command(parent,"REHASH",0) { flags_needed = 'o'; Penalty = 2; syntax = "[<servermask>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; + flags_needed = 'o'; + Penalty = 2; + syntax = "[<servermask>]"; +} -CmdResult CommandRehash::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandRehash::Handle(User* user, const Params& parameters) { std::string param = parameters.size() ? parameters[0] : ""; - FOREACH_MOD(I_OnPreRehash,OnPreRehash(user, param)); + FOREACH_MOD(OnPreRehash, (user, param)); if (param.empty()) { @@ -66,34 +55,27 @@ CmdResult CommandRehash::Handle (const std::vector<std::string>& parameters, Use // the leading "-" is optional; remove it if present. if (param[0] == '-') - param = param.substr(1); + param.erase(param.begin()); - FOREACH_MOD(I_OnModuleRehash,OnModuleRehash(user, param)); + FOREACH_MOD(OnModuleRehash, (user, param)); return CMD_SUCCESS; } // Rehash for me. Try to start the rehash thread if (!ServerInstance->ConfigThread) { - std::string m = user->nick + " is rehashing config file " + ServerConfig::CleanFilename(ServerInstance->ConfigFileName.c_str()) + " on " + ServerInstance->Config->ServerName; + std::string m = user->nick + " is rehashing config file " + FileSystem::GetFileName(ServerInstance->ConfigFileName) + " on " + ServerInstance->Config->ServerName; ServerInstance->SNO->WriteGlobalSno('a', m); if (IS_LOCAL(user)) - user->WriteNumeric(RPL_REHASHING, "%s %s :Rehashing", - user->nick.c_str(),ServerConfig::CleanFilename(ServerInstance->ConfigFileName.c_str())); + user->WriteNumeric(RPL_REHASHING, FileSystem::GetFileName(ServerInstance->ConfigFileName), "Rehashing"); else - ServerInstance->PI->SendUserNotice(user, std::string("*** Rehashing server ") + - ServerConfig::CleanFilename(ServerInstance->ConfigFileName.c_str())); + ServerInstance->PI->SendUserNotice(user, "*** Rehashing server " + FileSystem::GetFileName(ServerInstance->ConfigFileName)); /* Don't do anything with the logs here -- logs are restarted * after the config thread has completed. */ - ServerInstance->RehashUsersAndChans(); - FOREACH_MOD(I_OnGarbageCollect, OnGarbageCollect()); - - - ServerInstance->ConfigThread = new ConfigReaderThread(user->uuid); - ServerInstance->Threads->Start(ServerInstance->ConfigThread); + ServerInstance->Rehash(user->uuid); } else { @@ -102,7 +84,7 @@ CmdResult CommandRehash::Handle (const std::vector<std::string>& parameters, Use * XXX, todo: we should find some way to kill runaway rehashes that are blocking, this is a major problem for unrealircd users */ if (IS_LOCAL(user)) - user->WriteServ("NOTICE %s :*** Could not rehash: A rehash is already in progress.", user->nick.c_str()); + user->WriteNotice("*** Could not rehash: A rehash is already in progress."); else ServerInstance->PI->SendUserNotice(user, "*** Could not rehash: A rehash is already in progress."); } @@ -110,6 +92,3 @@ CmdResult CommandRehash::Handle (const std::vector<std::string>& parameters, Use // Always return success so spanningtree forwards an incoming REHASH even if we failed return CMD_SUCCESS; } - - -COMMAND_INIT(CommandRehash) diff --git a/src/commands/cmd_restart.cpp b/src/coremods/core_oper/cmd_restart.cpp index 48f902d1e..936348f95 100644 --- a/src/commands/cmd_restart.cpp +++ b/src/coremods/core_oper/cmd_restart.cpp @@ -19,32 +19,23 @@ #include "inspircd.h" +#include "core_oper.h" -/** Handle /RESTART - */ -class CommandRestart : public Command +CommandRestart::CommandRestart(Module* parent) + : Command(parent, "RESTART", 1, 1) { - public: - /** Constructor for restart. - */ - CommandRestart(Module* parent) : Command(parent,"RESTART",1,1) { flags_needed = 'o'; syntax = "<password>"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; + flags_needed = 'o'; + syntax = "<server>"; +} -CmdResult CommandRestart::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandRestart::Handle(User* user, const Params& parameters) { - ServerInstance->Logs->Log("COMMAND",DEFAULT,"Restart: %s",user->nick.c_str()); - if (!ServerInstance->PassCompare(user, ServerInstance->Config->restartpass, parameters[0].c_str(), ServerInstance->Config->powerhash)) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Restart: %s", user->nick.c_str()); + if (DieRestart::CheckPass(user, parameters[0], "restartpass")) { ServerInstance->SNO->WriteGlobalSno('a', "RESTART command from %s, restarting server.", user->GetFullRealHost().c_str()); - ServerInstance->SendError("Server restarting."); + DieRestart::SendError("Server restarting."); #ifndef _WIN32 /* XXX: This hack sets FD_CLOEXEC on all possible file descriptors, so they're closed if the execvp() below succeeds. @@ -71,6 +62,3 @@ CmdResult CommandRestart::Handle (const std::vector<std::string>& parameters, Us } return CMD_FAILURE; } - - -COMMAND_INIT(CommandRestart) diff --git a/src/coremods/core_oper/core_oper.cpp b/src/coremods/core_oper/core_oper.cpp new file mode 100644 index 000000000..a6b2abd81 --- /dev/null +++ b/src/coremods/core_oper/core_oper.cpp @@ -0,0 +1,55 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_oper.h" + +namespace DieRestart +{ + bool CheckPass(User* user, const std::string& inputpass, const char* confentry) + { + ConfigTag* tag = ServerInstance->Config->ConfValue("power"); + // The hash method for *BOTH* the die and restart passwords + const std::string hash = tag->getString("hash"); + const std::string correctpass = tag->getString(confentry, ServerInstance->Config->ServerName); + return ServerInstance->PassCompare(user, correctpass, inputpass, hash); + } +} + +class CoreModOper : public Module +{ + CommandDie cmddie; + CommandKill cmdkill; + CommandOper cmdoper; + CommandRehash cmdrehash; + CommandRestart cmdrestart; + + public: + CoreModOper() + : cmddie(this), cmdkill(this), cmdoper(this), cmdrehash(this), cmdrestart(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the DIE, KILL, OPER, REHASH, and RESTART commands", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModOper) diff --git a/src/coremods/core_oper/core_oper.h b/src/coremods/core_oper/core_oper.h new file mode 100644 index 000000000..db8c4161c --- /dev/null +++ b/src/coremods/core_oper/core_oper.h @@ -0,0 +1,130 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "inspircd.h" + +namespace DieRestart +{ + /** Checks a die or restart password + * @param user The user executing /DIE or /RESTART + * @param inputpass The password given by the user + * @param confkey The name of the key in the power tag containing the correct password + * @return True if the given password was correct, false if it was not + */ + bool CheckPass(User* user, const std::string& inputpass, const char* confkey); + + /** Send an ERROR to unregistered users and a NOTICE to all registered local users + * @param message Message to send + */ + void SendError(const std::string& message); +} + +/** Handle /DIE. + */ +class CommandDie : public Command +{ + public: + /** Constructor for die. + */ + CommandDie(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /KILL. + */ +class CommandKill : public Command +{ + std::string lastuuid; + std::string killreason; + ClientProtocol::EventProvider protoev; + + public: + /** Constructor for kill. + */ + CommandKill(Module* parent); + + /** Handle command. + * @param user User issuing the command + * @param parameters Parameters to the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; + + void EncodeParameter(std::string& param, unsigned int index) CXX11_OVERRIDE; +}; + +/** Handle /OPER. + */ +class CommandOper : public SplitCommand +{ + public: + /** Constructor for oper. + */ + CommandOper(Module* parent); + + /** Handle command. + * @param user User issuing the command + * @param parameters Parameters to the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /REHASH. + */ +class CommandRehash : public Command +{ + public: + /** Constructor for rehash. + */ + CommandRehash(Module* parent); + + /** Handle command. + * @param user User issuing the command + * @param parameters Parameters to the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /RESTART + */ +class CommandRestart : public Command +{ + public: + /** Constructor for restart. + */ + CommandRestart(Module* parent); + + /** Handle command. + * @param user User issuing the command + * @param parameters Parameters to the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; diff --git a/src/coremods/core_privmsg.cpp b/src/coremods/core_privmsg.cpp new file mode 100644 index 000000000..dae770abe --- /dev/null +++ b/src/coremods/core_privmsg.cpp @@ -0,0 +1,376 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +class MessageDetailsImpl : public MessageDetails +{ +public: + MessageDetailsImpl(MessageType mt, const std::string& msg, const ClientProtocol::TagMap& tags) + : MessageDetails(mt, msg, tags) + { + } + + bool IsCTCP(std::string& name, std::string& body) const CXX11_OVERRIDE + { + if (!this->IsCTCP()) + return false; + + size_t end_of_name = text.find(' ', 2); + size_t end_of_ctcp = *text.rbegin() == '\x1' ? 1 : 0; + if (end_of_name == std::string::npos) + { + // The CTCP only contains a name. + name.assign(text, 1, text.length() - 1 - end_of_ctcp); + body.clear(); + return true; + } + + // The CTCP contains a name and a body. + name.assign(text, 1, end_of_name - 1); + + size_t start_of_body = text.find_first_not_of(' ', end_of_name + 1); + if (start_of_body == std::string::npos) + { + // The CTCP body is provided but empty. + body.clear(); + return true; + } + + // The CTCP body provided was non-empty. + body.assign(text, start_of_body, text.length() - start_of_body - end_of_ctcp); + return true; + } + + bool IsCTCP(std::string& name) const CXX11_OVERRIDE + { + if (!this->IsCTCP()) + return false; + + size_t end_of_name = text.find(' ', 2); + if (end_of_name == std::string::npos) + { + // The CTCP only contains a name. + size_t end_of_ctcp = *text.rbegin() == '\x1' ? 1 : 0; + name.assign(text, 1, text.length() - 1 - end_of_ctcp); + return true; + } + + // The CTCP contains a name and a body. + name.assign(text, 1, end_of_name - 1); + return true; + } + + bool IsCTCP() const CXX11_OVERRIDE + { + // According to draft-oakley-irc-ctcp-02 a valid CTCP must begin with SOH and + // contain at least one octet which is not NUL, SOH, CR, LF, or SPACE. As most + // of these are restricted at the protocol level we only need to check for SOH + // and SPACE. + return (text.length() >= 2) && (text[0] == '\x1') && (text[1] != '\x1') && (text[1] != ' '); + } +}; + +class MessageCommandBase : public Command +{ + ChanModeReference moderatedmode; + ChanModeReference noextmsgmode; + + /** Send a PRIVMSG or NOTICE message to all local users from the given user + * @param user User sending the message + * @param msg The message to send + * @param mt Type of the message (MSG_PRIVMSG or MSG_NOTICE) + * @param tags Message tags to include in the outgoing protocol message + */ + static void SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags); + + public: + MessageCommandBase(Module* parent, MessageType mt) + : Command(parent, ClientProtocol::Messages::Privmsg::CommandStrFromMsgType(mt), 2, 2) + , moderatedmode(parent, "moderated") + , noextmsgmode(parent, "noextmsg") + { + syntax = "<target>{,<target>} <message>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleMessage(User* user, const Params& parameters, MessageType mt); + + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE + { + if (IS_LOCAL(user)) + // This is handled by the OnUserPostMessage hook to split the LoopCall pieces + return ROUTE_LOCALONLY; + else + return ROUTE_MESSAGE(parameters[0]); + } +}; + +void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags) +{ + ClientProtocol::Messages::Privmsg message(ClientProtocol::Messages::Privmsg::nocopy, user, "$*", msg, mt); + message.AddTags(tags); + message.SetSideEffect(true); + ClientProtocol::Event messageevent(ServerInstance->GetRFCEvents().privmsg, message); + + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + { + if ((*i)->registered == REG_ALL) + (*i)->Send(messageevent); + } +} + +CmdResult MessageCommandBase::HandleMessage(User* user, const Params& parameters, MessageType mt) +{ + User *dest; + Channel *chan; + + if (CommandParser::LoopCall(user, this, parameters, 0)) + return CMD_SUCCESS; + + if (parameters[0][0] == '$') + { + if (!user->HasPrivPermission("users/mass-message")) + return CMD_SUCCESS; + + std::string servername(parameters[0], 1); + MessageTarget msgtarget(&servername); + MessageDetailsImpl msgdetails(mt, parameters[1], parameters.GetTags()); + + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails)); + if (MOD_RESULT == MOD_RES_DENY) + { + FOREACH_MOD(OnUserMessageBlocked, (user, msgtarget, msgdetails)); + return CMD_FAILURE; + } + + FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails)); + if (InspIRCd::Match(ServerInstance->Config->ServerName, servername, NULL)) + { + SendAll(user, msgdetails.text, mt, msgdetails.tags_out); + } + FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails)); + return CMD_SUCCESS; + } + + char status = 0; + const char* target = parameters[0].c_str(); + + if (ServerInstance->Modes->FindPrefix(*target)) + { + status = *target; + target++; + } + if (*target == '#') + { + chan = ServerInstance->FindChan(target); + + if (chan) + { + if (IS_LOCAL(user) && chan->GetPrefixValue(user) < VOICE_VALUE) + { + if (chan->IsModeSet(noextmsgmode) && !chan->HasUser(user)) + { + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (no external messages)"); + return CMD_FAILURE; + } + + if (chan->IsModeSet(moderatedmode)) + { + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (+m)"); + return CMD_FAILURE; + } + + if (ServerInstance->Config->RestrictBannedUsers != ServerConfig::BUT_NORMAL) + { + if (chan->IsBanned(user)) + { + if (ServerInstance->Config->RestrictBannedUsers == ServerConfig::BUT_RESTRICT_NOTIFY) + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're banned)"); + return CMD_FAILURE; + } + } + } + + MessageTarget msgtarget(chan, status); + MessageDetailsImpl msgdetails(mt, parameters[1], parameters.GetTags()); + msgdetails.exemptions.insert(user); + + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails)); + if (MOD_RESULT == MOD_RES_DENY) + { + FOREACH_MOD(OnUserMessageBlocked, (user, msgtarget, msgdetails)); + return CMD_FAILURE; + } + + /* Check again, a module may have zapped the input string */ + if (msgdetails.text.empty()) + { + user->WriteNumeric(ERR_NOTEXTTOSEND, "No text to send"); + return CMD_FAILURE; + } + + FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails)); + + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, msgdetails.text, msgdetails.type, msgtarget.status); + privmsg.AddTags(msgdetails.tags_out); + privmsg.SetSideEffect(true); + chan->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, msgtarget.status, msgdetails.exemptions); + + FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails)); + } + else + { + /* channel does not exist */ + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); + return CMD_FAILURE; + } + return CMD_SUCCESS; + } + + const char* destnick = parameters[0].c_str(); + + if (IS_LOCAL(user)) + { + const char* targetserver = strchr(destnick, '@'); + + if (targetserver) + { + std::string nickonly; + + nickonly.assign(destnick, 0, targetserver - destnick); + dest = ServerInstance->FindNickOnly(nickonly); + if (dest && strcasecmp(dest->server->GetName().c_str(), targetserver + 1)) + { + /* Incorrect server for user */ + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + return CMD_FAILURE; + } + } + else + dest = ServerInstance->FindNickOnly(destnick); + } + else + dest = ServerInstance->FindNick(destnick); + + if ((dest) && (dest->registered == REG_ALL)) + { + if (parameters[1].empty()) + { + user->WriteNumeric(ERR_NOTEXTTOSEND, "No text to send"); + return CMD_FAILURE; + } + + if ((dest->IsAway()) && (mt == MSG_PRIVMSG)) + { + /* auto respond with aweh msg */ + user->WriteNumeric(RPL_AWAY, dest->nick, dest->awaymsg); + } + + MessageTarget msgtarget(dest); + MessageDetailsImpl msgdetails(mt, parameters[1], parameters.GetTags()); + + + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails)); + if (MOD_RESULT == MOD_RES_DENY) + { + FOREACH_MOD(OnUserMessageBlocked, (user, msgtarget, msgdetails)); + return CMD_FAILURE; + } + + FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails)); + + LocalUser* const localtarget = IS_LOCAL(dest); + if (localtarget) + { + // direct write, same server + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, localtarget->nick, msgdetails.text, mt); + privmsg.AddTags(msgdetails.tags_out); + privmsg.SetSideEffect(true); + localtarget->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); + } + + FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails)); + } + else + { + /* no such nick/channel */ + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + return CMD_FAILURE; + } + return CMD_SUCCESS; +} + +template<MessageType MT> +class CommandMessage : public MessageCommandBase +{ + public: + CommandMessage(Module* parent) + : MessageCommandBase(parent, MT) + { + } + + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + return HandleMessage(user, parameters, MT); + } +}; + +class ModuleCoreMessage : public Module +{ + CommandMessage<MSG_PRIVMSG> CommandPrivmsg; + CommandMessage<MSG_NOTICE> CommandNotice; + + public: + ModuleCoreMessage() + : CommandPrivmsg(this), CommandNotice(this) + { + } + + void OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE + { + // We only handle the idle times of local users. + LocalUser* luser = IS_LOCAL(user); + if (!luser) + return; + + // We don't update the idle time when a CTCP reply is sent. + if (details.type == MSG_NOTICE && details.IsCTCP()) + return; + + luser->idle_lastmsg = ServerInstance->Time(); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("PRIVMSG, NOTICE", VF_CORE|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleCoreMessage) diff --git a/src/coremods/core_reloadmodule.cpp b/src/coremods/core_reloadmodule.cpp new file mode 100644 index 000000000..ea5d40abd --- /dev/null +++ b/src/coremods/core_reloadmodule.cpp @@ -0,0 +1,785 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "listmode.h" +#include "modules/reload.h" + +static Events::ModuleEventProvider* reloadevprov; +static ClientProtocol::Serializer* dummyserializer; + +class DummySerializer : public ClientProtocol::Serializer +{ + bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE + { + return false; + } + + ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE + { + return ClientProtocol::SerializedMessage(); + } + + public: + DummySerializer(Module* mod) + : ClientProtocol::Serializer(mod, "dummy") + { + } +}; + +class CommandReloadmodule : public Command +{ + Events::ModuleEventProvider evprov; + DummySerializer dummyser; + + public: + /** Constructor for reloadmodule. + */ + CommandReloadmodule(Module* parent) + : Command(parent, "RELOADMODULE", 1) + , evprov(parent, "event/reloadmodule") + , dummyser(parent) + { + reloadevprov = &evprov; + dummyserializer = &dummyser; + flags_needed = 'o'; + syntax = "<modulename>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +namespace ReloadModule +{ + +class DataKeeper +{ + /** Data we save for each mode and extension provided by the module + */ + struct ProviderInfo + { + std::string itemname; + union + { + ModeHandler* mh; + ExtensionItem* extitem; + ClientProtocol::Serializer* serializer; + }; + + ProviderInfo(ModeHandler* mode) + : itemname(mode->name) + , mh(mode) + { + } + + ProviderInfo(ExtensionItem* ei) + : itemname(ei->name) + , extitem(ei) + { + } + + ProviderInfo(ClientProtocol::Serializer* ser) + : itemname(ser->name) + , serializer(ser) + { + } + }; + + struct InstanceData + { + /** Position of the ModeHandler or ExtensionItem that the serialized data belongs to + */ + size_t index; + + /** Serialized data + */ + std::string serialized; + + InstanceData(size_t Index, const std::string& Serialized) + : index(Index) + , serialized(Serialized) + { + } + }; + + struct ModesExts + { + /** Mode data for the object, one entry per mode set by the module being reloaded + */ + std::vector<InstanceData> modelist; + + /** Extensions for the object, one entry per extension set by the module being reloaded + */ + std::vector<InstanceData> extlist; + + bool empty() const { return ((modelist.empty()) && (extlist.empty())); } + + void swap(ModesExts& other) + { + modelist.swap(other.modelist); + extlist.swap(other.extlist); + } + }; + + struct OwnedModesExts : public ModesExts + { + /** User uuid or channel name + */ + std::string owner; + + OwnedModesExts(const std::string& Owner) + : owner(Owner) + { + } + }; + + // Data saved for each channel + struct ChanData : public OwnedModesExts + { + /** Type of data stored for each member who has any affected modes or extensions set + */ + typedef OwnedModesExts MemberData; + + /** List of data (modes and extensions) about each member + */ + std::vector<MemberData> memberdatalist; + + ChanData(Channel* chan) + : OwnedModesExts(chan->name) + { + } + }; + + // Data saved for each user + struct UserData : public OwnedModesExts + { + static const size_t UNUSED_INDEX = (size_t)-1; + size_t serializerindex; + + UserData(User* user, size_t serializeridx) + : OwnedModesExts(user->uuid) + , serializerindex(serializeridx) + { + } + }; + + /** Module being reloaded + */ + Module* mod; + + /** Stores all user and channel modes provided by the module + */ + std::vector<ProviderInfo> handledmodes[2]; + + /** Stores all extensions provided by the module + */ + std::vector<ProviderInfo> handledexts; + + /** Stores all serializers provided by the module + */ + std::vector<ProviderInfo> handledserializers; + + /** Stores all of the module data related to users + */ + std::vector<UserData> userdatalist; + + /** Stores all of the module data related to channels and memberships + */ + std::vector<ChanData> chandatalist; + + /** Data attached by modules + */ + ReloadModule::CustomData moddata; + + void SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdatalist); + void SaveMemberData(Channel* chan, std::vector<ChanData::MemberData>& memberdatalist); + static void SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata); + size_t SaveSerializer(User* user); + + /** Get the index of a ProviderInfo representing the serializer in the handledserializers list. + * If the serializer is not already in the list it is added. + * @param serializer Serializer to get an index to. + * @return Index of the ProviderInfo representing the serializer. + */ + size_t GetSerializerIndex(ClientProtocol::Serializer* serializer); + + void CreateModeList(ModeType modetype); + void DoSaveUsers(); + void DoSaveChans(); + + /** Link previously saved extension names to currently available ExtensionItems + */ + void LinkExtensions(); + + /** Link previously saved mode names to currently available ModeHandlers + * @param modetype Type of the modes to look for + */ + void LinkModes(ModeType modetype); + + /** Link previously saved serializer names to currently available Serializers + */ + void LinkSerializers(); + + void DoRestoreUsers(); + void DoRestoreChans(); + void DoRestoreModules(); + + /** Restore previously saved modes and extensions on an Extensible. + * The extensions are set directly on the extensible, the modes are added into the provided mode change list. + * @param data Data to unserialize from + * @param extensible Object to restore + * @param modetype MODETYPE_USER if the object being restored is a User, MODETYPE_CHANNEL otherwise + * (for Channels and Memberships). + * @param modechange Mode change to populate with the modes + */ + void RestoreObj(const OwnedModesExts& data, Extensible* extensible, ModeType modetype, Modes::ChangeList& modechange); + + /** Restore all previously saved extensions on an Extensible + * @param list List of extensions and their serialized data to restore + * @param extensible Target Extensible + */ + void RestoreExtensions(const std::vector<InstanceData>& list, Extensible* extensible); + + /** Restore all previously saved modes on a User, Channel or Membership + * @param list List of modes to restore + * @param modetype MODETYPE_USER if the object being restored is a User, MODETYPE_CHANNEL otherwise + * @param modechange Mode change to populate with the modes + */ + void RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange); + + /** Restore previously saved serializer on a User. + * Quit the user if the serializer cannot be restored. + * @param serializerindex Saved serializer index to restore. + * @param user User whose serializer to restore. If not local then calling this method is a no-op. + * @return True if the serializer didn't need restoring or was restored successfully. + * False if the serializer should have been restored but the required serializer is unavailable and the user was quit. + */ + bool RestoreSerializer(size_t serializerindex, User* user); + + /** Restore all modes and extensions of all members on a channel + * @param chan Channel whose members are being restored + * @param memberdata Data to restore + * @param modechange Mode change to populate with prefix modes + */ + void RestoreMemberData(Channel* chan, const std::vector<ChanData::MemberData>& memberdatalist, Modes::ChangeList& modechange); + + /** Verify that a service which had its data saved is available and owned by the module that owned it previously + * @param service Service descriptor + * @param type Human-readable type of the service for log messages + */ + void VerifyServiceProvider(const ProviderInfo& service, const char* type); + + public: + /** Save module state + * @param currmod Module whose data to save + */ + void Save(Module* currmod); + + /** Restore module state + * @param newmod Newly loaded instance of the module which had its data saved + */ + void Restore(Module* newmod); + + /** Handle reload failure + */ + void Fail(); +}; + +void DataKeeper::DoSaveUsers() +{ + ModesExts currdata; + + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i) + { + User* const user = i->second; + + // Serialize user modes + for (size_t j = 0; j < handledmodes[MODETYPE_USER].size(); j++) + { + ModeHandler* mh = handledmodes[MODETYPE_USER][j].mh; + if (user->IsModeSet(mh)) + currdata.modelist.push_back(InstanceData(j, mh->GetUserParameter(user))); + } + + // Serialize all extensions attached to the User + SaveExtensions(user, currdata.extlist); + + // Save serializer name if applicable and get an index to it + size_t serializerindex = SaveSerializer(user); + + // Add to list if the user has any modes or extensions set that we are interested in, otherwise we don't + // have to do anything with this user when restoring + if ((!currdata.empty()) || (serializerindex != UserData::UNUSED_INDEX)) + { + userdatalist.push_back(UserData(user, serializerindex)); + userdatalist.back().swap(currdata); + } + } +} + +size_t DataKeeper::GetSerializerIndex(ClientProtocol::Serializer* serializer) +{ + for (size_t i = 0; i < handledserializers.size(); i++) + { + if (handledserializers[i].serializer == serializer) + return i; + } + + handledserializers.push_back(ProviderInfo(serializer)); + return handledserializers.size()-1; +} + +size_t DataKeeper::SaveSerializer(User* user) +{ + LocalUser* const localuser = IS_LOCAL(user); + if ((!localuser) || (!localuser->serializer)) + return UserData::UNUSED_INDEX; + if (localuser->serializer->creator != mod) + return UserData::UNUSED_INDEX; + + const size_t serializerindex = GetSerializerIndex(localuser->serializer); + localuser->serializer = dummyserializer; + return serializerindex; +} + +void DataKeeper::SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdata) +{ + const Extensible::ExtensibleStore& setexts = extensible->GetExtList(); + + // Position of the extension saved in the handledexts list + size_t index = 0; + for (std::vector<ProviderInfo>::const_iterator i = handledexts.begin(); i != handledexts.end(); ++i, index++) + { + ExtensionItem* const item = i->extitem; + Extensible::ExtensibleStore::const_iterator it = setexts.find(item); + if (it == setexts.end()) + continue; + + std::string value = item->serialize(FORMAT_INTERNAL, extensible, it->second); + // If the serialized value is empty the extension won't be saved and restored + if (!value.empty()) + extdata.push_back(InstanceData(index, value)); + } +} + +void DataKeeper::SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata) +{ + const ListModeBase::ModeList* list = lm->GetList(chan); + if (!list) + return; + + for (ListModeBase::ModeList::const_iterator i = list->begin(); i != list->end(); ++i) + { + const ListModeBase::ListItem& listitem = *i; + currdata.modelist.push_back(InstanceData(index, listitem.mask)); + } +} + +void DataKeeper::DoSaveChans() +{ + ModesExts currdata; + std::vector<OwnedModesExts> currmemberdata; + + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) + { + Channel* const chan = i->second; + + // Serialize channel modes + for (size_t j = 0; j < handledmodes[MODETYPE_CHANNEL].size(); j++) + { + ModeHandler* mh = handledmodes[MODETYPE_CHANNEL][j].mh; + ListModeBase* lm = mh->IsListModeBase(); + if (lm) + SaveListModes(chan, lm, j, currdata); + else if (chan->IsModeSet(mh)) + currdata.modelist.push_back(InstanceData(j, chan->GetModeParameter(mh))); + } + + // Serialize all extensions attached to the Channel + SaveExtensions(chan, currdata.extlist); + + // Serialize all extensions attached to and all modes set on all members of the channel + SaveMemberData(chan, currmemberdata); + + // Same logic as in DoSaveUsers() plus we consider the modes and extensions of all members + if ((!currdata.empty()) || (!currmemberdata.empty())) + { + chandatalist.push_back(ChanData(chan)); + chandatalist.back().swap(currdata); + chandatalist.back().memberdatalist.swap(currmemberdata); + } + } +} + +void DataKeeper::SaveMemberData(Channel* chan, std::vector<OwnedModesExts>& memberdatalist) +{ + ModesExts currdata; + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) + { + Membership* const memb = i->second; + + for (size_t j = 0; j < handledmodes[MODETYPE_CHANNEL].size(); j++) + { + ModeHandler* mh = handledmodes[MODETYPE_CHANNEL][j].mh; + const PrefixMode* const pm = mh->IsPrefixMode(); + if ((pm) && (memb->HasMode(pm))) + currdata.modelist.push_back(InstanceData(j, memb->user->uuid)); // Need to pass the user's uuid to the mode parser to set the mode later + } + + SaveExtensions(memb, currdata.extlist); + + // Same logic as in DoSaveUsers() + if (!currdata.empty()) + { + memberdatalist.push_back(OwnedModesExts(memb->user->uuid)); + memberdatalist.back().swap(currdata); + } + } +} + +void DataKeeper::RestoreMemberData(Channel* chan, const std::vector<ChanData::MemberData>& memberdatalist, Modes::ChangeList& modechange) +{ + for (std::vector<ChanData::MemberData>::const_iterator i = memberdatalist.begin(); i != memberdatalist.end(); ++i) + { + const ChanData::MemberData& md = *i; + User* const user = ServerInstance->FindUUID(md.owner); + if (!user) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone (while processing %s)", md.owner.c_str(), chan->name.c_str()); + continue; + } + + Membership* const memb = chan->GetUser(user); + if (!memb) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Member %s is no longer on channel %s", md.owner.c_str(), chan->name.c_str()); + continue; + } + + RestoreObj(md, memb, MODETYPE_CHANNEL, modechange); + } +} + +void DataKeeper::CreateModeList(ModeType modetype) +{ + const ModeParser::ModeHandlerMap& modes = ServerInstance->Modes->GetModes(modetype); + for (ModeParser::ModeHandlerMap::const_iterator i = modes.begin(); i != modes.end(); ++i) + { + ModeHandler* mh = i->second; + if (mh->creator == mod) + handledmodes[modetype].push_back(ProviderInfo(mh)); + } +} + +void DataKeeper::Save(Module* currmod) +{ + this->mod = currmod; + + const ExtensionManager::ExtMap& allexts = ServerInstance->Extensions.GetExts(); + for (ExtensionManager::ExtMap::const_iterator i = allexts.begin(); i != allexts.end(); ++i) + { + ExtensionItem* ext = i->second; + if (ext->creator == mod) + handledexts.push_back(ProviderInfo(ext)); + } + + CreateModeList(MODETYPE_USER); + DoSaveUsers(); + + CreateModeList(MODETYPE_CHANNEL); + DoSaveChans(); + + FOREACH_MOD_CUSTOM(*reloadevprov, ReloadModule::EventListener, OnReloadModuleSave, (mod, this->moddata)); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Saved data about %lu users %lu chans %lu modules", (unsigned long)userdatalist.size(), (unsigned long)chandatalist.size(), (unsigned long)moddata.list.size()); +} + +void DataKeeper::VerifyServiceProvider(const ProviderInfo& service, const char* type) +{ + const ServiceProvider* sp = service.extitem; + if (!sp) + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "%s \"%s\" is no longer available", type, service.itemname.c_str()); + else if (sp->creator != mod) + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "%s \"%s\" is now handled by %s", type, service.itemname.c_str(), (sp->creator ? sp->creator->ModuleSourceFile.c_str() : "<core>")); +} + +void DataKeeper::LinkModes(ModeType modetype) +{ + std::vector<ProviderInfo>& list = handledmodes[modetype]; + for (std::vector<ProviderInfo>::iterator i = list.begin(); i != list.end(); ++i) + { + ProviderInfo& item = *i; + item.mh = ServerInstance->Modes->FindMode(item.itemname, modetype); + VerifyServiceProvider(item, (modetype == MODETYPE_USER ? "User mode" : "Channel mode")); + } +} + +void DataKeeper::LinkExtensions() +{ + for (std::vector<ProviderInfo>::iterator i = handledexts.begin(); i != handledexts.end(); ++i) + { + ProviderInfo& item = *i; + item.extitem = ServerInstance->Extensions.GetItem(item.itemname); + VerifyServiceProvider(item.extitem, "Extension"); + } +} + +void DataKeeper::LinkSerializers() +{ + for (std::vector<ProviderInfo>::iterator i = handledserializers.begin(); i != handledserializers.end(); ++i) + { + ProviderInfo& item = *i; + item.serializer = ServerInstance->Modules.FindDataService<ClientProtocol::Serializer>(item.itemname); + VerifyServiceProvider(item.serializer, "Serializer"); + } +} + +void DataKeeper::Restore(Module* newmod) +{ + this->mod = newmod; + + // Find the new extension items + LinkExtensions(); + LinkModes(MODETYPE_USER); + LinkModes(MODETYPE_CHANNEL); + LinkSerializers(); + + // Restore + DoRestoreUsers(); + DoRestoreChans(); + DoRestoreModules(); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restore finished"); +} + +void DataKeeper::Fail() +{ + this->mod = NULL; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restore failed, notifying modules"); + DoRestoreModules(); +} + +void DataKeeper::RestoreObj(const OwnedModesExts& data, Extensible* extensible, ModeType modetype, Modes::ChangeList& modechange) +{ + RestoreExtensions(data.extlist, extensible); + RestoreModes(data.modelist, modetype, modechange); +} + +void DataKeeper::RestoreExtensions(const std::vector<InstanceData>& list, Extensible* extensible) +{ + for (std::vector<InstanceData>::const_iterator i = list.begin(); i != list.end(); ++i) + { + const InstanceData& id = *i; + handledexts[id.index].extitem->unserialize(FORMAT_INTERNAL, extensible, id.serialized); + } +} + +void DataKeeper::RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange) +{ + for (std::vector<InstanceData>::const_iterator i = list.begin(); i != list.end(); ++i) + { + const InstanceData& id = *i; + modechange.push_add(handledmodes[modetype][id.index].mh, id.serialized); + } +} + +bool DataKeeper::RestoreSerializer(size_t serializerindex, User* user) +{ + if (serializerindex == UserData::UNUSED_INDEX) + return true; + + // The following checks are redundant + LocalUser* const localuser = IS_LOCAL(user); + if (!localuser) + return true; + if (localuser->serializer != dummyserializer) + return true; + + const ProviderInfo& provinfo = handledserializers[serializerindex]; + if (!provinfo.serializer) + { + // Users cannot exist without a serializer + ServerInstance->Users.QuitUser(user, "Serializer lost in reload"); + return false; + } + + localuser->serializer = provinfo.serializer; + return true; +} + +void DataKeeper::DoRestoreUsers() +{ + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring user data"); + Modes::ChangeList modechange; + + for (std::vector<UserData>::const_iterator i = userdatalist.begin(); i != userdatalist.end(); ++i) + { + const UserData& userdata = *i; + User* const user = ServerInstance->FindUUID(userdata.owner); + if (!user) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone", userdata.owner.c_str()); + continue; + } + + // Attempt to restore serializer first, if it fails it's a fatal error and RestoreSerializer() quits them + if (!RestoreSerializer(userdata.serializerindex, user)) + continue; + + RestoreObj(userdata, user, MODETYPE_USER, modechange); + ServerInstance->Modes.Process(ServerInstance->FakeClient, NULL, user, modechange, ModeParser::MODE_LOCALONLY); + modechange.clear(); + } +} + +void DataKeeper::DoRestoreChans() +{ + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring channel data"); + Modes::ChangeList modechange; + + for (std::vector<ChanData>::const_iterator i = chandatalist.begin(); i != chandatalist.end(); ++i) + { + const ChanData& chandata = *i; + Channel* const chan = ServerInstance->FindChan(chandata.owner); + if (!chan) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Channel %s not found", chandata.owner.c_str()); + continue; + } + + RestoreObj(chandata, chan, MODETYPE_CHANNEL, modechange); + // Process the mode change before applying any prefix modes + ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, NULL, modechange, ModeParser::MODE_LOCALONLY); + modechange.clear(); + + // Restore all member data + RestoreMemberData(chan, chandata.memberdatalist, modechange); + ServerInstance->Modes.Process(ServerInstance->FakeClient, chan, NULL, modechange, ModeParser::MODE_LOCALONLY); + modechange.clear(); + } +} + +void DataKeeper::DoRestoreModules() +{ + for (ReloadModule::CustomData::List::iterator i = moddata.list.begin(); i != moddata.list.end(); ++i) + { + ReloadModule::CustomData::Data& data = *i; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Calling module data handler %p", (void*)data.handler); + data.handler->OnReloadModuleRestore(mod, data.data); + } +} + +} // namespace ReloadModule + +class ReloadAction : public ActionBase +{ + Module* const mod; + const std::string uuid; + const std::string passedname; + + public: + ReloadAction(Module* m, const std::string& uid, const std::string& passedmodname) + : mod(m) + , uuid(uid) + , passedname(passedmodname) + { + } + + void Call() CXX11_OVERRIDE + { + ReloadModule::DataKeeper datakeeper; + datakeeper.Save(mod); + + DLLManager* dll = mod->ModuleDLLManager; + std::string name = mod->ModuleSourceFile; + ServerInstance->Modules->DoSafeUnload(mod); + ServerInstance->GlobalCulls.Apply(); + delete dll; + bool result = ServerInstance->Modules->Load(name); + + if (result) + { + Module* newmod = ServerInstance->Modules->Find(name); + datakeeper.Restore(newmod); + } + else + datakeeper.Fail(); + + ServerInstance->SNO->WriteGlobalSno('a', "RELOAD MODULE: %s %ssuccessfully reloaded", passedname.c_str(), result ? "" : "un"); + User* user = ServerInstance->FindUUID(uuid); + if (user) + user->WriteNumeric(RPL_LOADEDMODULE, passedname, InspIRCd::Format("Module %ssuccessfully reloaded.", (result ? "" : "un"))); + + ServerInstance->GlobalCulls.AddItem(this); + } +}; + +CmdResult CommandReloadmodule::Handle(User* user, const Params& parameters) +{ + Module* m = ServerInstance->Modules->Find(parameters[0]); + if (m == creator) + { + user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "You cannot reload core_reloadmodule (unload and load it)"); + return CMD_FAILURE; + } + + if (creator->dying) + return CMD_FAILURE; + + if ((m) && (ServerInstance->Modules.CanUnload(m))) + { + ServerInstance->AtomicActions.AddAction(new ReloadAction(m, user->uuid, parameters[0])); + return CMD_SUCCESS; + } + else + { + user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "Could not find module by that name"); + return CMD_FAILURE; + } +} + +class CoreModReloadmodule : public Module +{ + private: + CommandReloadmodule cmd; + + public: + CoreModReloadmodule() + : cmd(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the RELOADMODULE command", VF_CORE | VF_VENDOR); + } +}; + +MODULE_INIT(CoreModReloadmodule) diff --git a/src/coremods/core_serialize_rfc.cpp b/src/coremods/core_serialize_rfc.cpp new file mode 100644 index 000000000..1ba330146 --- /dev/null +++ b/src/coremods/core_serialize_rfc.cpp @@ -0,0 +1,222 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +class RFCSerializer : public ClientProtocol::Serializer +{ + /** Maximum size of the message tags portion of the message, including the `@` and the trailing space characters. + */ + static const std::string::size_type MAX_MESSAGE_TAG_LENGTH = 512; + + static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line); + + public: + RFCSerializer(Module* mod) + : ClientProtocol::Serializer(mod, "rfc") + { + } + + bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE; + ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE; +}; + +bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) +{ + size_t start = line.find_first_not_of(" "); + if (start == std::string::npos) + { + // Discourage the user from flooding the server. + user->CommandFloodPenalty += 2000; + return false; + } + + ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str()); + + irc::tokenstream tokens(line, start); + std::string token; + + // This will always exist because of the check at the start of the function. + tokens.GetMiddle(token); + if (token[0] == '@') + { + // Line begins with message tags, parse them. + std::string tagval; + irc::sepstream ss(token.substr(1), ';'); + while (ss.GetToken(token)) + { + // Two or more tags with the same key must not be sent, but if a client violates that we accept + // the first occurence of duplicate tags and ignore all later occurences. + // + // Another option is to reject the message entirely but there is no standard way of doing that. + const std::string::size_type p = token.find('='); + if (p != std::string::npos) + { + // Tag has a value + tagval.assign(token, p+1, std::string::npos); + token.erase(p); + } + else + tagval.clear(); + + HandleTag(user, token, tagval, parseoutput.tags); + } + + + // Try to read the prefix or command name. + if (!tokens.GetMiddle(token)) + { + // Discourage the user from flooding the server. + user->CommandFloodPenalty += 2000; + return false; + } + } + + if (token[0] == ':') + { + // If this exists then the client sent a prefix as part of their + // message. Section 2.3 of RFC 1459 technically says we should only + // allow the nick of the client here but in practise everyone just + // ignores it so we will copy them. + + // Try to read the command name. + if (!tokens.GetMiddle(token)) + { + // Discourage the user from flooding the server. + user->CommandFloodPenalty += 2000; + return false; + } + } + + parseoutput.cmd.assign(token); + + // Build the parameter map. We intentionally do not respect the RFC 1459 + // thirteen parameter limit here. + while (tokens.GetTrailing(token)) + parseoutput.params.push_back(token); + + return true; +} + +void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line) +{ + char prefix = '@'; // First tag name is prefixed with a '@' + for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i) + { + if (!tagwl.IsSelected(tags, i)) + continue; + + const std::string::size_type prevsize = line.size(); + line.push_back(prefix); + prefix = ';'; // Remaining tags are prefixed with ';' + line.append(i->first); + const std::string& val = i->second.value; + if (!val.empty()) + { + line.push_back('='); + line.append(val); + } + + // The tags part of the message mustn't grow longer than what is allowed by the spec. If it does, + // remove last tag and stop adding more tags. + // + // One is subtracted from the limit before comparing because there must be a ' ' char after the last tag + // which also counts towards the limit. + if (line.size() > MAX_MESSAGE_TAG_LENGTH-1) + { + line.erase(prevsize); + break; + } + } + + if (!line.empty()) + line.push_back(' '); +} + +ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const +{ + std::string line; + SerializeTags(msg.GetTags(), tagwl, line); + + // Save position for length calculation later + const std::string::size_type rfcmsg_begin = line.size(); + + if (msg.GetSource()) + { + line.push_back(':'); + line.append(*msg.GetSource()); + line.push_back(' '); + } + line.append(msg.GetCommand()); + + const ClientProtocol::Message::ParamList& params = msg.GetParams(); + if (!params.empty()) + { + for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i) + { + const std::string& param = *i; + line.push_back(' '); + line.append(param); + } + + line.append(" :", 2).append(params.back()); + } + + // Truncate if too long + std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2; + if (line.length() - rfcmsg_begin > maxline) + line.erase(rfcmsg_begin + maxline); + + line.append("\r\n", 2); + return line; +} + +class ModuleCoreRFCSerializer : public Module +{ + RFCSerializer rfcserializer; + + public: + ModuleCoreRFCSerializer() + : rfcserializer(this) + { + } + + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE + { + if (type != ExtensionItem::EXT_USER) + return; + + LocalUser* const user = IS_LOCAL(static_cast<User*>(item)); + if ((user) && (user->serializer == &rfcserializer)) + ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading"); + } + + void OnUserInit(LocalUser* user) CXX11_OVERRIDE + { + if (!user->serializer) + user->serializer = &rfcserializer; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleCoreRFCSerializer) diff --git a/src/coremods/core_stats.cpp b/src/coremods/core_stats.cpp new file mode 100644 index 000000000..06602b288 --- /dev/null +++ b/src/coremods/core_stats.cpp @@ -0,0 +1,417 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "xline.h" +#include "modules/stats.h" + +#ifdef _WIN32 +#include <psapi.h> +#pragma comment(lib, "psapi.lib") // For GetProcessMemoryInfo() +#endif + +/** Handle /STATS. + */ +class CommandStats : public Command +{ + Events::ModuleEventProvider statsevprov; + void DoStats(Stats::Context& stats); + + public: + /** Constructor for stats. + */ + CommandStats(Module* Creator) + : Command(Creator, "STATS", 1, 2) + , statsevprov(Creator, "event/stats") + { + allow_empty_last_param = false; + syntax = "<stats-symbol> [<servername>]"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE + { + if ((parameters.size() > 1) && (parameters[1].find('.') != std::string::npos)) + return ROUTE_UNICAST(parameters[1]); + return ROUTE_LOCALONLY; + } +}; + +static void GenerateStatsLl(Stats::Context& stats) +{ + stats.AddRow(211, InspIRCd::Format("nick[ident@%s] sendq cmds_out bytes_out cmds_in bytes_in time_open", (stats.GetSymbol() == 'l' ? "host" : "ip"))); + + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* u = *i; + stats.AddRow(211, u->nick+"["+u->ident+"@"+(stats.GetSymbol() == 'l' ? u->GetDisplayedHost() : u->GetIPString())+"] "+ConvToStr(u->eh.getSendQSize())+" "+ConvToStr(u->cmds_out)+" "+ConvToStr(u->bytes_out)+" "+ConvToStr(u->cmds_in)+" "+ConvToStr(u->bytes_in)+" "+ConvToStr(ServerInstance->Time() - u->signon)); + } +} + +void CommandStats::DoStats(Stats::Context& stats) +{ + User* const user = stats.GetSource(); + const char statschar = stats.GetSymbol(); + + bool isPublic = ServerInstance->Config->UserStats.find(statschar) != std::string::npos; + bool isRemoteOper = IS_REMOTE(user) && (user->IsOper()); + bool isLocalOperWithPrivs = IS_LOCAL(user) && user->HasPrivPermission("servers/auspex"); + + if (!isPublic && !isRemoteOper && !isLocalOperWithPrivs) + { + ServerInstance->SNO->WriteToSnoMask('t', + "%s '%c' denied for %s (%s@%s)", + (IS_LOCAL(user) ? "Stats" : "Remote stats"), + statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str()); + stats.AddRow(481, (std::string("Permission Denied - STATS ") + statschar + " requires the servers/auspex priv.")); + return; + } + + ModResult MOD_RESULT; + FIRST_MOD_RESULT_CUSTOM(statsevprov, Stats::EventListener, OnStats, MOD_RESULT, (stats)); + if (MOD_RESULT == MOD_RES_DENY) + { + stats.AddRow(219, statschar, "End of /STATS report"); + ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)", + (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str()); + return; + } + + switch (statschar) + { + /* stats p (show listening ports) */ + case 'p': + { + for (std::vector<ListenSocket*>::const_iterator i = ServerInstance->ports.begin(); i != ServerInstance->ports.end(); ++i) + { + ListenSocket* ls = *i; + std::string type = ls->bind_tag->getString("type", "clients"); + std::string hook = ls->bind_tag->getString("ssl", "plaintext"); + + stats.AddRow(249, ls->bind_sa.str() + " (" + type + ", " + hook + ")"); + } + } + break; + + /* These stats symbols must be handled by a linking module */ + case 'n': + case 'c': + break; + + case 'i': + { + for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i) + { + ConnectClass* c = *i; + Stats::Row row(215); + row.push("I").push(c->name); + + std::string param; + if (c->type == CC_ALLOW) + param.push_back('+'); + if (c->type == CC_DENY) + param.push_back('-'); + + if (c->type == CC_NAMED) + param.push_back('*'); + else + param.append(c->host); + + row.push(param).push(c->config->getString("port", "*")); + row.push(ConvToStr(c->GetRecvqMax())).push(ConvToStr(c->GetSendqSoftMax())).push(ConvToStr(c->GetSendqHardMax())).push(ConvToStr(c->GetCommandRate())); + + param = ConvToStr(c->GetPenaltyThreshold()); + if (c->fakelag) + param.push_back('*'); + row.push(param); + + stats.AddRow(row); + } + } + break; + + case 'Y': + { + int idx = 0; + for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++) + { + ConnectClass* c = *i; + stats.AddRow(215, 'i', "NOMATCH", '*', c->GetHost(), (c->limit ? c->limit : SocketEngine::GetMaxFds()), idx, ServerInstance->Config->ServerName, '*'); + stats.AddRow(218, 'Y', idx, c->GetPingTime(), '0', c->GetSendqHardMax(), ConvToStr(c->GetRecvqMax())+" "+ConvToStr(c->GetRegTimeout())); + idx++; + } + } + break; + + case 'P': + { + unsigned int idx = 0; + const UserManager::OperList& opers = ServerInstance->Users->all_opers; + for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i) + { + User* oper = *i; + if (!oper->server->IsULine()) + { + LocalUser* lu = IS_LOCAL(oper); + stats.AddRow(249, oper->nick + " (" + oper->ident + "@" + oper->GetDisplayedHost() + ") Idle: " + + (lu ? ConvToStr(ServerInstance->Time() - lu->idle_lastmsg) + " secs" : "unavailable")); + idx++; + } + } + stats.AddRow(249, ConvToStr(idx)+" OPER(s)"); + } + break; + + case 'k': + ServerInstance->XLines->InvokeStats("K",216,stats); + break; + case 'g': + ServerInstance->XLines->InvokeStats("G",223,stats); + break; + case 'q': + ServerInstance->XLines->InvokeStats("Q",217,stats); + break; + case 'Z': + ServerInstance->XLines->InvokeStats("Z",223,stats); + break; + case 'e': + ServerInstance->XLines->InvokeStats("E",223,stats); + break; + case 'E': + { + const SocketEngine::Statistics& sestats = SocketEngine::GetStats(); + stats.AddRow(249, "Total events: "+ConvToStr(sestats.TotalEvents)); + stats.AddRow(249, "Read events: "+ConvToStr(sestats.ReadEvents)); + stats.AddRow(249, "Write events: "+ConvToStr(sestats.WriteEvents)); + stats.AddRow(249, "Error events: "+ConvToStr(sestats.ErrorEvents)); + break; + } + + /* stats m (list number of times each command has been used, plus bytecount) */ + case 'm': + { + const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands(); + for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i) + { + if (i->second->use_count) + { + /* RPL_STATSCOMMANDS */ + stats.AddRow(212, i->second->name, i->second->use_count); + } + } + } + break; + + /* stats z (debug and memory info) */ + case 'z': + { + stats.AddRow(249, "Users: "+ConvToStr(ServerInstance->Users->GetUsers().size())); + stats.AddRow(249, "Channels: "+ConvToStr(ServerInstance->GetChans().size())); + stats.AddRow(249, "Commands: "+ConvToStr(ServerInstance->Parser.GetCommands().size())); + + float kbitpersec_in, kbitpersec_out, kbitpersec_total; + SocketEngine::GetStats().GetBandwidth(kbitpersec_in, kbitpersec_out, kbitpersec_total); + + stats.AddRow(249, InspIRCd::Format("Bandwidth total: %03.5f kilobits/sec", kbitpersec_total)); + stats.AddRow(249, InspIRCd::Format("Bandwidth out: %03.5f kilobits/sec", kbitpersec_out)); + stats.AddRow(249, InspIRCd::Format("Bandwidth in: %03.5f kilobits/sec", kbitpersec_in)); + +#ifndef _WIN32 + /* Moved this down here so all the not-windows stuff (look w00tie, I didn't say win32!) is in one ifndef. + * Also cuts out some identical code in both branches of the ifndef. -- Om + */ + rusage R; + + /* Not sure why we were doing '0' with a RUSAGE_SELF comment rather than just using RUSAGE_SELF -- Om */ + if (!getrusage(RUSAGE_SELF,&R)) /* RUSAGE_SELF */ + { +#ifndef __HAIKU__ + stats.AddRow(249, "Total allocation: "+ConvToStr(R.ru_maxrss)+"K"); + stats.AddRow(249, "Signals: "+ConvToStr(R.ru_nsignals)); + stats.AddRow(249, "Page faults: "+ConvToStr(R.ru_majflt)); + stats.AddRow(249, "Swaps: "+ConvToStr(R.ru_nswap)); + stats.AddRow(249, "Context Switches: Voluntary; "+ConvToStr(R.ru_nvcsw)+" Involuntary; "+ConvToStr(R.ru_nivcsw)); +#endif + float n_elapsed = (ServerInstance->Time() - ServerInstance->stats.LastSampled.tv_sec) * 1000000 + + (ServerInstance->Time_ns() - ServerInstance->stats.LastSampled.tv_nsec) / 1000; + float n_eaten = ((R.ru_utime.tv_sec - ServerInstance->stats.LastCPU.tv_sec) * 1000000 + R.ru_utime.tv_usec - ServerInstance->stats.LastCPU.tv_usec); + float per = (n_eaten / n_elapsed) * 100; + + stats.AddRow(249, InspIRCd::Format("CPU Use (now): %03.5f%%", per)); + + n_elapsed = ServerInstance->Time() - ServerInstance->startup_time; + n_eaten = (float)R.ru_utime.tv_sec + R.ru_utime.tv_usec / 100000.0; + per = (n_eaten / n_elapsed) * 100; + + stats.AddRow(249, InspIRCd::Format("CPU Use (total): %03.5f%%", per)); + } +#else + PROCESS_MEMORY_COUNTERS MemCounters; + if (GetProcessMemoryInfo(GetCurrentProcess(), &MemCounters, sizeof(MemCounters))) + { + stats.AddRow(249, "Total allocation: "+ConvToStr((MemCounters.WorkingSetSize + MemCounters.PagefileUsage) / 1024)+"K"); + stats.AddRow(249, "Pagefile usage: "+ConvToStr(MemCounters.PagefileUsage / 1024)+"K"); + stats.AddRow(249, "Page faults: "+ConvToStr(MemCounters.PageFaultCount)); + } + + FILETIME CreationTime; + FILETIME ExitTime; + FILETIME KernelTime; + FILETIME UserTime; + LARGE_INTEGER ThisSample; + if(GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime) && + QueryPerformanceCounter(&ThisSample)) + { + KernelTime.dwHighDateTime += UserTime.dwHighDateTime; + KernelTime.dwLowDateTime += UserTime.dwLowDateTime; + double n_eaten = (double)( ( (uint64_t)(KernelTime.dwHighDateTime - ServerInstance->stats.LastCPU.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime - ServerInstance->stats.LastCPU.dwLowDateTime) )/100000; + double n_elapsed = (double)(ThisSample.QuadPart - ServerInstance->stats.LastSampled.QuadPart) / ServerInstance->stats.QPFrequency.QuadPart; + double per = (n_eaten/n_elapsed); + + stats.AddRow(249, InspIRCd::Format("CPU Use (now): %03.5f%%", per)); + + n_elapsed = ServerInstance->Time() - ServerInstance->startup_time; + n_eaten = (double)(( (uint64_t)(KernelTime.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime))/100000; + per = (n_eaten / n_elapsed); + + stats.AddRow(249, InspIRCd::Format("CPU Use (total): %03.5f%%", per)); + } +#endif + } + break; + + case 'T': + { + stats.AddRow(249, "accepts "+ConvToStr(ServerInstance->stats.Accept)+" refused "+ConvToStr(ServerInstance->stats.Refused)); + stats.AddRow(249, "unknown commands "+ConvToStr(ServerInstance->stats.Unknown)); + stats.AddRow(249, "nick collisions "+ConvToStr(ServerInstance->stats.Collisions)); + stats.AddRow(249, "dns requests "+ConvToStr(ServerInstance->stats.DnsGood+ServerInstance->stats.DnsBad)+" succeeded "+ConvToStr(ServerInstance->stats.DnsGood)+" failed "+ConvToStr(ServerInstance->stats.DnsBad)); + stats.AddRow(249, "connection count "+ConvToStr(ServerInstance->stats.Connects)); + stats.AddRow(249, InspIRCd::Format("bytes sent %5.2fK recv %5.2fK", + ServerInstance->stats.Sent / 1024.0, ServerInstance->stats.Recv / 1024.0)); + } + break; + + /* stats o */ + case 'o': + { + for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i) + { + OperInfo* ifo = i->second; + ConfigTag* tag = ifo->oper_block; + stats.AddRow(243, 'O', tag->getString("host"), '*', tag->getString("name"), tag->getString("type"), '0'); + } + } + break; + case 'O': + { + for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->OperTypes.begin(); i != ServerInstance->Config->OperTypes.end(); ++i) + { + OperInfo* tag = i->second; + tag->init(); + std::string umodes; + std::string cmodes; + for(char c='A'; c <= 'z'; c++) + { + ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_USER); + if (mh && mh->NeedsOper() && tag->AllowedUserModes[c - 'A']) + umodes.push_back(c); + mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL); + if (mh && mh->NeedsOper() && tag->AllowedChanModes[c - 'A']) + cmodes.push_back(c); + } + stats.AddRow(243, 'O', tag->name, umodes, cmodes); + } + } + break; + + /* stats l (show user I/O stats) */ + case 'l': + /* stats L (show user I/O stats with IP addresses) */ + case 'L': + GenerateStatsLl(stats); + break; + + /* stats u (show server uptime) */ + case 'u': + { + unsigned int up = static_cast<unsigned int>(ServerInstance->Time() - ServerInstance->startup_time); + stats.AddRow(242, InspIRCd::Format("Server up %u days, %.2u:%.2u:%.2u", + up / 86400, (up / 3600) % 24, (up / 60) % 60, up % 60)); + } + break; + + default: + break; + } + + stats.AddRow(219, statschar, "End of /STATS report"); + ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)", + (IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str()); + return; +} + +CmdResult CommandStats::Handle(User* user, const Params& parameters) +{ + if (parameters.size() > 1 && parameters[1] != ServerInstance->Config->ServerName) + { + // Give extra penalty if a non-oper does /STATS <remoteserver> + LocalUser* localuser = IS_LOCAL(user); + if ((localuser) && (!user->IsOper())) + localuser->CommandFloodPenalty += 2000; + return CMD_SUCCESS; + } + Stats::Context stats(user, parameters[0][0]); + DoStats(stats); + const std::vector<Stats::Row>& rows = stats.GetRows(); + for (std::vector<Stats::Row>::const_iterator i = rows.begin(); i != rows.end(); ++i) + { + const Stats::Row& row = *i; + user->WriteRemoteNumeric(row); + } + + return CMD_SUCCESS; +} + +class CoreModStats : public Module +{ + private: + CommandStats cmd; + + public: + CoreModStats() + : cmd(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the STATS command", VF_CORE | VF_VENDOR); + } +}; + +MODULE_INIT(CoreModStats) diff --git a/src/coremods/core_stub.cpp b/src/coremods/core_stub.cpp new file mode 100644 index 000000000..7ed3865e3 --- /dev/null +++ b/src/coremods/core_stub.cpp @@ -0,0 +1,156 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + + +/** Handle /CONNECT. + */ +class CommandConnect : public Command +{ + public: + /** Constructor for connect. + */ + CommandConnect(Module* parent) + : Command(parent, "CONNECT", 1) + { + flags_needed = 'o'; + syntax = "<servername>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + /* + * This is handled by the server linking module, if necessary. Do not remove this stub. + */ + user->WriteNotice("Look into loading a linking module (like m_spanningtree) if you want this to do anything useful."); + return CMD_SUCCESS; + } +}; + +/** Handle /LINKS. + */ +class CommandLinks : public Command +{ + public: + /** Constructor for links. + */ + CommandLinks(Module* parent) + : Command(parent, "LINKS", 0, 0) + { + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + user->WriteNumeric(RPL_LINKS, ServerInstance->Config->ServerName, ServerInstance->Config->ServerName, InspIRCd::Format("0 %s", ServerInstance->Config->ServerDesc.c_str())); + user->WriteNumeric(RPL_ENDOFLINKS, '*', "End of /LINKS list."); + return CMD_SUCCESS; + } +}; + +/** Handle /SERVER. + */ +class CommandServer : public Command +{ + public: + /** Constructor for server. + */ + CommandServer(Module* parent) + : Command(parent, "SERVER") + { + works_before_reg = true; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + if (user->registered == REG_ALL) + { + user->WriteNumeric(ERR_ALREADYREGISTERED, "You are already registered. (Perhaps your IRC client does not have a /SERVER command)."); + } + else + { + user->WriteNumeric(ERR_NOTREGISTERED, "SERVER", "You may not register as a server (servers have separate ports from clients, change your config)"); + } + return CMD_FAILURE; + } +}; + +/** Handle /SQUIT. + */ +class CommandSquit : public Command +{ + public: + /** Constructor for squit. + */ + CommandSquit(Module* parent) + : Command(parent, "SQUIT", 1, 2) + { + flags_needed = 'o'; + syntax = "<servername>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + user->WriteNotice("Look into loading a linking module (like m_spanningtree) if you want this to do anything useful."); + return CMD_FAILURE; + } +}; + +class CoreModStub : public Module +{ + CommandConnect cmdconnect; + CommandLinks cmdlinks; + CommandServer cmdserver; + CommandSquit cmdsquit; + + public: + CoreModStub() + : cmdconnect(this), cmdlinks(this), cmdserver(this), cmdsquit(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the stub commands CONNECT, LINKS, SERVER and SQUIT", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModStub) diff --git a/src/coremods/core_user/cmd_away.cpp b/src/coremods/core_user/cmd_away.cpp new file mode 100644 index 000000000..50a586392 --- /dev/null +++ b/src/coremods/core_user/cmd_away.cpp @@ -0,0 +1,82 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_user.h" + +enum +{ + // From RFC 1459. + RPL_UNAWAY = 305, + RPL_NOWAWAY = 306 +}; + +CommandAway::CommandAway(Module* parent) + : Command(parent, "AWAY", 0, 1) + , awayevprov(parent) +{ + allow_empty_last_param = false; + syntax = "[<message>]"; +} + +/** Handle /AWAY + */ +CmdResult CommandAway::Handle(User* user, const Params& parameters) +{ + LocalUser* luser = IS_LOCAL(user); + ModResult MOD_RESULT; + + if (!parameters.empty()) + { + std::string message(parameters[0]); + if (luser) + { + FIRST_MOD_RESULT_CUSTOM(awayevprov, Away::EventListener, OnUserPreAway, MOD_RESULT, (luser, message)); + if (MOD_RESULT == MOD_RES_DENY) + return CMD_FAILURE; + } + + user->awaytime = ServerInstance->Time(); + user->awaymsg.assign(message, 0, ServerInstance->Config->Limits.MaxAway); + user->WriteNumeric(RPL_NOWAWAY, "You have been marked as being away"); + FOREACH_MOD_CUSTOM(awayevprov, Away::EventListener, OnUserAway, (user)); + } + else + { + if (luser) + { + FIRST_MOD_RESULT_CUSTOM(awayevprov, Away::EventListener, OnUserPreBack, MOD_RESULT, (luser)); + if (MOD_RESULT == MOD_RES_DENY) + return CMD_FAILURE; + } + + user->awaytime = 0; + user->awaymsg.clear(); + user->WriteNumeric(RPL_UNAWAY, "You are no longer marked as being away"); + FOREACH_MOD_CUSTOM(awayevprov, Away::EventListener, OnUserBack, (user)); + } + + return CMD_SUCCESS; +} + +RouteDescriptor CommandAway::GetRouting(User* user, const Params& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} diff --git a/src/coremods/core_user/cmd_mode.cpp b/src/coremods/core_user/cmd_mode.cpp new file mode 100644 index 000000000..101f748f1 --- /dev/null +++ b/src/coremods/core_user/cmd_mode.cpp @@ -0,0 +1,177 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2004-2008 Craig Edwards <craigedwards@brainbox.cc> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "inspircd.h" +#include "core_user.h" + +CommandMode::CommandMode(Module* parent) + : Command(parent, "MODE", 1) + , seq(0) +{ + syntax = "<target> <modes> {<mode-parameters>}"; + memset(&sent, 0, sizeof(sent)); +} + +CmdResult CommandMode::Handle(User* user, const Params& parameters) +{ + const std::string& target = parameters[0]; + Channel* targetchannel = ServerInstance->FindChan(target); + User* targetuser = NULL; + if (!targetchannel) + { + if (IS_LOCAL(user)) + targetuser = ServerInstance->FindNickOnly(target); + else + targetuser = ServerInstance->FindNick(target); + } + + if ((!targetchannel) && (!targetuser)) + { + if (target[0] == '#') + user->WriteNumeric(Numerics::NoSuchChannel(target)); + else + user->WriteNumeric(Numerics::NoSuchNick(target)); + return CMD_FAILURE; + } + if (parameters.size() == 1) + { + this->DisplayCurrentModes(user, targetuser, targetchannel); + return CMD_SUCCESS; + } + + // Populate a temporary Modes::ChangeList with the parameters + Modes::ChangeList changelist; + ModeType type = targetchannel ? MODETYPE_CHANNEL : MODETYPE_USER; + ServerInstance->Modes.ModeParamsToChangeList(user, type, parameters, changelist); + + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (user, targetuser, targetchannel, changelist)); + + ModeParser::ModeProcessFlag flags = ModeParser::MODE_NONE; + if (IS_LOCAL(user)) + { + if (MOD_RESULT == MOD_RES_PASSTHRU) + { + if ((targetuser) && (user != targetuser)) + { + // Local users may only change the modes of other users if a module explicitly allows it + user->WriteNumeric(ERR_USERSDONTMATCH, "Can't change mode for other users"); + return CMD_FAILURE; + } + + // This is a mode change by a local user and modules didn't explicitly allow/deny. + // Ensure access checks will happen for each mode being changed. + flags |= ModeParser::MODE_CHECKACCESS; + } + else if (MOD_RESULT == MOD_RES_DENY) + return CMD_FAILURE; // Entire mode change denied by a module + } + else + flags |= ModeParser::MODE_LOCALONLY; + + if (IS_LOCAL(user)) + ServerInstance->Modes->ProcessSingle(user, targetchannel, targetuser, changelist, flags); + else + ServerInstance->Modes->Process(user, targetchannel, targetuser, changelist, flags); + + if ((ServerInstance->Modes.GetLastChangeList().empty()) && (targetchannel) && (parameters.size() == 2)) + { + /* Special case for displaying the list for listmodes, + * e.g. MODE #chan b, or MODE #chan +b without a parameter + */ + this->DisplayListModes(user, targetchannel, parameters[1]); + } + + return CMD_SUCCESS; +} + +RouteDescriptor CommandMode::GetRouting(User* user, const Params& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} + +void CommandMode::DisplayListModes(User* user, Channel* chan, const std::string& mode_sequence) +{ + seq++; + + for (std::string::const_iterator i = mode_sequence.begin(); i != mode_sequence.end(); ++i) + { + unsigned char mletter = *i; + if (mletter == '+') + continue; + + ModeHandler* mh = ServerInstance->Modes->FindMode(mletter, MODETYPE_CHANNEL); + if (!mh || !mh->IsListMode()) + return; + + /* Ensure the user doesnt request the same mode twice, + * so they can't flood themselves off out of idiocy. + */ + if (sent[mletter] == seq) + continue; + + sent[mletter] = seq; + ServerInstance->Modes.ShowListModeList(user, chan, mh); + } +} + +static std::string GetSnomasks(const User* user) +{ + ModeHandler* const snomask = ServerInstance->Modes.FindMode('s', MODETYPE_USER); + std::string snomaskstr = snomask->GetUserParameter(user); + // snomaskstr is empty if the snomask mode isn't set, otherwise it begins with a '+'. + // In the former case output a "+", not an empty string. + if (snomaskstr.empty()) + snomaskstr.push_back('+'); + return snomaskstr; +} + +void CommandMode::DisplayCurrentModes(User* user, User* targetuser, Channel* targetchannel) +{ + if (targetchannel) + { + // Display channel's current mode string + user->WriteNumeric(RPL_CHANNELMODEIS, targetchannel->name, (std::string("+") + targetchannel->ChanModes(targetchannel->HasUser(user)))); + user->WriteNumeric(RPL_CHANNELCREATED, targetchannel->name, (unsigned long)targetchannel->age); + } + else + { + if (targetuser == user) + { + // Display user's current mode string + user->WriteNumeric(RPL_UMODEIS, targetuser->GetModeLetters()); + if (targetuser->IsOper()) + user->WriteNumeric(RPL_SNOMASKIS, GetSnomasks(targetuser), "Server notice mask"); + } + else if (user->HasPrivPermission("users/auspex")) + { + // Querying the modes of another user. + // We cannot use RPL_UMODEIS because that's only for showing the user's own modes. + user->WriteNumeric(RPL_OTHERUMODEIS, targetuser->nick, targetuser->GetModeLetters()); + if (targetuser->IsOper()) + user->WriteNumeric(RPL_OTHERSNOMASKIS, targetuser->nick, GetSnomasks(targetuser), "Server notice mask"); + } + else + { + user->WriteNumeric(ERR_USERSDONTMATCH, "Can't view modes for other users"); + } + } +} diff --git a/src/commands/cmd_nick.cpp b/src/coremods/core_user/cmd_nick.cpp index a079e59d0..670931f7a 100644 --- a/src/commands/cmd_nick.cpp +++ b/src/coremods/core_user/cmd_nick.cpp @@ -21,44 +21,33 @@ #include "inspircd.h" +#include "core_user.h" -/** Handle /NICK. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandNick : public Command +CommandNick::CommandNick(Module* parent) + : SplitCommand(parent, "NICK", 1, 1) { - public: - /** Constructor for nick. - */ - CommandNick ( Module* parent) : Command(parent,"NICK", 1, 1) { works_before_reg = true; syntax = "<newnick>"; Penalty = 0; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; + works_before_reg = true; + syntax = "<newnick>"; + Penalty = 0; +} /** Handle nick changes from users. * NOTE: If you are used to ircds based on ircd2.8, and are looking * for the client introduction code in here, youre in the wrong place. * You need to look in the spanningtree module for this! */ -CmdResult CommandNick::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandNick::HandleLocal(LocalUser* user, const Params& parameters) { std::string oldnick = user->nick; std::string newnick = parameters[0]; // anything except the initial NICK gets a flood penalty - if (user->registered == REG_ALL && IS_LOCAL(user)) - IS_LOCAL(user)->CommandFloodPenalty += 4000; + if (user->registered == REG_ALL) + user->CommandFloodPenalty += 4000; if (newnick.empty()) { - user->WriteNumeric(432, "%s * :Erroneous Nickname", oldnick.c_str()); + user->WriteNumeric(ERR_NONICKNAMEGIVEN, "No nickname given"); return CMD_FAILURE; } @@ -66,33 +55,44 @@ CmdResult CommandNick::Handle (const std::vector<std::string>& parameters, User { newnick = user->uuid; } - else if (!ServerInstance->IsNick(newnick.c_str(), ServerInstance->Config->Limits.NickMax)) + else if (!ServerInstance->IsNick(newnick)) { - user->WriteNumeric(432, "%s %s :Erroneous Nickname", user->nick.c_str(),newnick.c_str()); + user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, "Erroneous Nickname"); return CMD_FAILURE; } - if (!user->ChangeNick(newnick, false)) + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnUserPreNick, MOD_RESULT, (user, newnick)); + + // If a module denied the change, abort now + if (MOD_RESULT == MOD_RES_DENY) return CMD_FAILURE; - if (user->registered < REG_NICKUSER) + // Disallow the nick change if <security:restrictbannedusers> is on and there is a ban matching this user in + // one of the channels they are on + if (ServerInstance->Config->RestrictBannedUsers != ServerConfig::BUT_NORMAL) { - user->registered = (user->registered | REG_NICK); - if (user->registered == REG_NICKUSER) + for (User::ChanList::iterator i = user->chans.begin(); i != user->chans.end(); ++i) { - /* user is registered now, bit 0 = USER command, bit 1 = sent a NICK command */ - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnUserRegister, MOD_RESULT, (IS_LOCAL(user))); - if (MOD_RESULT == MOD_RES_DENY) + Channel* chan = (*i)->chan; + if (chan->GetPrefixValue(user) < VOICE_VALUE && chan->IsBanned(user)) + { + if (ServerInstance->Config->RestrictBannedUsers == ServerConfig::BUT_RESTRICT_NOTIFY) + user->WriteNumeric(ERR_CANTCHANGENICK, InspIRCd::Format("Cannot change nickname while on %s (you're banned)", + chan->name.c_str())); return CMD_FAILURE; - - // return early to not penalize new users - return CMD_SUCCESS; + } } } - return CMD_SUCCESS; -} + if (!user->ChangeNick(newnick)) + return CMD_FAILURE; + if (user->registered < REG_NICKUSER) + { + user->registered = (user->registered | REG_NICK); + return CommandUser::CheckRegister(user); + } -COMMAND_INIT(CommandNick) + return CMD_SUCCESS; +} diff --git a/src/coremods/core_user/cmd_part.cpp b/src/coremods/core_user/cmd_part.cpp new file mode 100644 index 000000000..2bc431ab3 --- /dev/null +++ b/src/coremods/core_user/cmd_part.cpp @@ -0,0 +1,65 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_user.h" + +CommandPart::CommandPart(Module* parent) + : Command(parent, "PART", 1, 2) +{ + Penalty = 5; + syntax = "<channel>{,<channel>} [<reason>]"; +} + +CmdResult CommandPart::Handle(User* user, const Params& parameters) +{ + std::string reason; + if (parameters.size() > 1) + { + if (IS_LOCAL(user)) + msgwrap.Wrap(parameters[1], reason); + else + reason = parameters[1]; + } + + if (CommandParser::LoopCall(user, this, parameters, 0)) + return CMD_SUCCESS; + + Channel* c = ServerInstance->FindChan(parameters[0]); + + if (!c) + { + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); + return CMD_FAILURE; + } + + if (!c->PartUser(user, reason)) + { + user->WriteNumeric(ERR_NOTONCHANNEL, c->name, "You're not on that channel"); + return CMD_FAILURE; + } + + return CMD_SUCCESS; +} + +RouteDescriptor CommandPart::GetRouting(User* user, const Params& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} diff --git a/src/coremods/core_user/cmd_quit.cpp b/src/coremods/core_user/cmd_quit.cpp new file mode 100644 index 000000000..d919a5761 --- /dev/null +++ b/src/coremods/core_user/cmd_quit.cpp @@ -0,0 +1,51 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_user.h" + +CommandQuit::CommandQuit(Module* parent) + : Command(parent, "QUIT", 0, 1) + , operquit("operquit", ExtensionItem::EXT_USER, parent) +{ + works_before_reg = true; + syntax = "[<message>]"; +} + +CmdResult CommandQuit::Handle(User* user, const Params& parameters) +{ + std::string quitmsg; + if (parameters.empty()) + quitmsg = "Client exited"; + else if (IS_LOCAL(user)) + msgwrap.Wrap(parameters[0], quitmsg); + else + quitmsg = parameters[0]; + + std::string* operquitmsg = operquit.get(user); + ServerInstance->Users->QuitUser(user, quitmsg, operquitmsg); + + return CMD_SUCCESS; +} + +RouteDescriptor CommandQuit::GetRouting(User* user, const Params& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} diff --git a/src/coremods/core_user/cmd_user.cpp b/src/coremods/core_user/cmd_user.cpp new file mode 100644 index 000000000..1a8b091f3 --- /dev/null +++ b/src/coremods/core_user/cmd_user.cpp @@ -0,0 +1,80 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_user.h" + +enum +{ + // From ircu. + ERR_INVALIDUSERNAME = 468 +}; + +CommandUser::CommandUser(Module* parent) + : SplitCommand(parent, "USER", 4, 4) +{ + allow_empty_last_param = false; + works_before_reg = true; + Penalty = 0; + syntax = "<username> <localhost> <remotehost> <realname>"; +} + +CmdResult CommandUser::HandleLocal(LocalUser* user, const Params& parameters) +{ + /* A user may only send the USER command once */ + if (!(user->registered & REG_USER)) + { + if (!ServerInstance->IsIdent(parameters[0])) + { + user->WriteNumeric(ERR_INVALIDUSERNAME, name, "Your username is not valid"); + return CMD_FAILURE; + } + else + { + user->ChangeIdent(parameters[0]); + user->ChangeRealName(parameters[3]); + user->registered = (user->registered | REG_USER); + } + } + else + { + user->WriteNumeric(ERR_ALREADYREGISTERED, "You may not reregister"); + user->CommandFloodPenalty += 1000; + return CMD_FAILURE; + } + + /* parameters 2 and 3 are local and remote hosts, and are ignored */ + return CheckRegister(user); +} + +CmdResult CommandUser::CheckRegister(LocalUser* user) +{ + // If the user is registered, return CMD_SUCCESS/CMD_FAILURE depending on what modules say, otherwise just + // return CMD_SUCCESS without doing anything, knowing the other handler will call us again + if (user->registered == REG_NICKUSER) + { + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnUserRegister, MOD_RESULT, (user)); + if (MOD_RESULT == MOD_RES_DENY) + return CMD_FAILURE; + } + + return CMD_SUCCESS; +} diff --git a/src/coremods/core_user/core_user.cpp b/src/coremods/core_user/core_user.cpp new file mode 100644 index 000000000..e4e7a5056 --- /dev/null +++ b/src/coremods/core_user/core_user.cpp @@ -0,0 +1,184 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_user.h" + +/** Handle /PASS. + */ +class CommandPass : public SplitCommand +{ + public: + /** Constructor for pass. + */ + CommandPass(Module* parent) + : SplitCommand(parent, "PASS", 1, 1) + { + works_before_reg = true; + Penalty = 0; + syntax = "<password>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE + { + // Check to make sure they haven't registered -- Fix by FCS + if (user->registered == REG_ALL) + { + user->CommandFloodPenalty += 1000; + user->WriteNumeric(ERR_ALREADYREGISTERED, "You may not reregister"); + return CMD_FAILURE; + } + user->password = parameters[0]; + + return CMD_SUCCESS; + } +}; + +/** Handle /PING. + */ +class CommandPing : public SplitCommand +{ + public: + /** Constructor for ping. + */ + CommandPing(Module* parent) + : SplitCommand(parent, "PING", 1, 2) + { + syntax = "<servername> [:<servername>]"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE + { + ClientProtocol::Messages::Pong pong(parameters[0]); + user->Send(ServerInstance->GetRFCEvents().pong, pong); + return CMD_SUCCESS; + } +}; + +/** Handle /PONG. + */ +class CommandPong : public Command +{ + public: + /** Constructor for pong. + */ + CommandPong(Module* parent) + : Command(parent, "PONG", 0, 1) + { + Penalty = 0; + syntax = "<ping-text>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + // set the user as alive so they survive to next ping + LocalUser* localuser = IS_LOCAL(user); + if (localuser) + { + // Increase penalty unless we've sent a PING and this is the reply + if (localuser->lastping) + localuser->CommandFloodPenalty += 1000; + else + localuser->lastping = 1; + } + return CMD_SUCCESS; + } +}; + +void MessageWrapper::Wrap(const std::string& message, std::string& out) +{ + // If there is a fixed message, it is stored in prefix. Otherwise prefix contains + // only the prefix, so append the message and the suffix + out.assign(prefix); + if (!fixed) + out.append(message).append(suffix); +} + +void MessageWrapper::ReadConfig(const char* prefixname, const char* suffixname, const char* fixedname) +{ + ConfigTag* tag = ServerInstance->Config->ConfValue("options"); + prefix = tag->getString(fixedname); + fixed = (!prefix.empty()); + if (!fixed) + { + prefix = tag->getString(prefixname); + suffix = tag->getString(suffixname); + } +} + +class CoreModUser : public Module +{ + CommandAway cmdaway; + CommandMode cmdmode; + CommandNick cmdnick; + CommandPart cmdpart; + CommandPass cmdpass; + CommandPing cmdping; + CommandPong cmdpong; + CommandQuit cmdquit; + CommandUser cmduser; + SimpleUserModeHandler invisiblemode; + ModeUserOperator operatormode; + ModeUserServerNoticeMask snomaskmode; + + public: + CoreModUser() + : cmdaway(this) + , cmdmode(this) + , cmdnick(this) + , cmdpart(this) + , cmdpass(this) + , cmdping(this) + , cmdpong(this) + , cmdquit(this) + , cmduser(this) + , invisiblemode(this, "invisible", 'i') + , operatormode(this) + , snomaskmode(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + cmdpart.msgwrap.ReadConfig("prefixpart", "suffixpart", "fixedpart"); + cmdquit.msgwrap.ReadConfig("prefixquit", "suffixquit", "fixedquit"); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the AWAY, MODE, NICK, PART, PASS, PING, PONG, QUIT and USER commands", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModUser) diff --git a/src/coremods/core_user/core_user.h b/src/coremods/core_user/core_user.h new file mode 100644 index 000000000..1365cd048 --- /dev/null +++ b/src/coremods/core_user/core_user.h @@ -0,0 +1,226 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "inspircd.h" +#include "listmode.h" +#include "modules/away.h" + +class MessageWrapper +{ + std::string prefix; + std::string suffix; + bool fixed; + + public: + /** + * Wrap the given message according to the config rules + * @param message The message to wrap + * @param out String where the result is placed + */ + void Wrap(const std::string& message, std::string& out); + + /** + * Read the settings from the given config keys (options block) + * @param prefixname Name of the config key to read the prefix from + * @param suffixname Name of the config key to read the suffix from + * @param fixedname Name of the config key to read the fixed string string from. + * If this key has a non-empty value, all messages will be replaced with it. + */ + void ReadConfig(const char* prefixname, const char* suffixname, const char* fixedname); +}; + +/** Handle /AWAY. + */ +class CommandAway : public Command +{ + private: + Away::EventProvider awayevprov; + + public: + /** Constructor for away. + */ + CommandAway(Module* parent); + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +class CommandMode : public Command +{ + unsigned int sent[256]; + unsigned int seq; + + /** Show the list of one or more list modes to a user. + * @param user User to send to. + * @param chan Channel whose lists to show. + * @param mode_sequence Mode letters to show the lists of. + */ + void DisplayListModes(User* user, Channel* chan, const std::string& mode_sequence); + + /** Show the current modes of a channel or a user to a user. + * @param user User to show the modes to. + * @param targetuser User whose modes to show. NULL if showing the modes of a channel. + * @param targetchannel Channel whose modes to show. NULL if showing the modes of a user. + */ + void DisplayCurrentModes(User* user, User* targetuser, Channel* targetchannel); + + public: + /** Constructor for mode. + */ + CommandMode(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /NICK. + */ +class CommandNick : public SplitCommand +{ + public: + /** Constructor for nick. + */ + CommandNick(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /PART. + */ +class CommandPart : public Command +{ + public: + MessageWrapper msgwrap; + + /** Constructor for part. + */ + CommandPart(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /QUIT. + */ +class CommandQuit : public Command +{ + private: + StringExtItem operquit; + + public: + MessageWrapper msgwrap; + + /** Constructor for quit. + */ + CommandQuit(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /USER. + */ +class CommandUser : public SplitCommand +{ + public: + /** Constructor for user. + */ + CommandUser(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; + + /** Run the OnUserRegister hook if the user has sent both NICK and USER. Called after an unregistered user + * successfully executes the USER or the NICK command. + * @param user User to inspect and possibly pass to the OnUserRegister hook + * @return CMD_FAILURE if OnUserRegister was called and it returned MOD_RES_DENY, CMD_SUCCESS in every other case + * (i.e. if the hook wasn't fired because the user still needs to send NICK/USER or if it was fired and finished with + * a non-MOD_RES_DENY result). + */ + static CmdResult CheckRegister(LocalUser* user); +}; + +/** User mode +s + */ +class ModeUserServerNoticeMask : public ModeHandler +{ + /** Process a snomask modifier string, e.g. +abc-de + * @param user The target user + * @param input A sequence of notice mask characters + * @return The cleaned mode sequence which can be output, + * e.g. in the above example if masks c and e are not + * valid, this function will return +ab-d + */ + std::string ProcessNoticeMasks(User* user, const std::string& input); + + public: + ModeUserServerNoticeMask(Module* Creator); + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) CXX11_OVERRIDE; + void OnParameterMissing(User* user, User* dest, Channel* channel) CXX11_OVERRIDE; + + /** Create a displayable mode string of the snomasks set on a given user + * @param user The user whose notice masks to format + * @return The notice mask character sequence + */ + std::string GetUserParameter(const User* user) const CXX11_OVERRIDE; +}; + +/** User mode +o + */ +class ModeUserOperator : public ModeHandler +{ + public: + ModeUserOperator(Module* Creator); + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) CXX11_OVERRIDE; +}; diff --git a/src/modes/umode_o.cpp b/src/coremods/core_user/umode_o.cpp index a5f590ba0..20668fdaa 100644 --- a/src/modes/umode_o.cpp +++ b/src/coremods/core_user/umode_o.cpp @@ -19,12 +19,10 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/umode_o.h" +#include "core_user.h" -ModeUserOperator::ModeUserOperator() : ModeHandler(NULL, "oper", 'o', PARAM_NONE, MODETYPE_USER) +ModeUserOperator::ModeUserOperator(Module* Creator) + : ModeHandler(Creator, "oper", 'o', PARAM_NONE, MODETYPE_USER) { oper = true; } @@ -32,7 +30,7 @@ ModeUserOperator::ModeUserOperator() : ModeHandler(NULL, "oper", 'o', PARAM_NONE ModeAction ModeUserOperator::OnModeChange(User* source, User* dest, Channel*, std::string&, bool adding) { /* Only opers can execute this class at all */ - if (!ServerInstance->ULine(source->server) && !IS_OPER(source)) + if (!source->server->IsULine() && !source->IsOper()) return MODEACTION_DENY; /* Not even opers can GIVE the +o mode, only take it away */ @@ -40,14 +38,13 @@ ModeAction ModeUserOperator::OnModeChange(User* source, User* dest, Channel*, st return MODEACTION_DENY; /* Set the bitfields. - * Note that oper status is only given in cmd_oper.cpp + * Note that oper status is only given in User::Oper() * NOT here. It is impossible to directly set +o without * verifying as an oper and getting an opertype assigned * to your User! */ char snomask = IS_LOCAL(dest) ? 'o' : 'O'; - ServerInstance->SNO->WriteToSnoMask(snomask, "User %s de-opered (by %s)", dest->nick.c_str(), - source->nick.empty() ? source->server.c_str() : source->nick.c_str()); + ServerInstance->SNO->WriteToSnoMask(snomask, "User %s de-opered (by %s)", dest->nick.c_str(), source->nick.c_str()); dest->UnOper(); return MODEACTION_ALLOW; diff --git a/src/coremods/core_user/umode_s.cpp b/src/coremods/core_user/umode_s.cpp new file mode 100644 index 000000000..0122ebe3e --- /dev/null +++ b/src/coremods/core_user/umode_s.cpp @@ -0,0 +1,145 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "core_user.h" + +ModeUserServerNoticeMask::ModeUserServerNoticeMask(Module* Creator) + : ModeHandler(Creator, "snomask", 's', PARAM_SETONLY, MODETYPE_USER) +{ + oper = true; +} + +ModeAction ModeUserServerNoticeMask::OnModeChange(User* source, User* dest, Channel*, std::string ¶meter, bool adding) +{ + if (adding) + { + dest->SetMode(this, true); + // Process the parameter (remove chars we don't understand, remove redundant chars, etc.) + parameter = ProcessNoticeMasks(dest, parameter); + return MODEACTION_ALLOW; + } + else + { + if (dest->IsModeSet(this)) + { + dest->SetMode(this, false); + dest->snomasks.reset(); + return MODEACTION_ALLOW; + } + } + + // Mode not set and trying to unset, deny + return MODEACTION_DENY; +} + +std::string ModeUserServerNoticeMask::GetUserParameter(const User* user) const +{ + std::string ret; + if (!user->IsModeSet(this)) + return ret; + + ret.push_back('+'); + for (unsigned char n = 0; n < 64; n++) + { + if (user->snomasks[n]) + ret.push_back(n + 'A'); + } + return ret; +} + +void ModeUserServerNoticeMask::OnParameterMissing(User* user, User* dest, Channel* channel) +{ + user->WriteNotice("*** The user mode +s requires a parameter (server notice mask). Please provide a parameter, e.g. '+s +*'."); +} + +std::string ModeUserServerNoticeMask::ProcessNoticeMasks(User* user, const std::string& input) +{ + bool adding = true; + std::bitset<64> curr = user->snomasks; + + for (std::string::const_iterator i = input.begin(); i != input.end(); ++i) + { + switch (*i) + { + case '+': + adding = true; + break; + case '-': + adding = false; + break; + case '*': + for (size_t j = 0; j < 64; j++) + { + if (ServerInstance->SNO->IsSnomaskUsable(j+'A')) + curr[j] = adding; + } + break; + default: + // For local users check whether the given snomask is valid and enabled - IsSnomaskUsable() tests both. + // For remote users accept what we were told, unless the snomask char is not a letter. + if (IS_LOCAL(user)) + { + if (!ServerInstance->SNO->IsSnomaskUsable(*i)) + { + user->WriteNumeric(ERR_UNKNOWNSNOMASK, *i, "is unknown snomask char to me"); + continue; + } + } + else if (!(((*i >= 'a') && (*i <= 'z')) || ((*i >= 'A') && (*i <= 'Z')))) + continue; + + size_t index = ((*i) - 'A'); + curr[index] = adding; + break; + } + } + + std::string plus = "+"; + std::string minus = "-"; + + // Apply changes and construct two strings consisting of the newly added and the removed snomask chars + for (size_t i = 0; i < 64; i++) + { + bool isset = curr[i]; + if (user->snomasks[i] != isset) + { + user->snomasks[i] = isset; + std::string& appendhere = (isset ? plus : minus); + appendhere.push_back(i+'A'); + } + } + + // Create the final string that will be shown to the user and sent to servers + // Form: "+ABc-de" + std::string output; + if (plus.length() > 1) + output = plus; + + if (minus.length() > 1) + output += minus; + + // Unset the snomask usermode itself if every snomask was unset + if (user->snomasks.none()) + user->SetMode(this, false); + + return output; +} diff --git a/src/commands/cmd_userhost.cpp b/src/coremods/core_userhost.cpp index 7e1efd637..542c1831d 100644 --- a/src/commands/cmd_userhost.cpp +++ b/src/coremods/core_userhost.cpp @@ -20,78 +20,83 @@ #include "inspircd.h" -/** Handle /USERHOST. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. +/** Handle /USERHOST. */ class CommandUserhost : public Command { + UserModeReference hideopermode; + public: /** Constructor for userhost. */ - CommandUserhost ( Module* parent) : Command(parent,"USERHOST", 1) { + CommandUserhost(Module* parent) + : Command(parent,"USERHOST", 1) + , hideopermode(parent, "hideoper") + { syntax = "<nick> [<nick> ...]"; } /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command + * @param parameters The parameters to the command * @param user The user issuing the command * @return A value from CmdResult to indicate command success or failure. */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; }; -CmdResult CommandUserhost::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandUserhost::Handle(User* user, const Params& parameters) { - std::string retbuf = "302 " + user->nick + " :"; + const bool has_privs = user->HasPrivPermission("users/auspex"); + + std::string retbuf; unsigned int max = parameters.size(); if (max > 5) max = 5; - bool has_privs = user->HasPrivPermission("users/auspex"); for (unsigned int i = 0; i < max; i++) { User *u = ServerInstance->FindNickOnly(parameters[i]); if ((u) && (u->registered == REG_ALL)) { - retbuf = retbuf + u->nick; + retbuf += u->nick; - if (IS_OPER(u)) + if (u->IsOper()) { // XXX: +H hidden opers must not be shown as opers - ModeHandler* mh = ServerInstance->Modes->FindMode('H', MODETYPE_USER); - if ((u == user) || (has_privs) || (!mh) || (!u->IsModeSet('H')) || (mh->name != "hideoper")) + if ((u == user) || (has_privs) || (!u->IsModeSet(hideopermode))) retbuf += '*'; } - retbuf = retbuf + "="; - - if (IS_AWAY(u)) - retbuf += "-"; - else - retbuf += "+"; - - retbuf = retbuf + u->ident + "@"; - - if (has_privs) - { - retbuf = retbuf + u->host; - } - else - { - retbuf = retbuf + u->dhost; - } - - retbuf = retbuf + " "; + retbuf += '='; + retbuf += (u->IsAway() ? '-' : '+'); + retbuf += u->ident; + retbuf += '@'; + retbuf += u->GetHost(u == user || has_privs); + retbuf += ' '; } } - user->WriteServ(retbuf); + user->WriteNumeric(RPL_USERHOST, retbuf); return CMD_SUCCESS; } -COMMAND_INIT(CommandUserhost) +class CoreModUserhost : public Module +{ + private: + CommandUserhost cmd; + + public: + CoreModUserhost() + : cmd(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the USERHOST command", VF_CORE | VF_VENDOR); + } +}; + +MODULE_INIT(CoreModUserhost) diff --git a/src/coremods/core_wallops.cpp b/src/coremods/core_wallops.cpp new file mode 100644 index 000000000..6b055f874 --- /dev/null +++ b/src/coremods/core_wallops.cpp @@ -0,0 +1,89 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +/** Handle /WALLOPS. + */ +class CommandWallops : public Command +{ + SimpleUserModeHandler wallopsmode; + ClientProtocol::EventProvider protoevprov; + + public: + /** Constructor for wallops. + */ + CommandWallops(Module* parent) + : Command(parent, "WALLOPS", 1, 1) + , wallopsmode(parent, "wallops", 'w') + , protoevprov(parent, name) + { + flags_needed = 'o'; + syntax = "<any-text>"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE + { + return ROUTE_BROADCAST; + } +}; + +CmdResult CommandWallops::Handle(User* user, const Params& parameters) +{ + ClientProtocol::Message msg("WALLOPS", user); + msg.PushParamRef(parameters[0]); + ClientProtocol::Event wallopsevent(protoevprov, msg); + + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* curr = *i; + if (curr->IsModeSet(wallopsmode)) + curr->Send(wallopsevent); + } + + return CMD_SUCCESS; +} + +class CoreModWallops : public Module +{ + private: + CommandWallops cmd; + + public: + CoreModWallops() + : cmd(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the WALLOPS command", VF_CORE | VF_VENDOR); + } +}; + +MODULE_INIT(CoreModWallops) diff --git a/src/coremods/core_who.cpp b/src/coremods/core_who.cpp new file mode 100644 index 000000000..362e79c03 --- /dev/null +++ b/src/coremods/core_who.cpp @@ -0,0 +1,591 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2018 Peter Powell <petpow@saberuk.com> + * Copyright (C) 2014 Adam <Adam@anope.org> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/account.h" + +enum +{ + // From RFC 1459. + RPL_ENDOFWHO = 315, + RPL_WHOREPLY = 352, + + // From ircu. + RPL_WHOSPCRPL = 354 +}; + +struct WhoData +{ + // The flags for matching users to include. + std::bitset<UCHAR_MAX> flags; + + // Whether we are matching using a wildcard or a flag. + bool fuzzy_match; + + // The text to match against. + std::string matchtext; + + // The WHO/WHOX responses we will send to the source. + std::vector<Numeric::Numeric> results; + + // Whether the source requested a WHOX response. + bool whox; + + // The fields to include in the WHOX response. + std::bitset<UCHAR_MAX> whox_fields; + + // A user specified label for the WHOX response. + std::string whox_querytype; + + WhoData(const CommandBase::Params& parameters) + : whox(false) + { + // Find the matchtext and swap the 0 for a * so we can use InspIRCd::Match on it. + matchtext = parameters.size() > 2 ? parameters[2] : parameters[0]; + if (matchtext == "0") + matchtext = "*"; + + // Fuzzy matches are when the source has not specified a specific user. + fuzzy_match = (parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos); + + // If flags have been specified by the source. + if (parameters.size() > 1) + { + std::bitset<UCHAR_MAX>* current_bitset = &flags; + for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter) + { + unsigned char chr = static_cast<unsigned char>(*iter); + + // If the source specifies a percentage the rest of the flags are WHOX fields. + if (chr == '%') + { + whox = true; + current_bitset = &whox_fields; + continue; + } + + // If we are in WHOX mode and the source specifies a comma + // the rest of the parameter is the query type. + if (whox && chr == ',') + { + whox_querytype.assign(++iter, parameters[1].end()); + break; + } + + // The source specified a matching flag. + current_bitset->set(chr); + } + } + } +}; + +class CommandWho : public SplitCommand +{ + private: + ChanModeReference secretmode; + ChanModeReference privatemode; + UserModeReference hidechansmode; + UserModeReference invisiblemode; + + /** Determines whether a user can view the users of a channel. */ + bool CanView(Channel* chan, User* user) + { + // If we are in a channel we can view all users in it. + if (chan->HasUser(user)) + return true; + + // Opers with the users/auspex priv can see everything. + if (user->HasPrivPermission("users/auspex")) + return true; + + // You can see inside a channel from outside unless it is secret or private. + return !chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode); + } + + /** Gets the first channel which is visible between the source and the target users. */ + Membership* GetFirstVisibleChannel(LocalUser* source, User* user) + { + for (User::ChanList::iterator iter = user->chans.begin(); iter != user->chans.end(); ++iter) + { + Membership* memb = *iter; + + // TODO: move the +I check into m_hidechans. + bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || user->IsModeSet(hidechansmode); + if (source == user || !has_modes || memb->chan->HasUser(source)) + return memb; + } + return NULL; + } + + /** Determines whether WHO flags match a specific channel user. */ + static bool MatchChannel(LocalUser* source, Membership* memb, WhoData& data); + + /** Determines whether WHO flags match a specific user. */ + static bool MatchUser(LocalUser* source, User* target, WhoData& data); + + /** Performs a WHO request on a channel. */ + void WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* c, WhoData& data); + + /** Template for getting a user from various types of collection. */ + template<typename T> + static User* GetUser(T& t); + + /** Performs a WHO request on a list of users. */ + template<typename T> + void WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data); + + public: + CommandWho(Module* parent) + : SplitCommand(parent, "WHO", 1, 3) + , secretmode(parent, "secret") + , privatemode(parent, "private") + , hidechansmode(parent, "hidechans") + , invisiblemode(parent, "invisible") + { + allow_empty_last_param = false; + syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [[Aafhilmnoprstu] <server>|<nickname>|<channel>|<realname>|<host>|0]"; + } + + /** Sends a WHO reply to a user. */ + void SendWhoLine(LocalUser* user, const std::vector<std::string>& parameters, Membership* memb, User* u, WhoData& data); + + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; +}; + +template<> User* CommandWho::GetUser(UserManager::OperList::const_iterator& t) { return *t; } +template<> User* CommandWho::GetUser(user_hash::const_iterator& t) { return t->second; } + +bool CommandWho::MatchChannel(LocalUser* source, Membership* memb, WhoData& data) +{ + bool source_has_users_auspex = source->HasPrivPermission("users/auspex"); + bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex; + + // The source only wants remote users. This user is eligible if: + // (1) The source can't see server information. + // (2) The source is not local to the current server. + LocalUser* lu = IS_LOCAL(memb->user); + if (data.flags['f'] && source_can_see_server && lu) + return false; + + // The source only wants local users. This user is eligible if: + // (1) The source can't see server information. + // (2) The source is local to the current server. + if (data.flags['l'] && source_can_see_server && !lu) + return false; + + // Only show operators if the oper flag has been specified. + if (data.flags['o'] && !memb->user->IsOper()) + return false; + + // All other flags are ignored for channels. + return true; +} + +bool CommandWho::MatchUser(LocalUser* source, User* user, WhoData& data) +{ + // Users who are not fully registered can never match. + if (user->registered != REG_ALL) + return false; + + bool source_has_users_auspex = source->HasPrivPermission("users/auspex"); + bool source_can_see_target = source == user || source_has_users_auspex; + bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex; + + // The source only wants remote users. This user is eligible if: + // (1) The source can't see server information. + // (2) The source is not local to the current server. + LocalUser* lu = IS_LOCAL(user); + if (data.flags['f'] && source_can_see_server && lu) + return false; + + // The source only wants local users. This user is eligible if: + // (1) The source can't see server information. + // (2) The source is local to the current server. + if (data.flags['l'] && source_can_see_server && !lu) + return false; + + // The source wants to match against users' away messages. + bool match = false; + if (data.flags['A']) + match = user->IsAway() && InspIRCd::Match(user->awaymsg, data.matchtext, ascii_case_insensitive_map); + + // The source wants to match against users' account names. + else if (data.flags['a']) + { + const AccountExtItem* accountext = GetAccountExtItem(); + const std::string* account = accountext ? accountext->get(user) : NULL; + match = account && InspIRCd::Match(*account, data.matchtext); + } + + // The source wants to match against users' hostnames. + else if (data.flags['h']) + { + const std::string host = user->GetHost(source_can_see_target && data.flags['x']); + match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map); + } + + // The source wants to match against users' IP addresses. + else if (data.flags['i']) + match = source_can_see_target && InspIRCd::MatchCIDR(user->GetIPString(), data.matchtext, ascii_case_insensitive_map); + + // The source wants to match against users' modes. + else if (data.flags['m']) + { + if (source_can_see_target) + { + bool set = true; + for (std::string::const_iterator iter = data.matchtext.begin(); iter != data.matchtext.end(); ++iter) + { + unsigned char chr = static_cast<unsigned char>(*iter); + switch (chr) + { + // The following user modes should be set. + case '+': + set = true; + break; + + // The following user modes should be unset. + case '-': + set = false; + break; + + default: + if (user->IsModeSet(chr) != set) + return false; + break; + } + } + + // All of the modes matched. + return true; + } + } + + // The source wants to match against users' nicks. + else if (data.flags['n']) + match = InspIRCd::Match(user->nick, data.matchtext); + + // The source wants to match against users' connection ports. + else if (data.flags['p']) + { + if (source_can_see_target && lu) + { + irc::portparser portrange(data.matchtext, false); + long port; + while ((port = portrange.GetToken())) + { + if (port == lu->GetServerPort()) + { + match = true; + break; + } + } + } + } + + // The source wants to match against users' real names. + else if (data.flags['r']) + match = InspIRCd::Match(user->GetRealName(), data.matchtext, ascii_case_insensitive_map); + + else if (data.flags['s']) + { + bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']); + const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer; + match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map); + } + + // The source wants to match against users' connection times. + else if (data.flags['t']) + { + time_t seconds = ServerInstance->Time() - InspIRCd::Duration(data.matchtext); + if (user->signon >= seconds) + match = true; + } + + // The source wants to match against users' idents. + else if (data.flags['u']) + match = InspIRCd::Match(user->ident, data.matchtext, ascii_case_insensitive_map); + + // The <name> passed to WHO is matched against users' host, server, + // real name and nickname if the channel <name> cannot be found. + else + { + const std::string host = user->GetHost(source_can_see_target && data.flags['x']); + match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map); + + if (!match) + { + bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']); + const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer; + match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map); + } + + if (!match) + match = InspIRCd::Match(user->GetRealName(), data.matchtext, ascii_case_insensitive_map); + + if (!match) + match = InspIRCd::Match(user->nick, data.matchtext); + } + + return match; +} + +void CommandWho::WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* chan, WhoData& data) +{ + if (!CanView(chan, source)) + return; + + bool inside = chan->HasUser(source); + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator iter = users.begin(); iter != users.end(); ++iter) + { + User* user = iter->first; + Membership* memb = iter->second; + + // Only show invisible users if the source is in the channel or has the users/auspex priv. + if (!inside && user->IsModeSet(invisiblemode) && !source->HasPrivPermission("users/auspex")) + continue; + + // Skip the user if it doesn't match the query. + if (!MatchChannel(source, memb, data)) + continue; + + SendWhoLine(source, parameters, memb, user, data); + } +} + +template<typename T> +void CommandWho::WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data) +{ + for (typename T::const_iterator iter = users.begin(); iter != users.end(); ++iter) + { + User* user = GetUser(iter); + + // Only show users in response to a fuzzy WHO if we can see them normally. + bool can_see_normally = user == source || source->SharesChannelWith(user) || !user->IsModeSet(invisiblemode); + if (data.fuzzy_match && !can_see_normally && !source->HasPrivPermission("users/auspex")) + continue; + + // Skip the user if it doesn't match the query. + if (!MatchUser(source, user, data)) + continue; + + SendWhoLine(source, parameters, NULL, user, data); + } +} + +void CommandWho::SendWhoLine(LocalUser* source, const std::vector<std::string>& parameters, Membership* memb, User* user, WhoData& data) +{ + if (!memb) + memb = GetFirstVisibleChannel(source, user); + + bool source_can_see_target = source == user || source->HasPrivPermission("users/auspex"); + Numeric::Numeric wholine(data.whox ? RPL_WHOSPCRPL : RPL_WHOREPLY); + if (data.whox) + { + // The source used WHOX so we send a fancy customised response. + + // Include the query type in the reply. + if (data.whox_fields['t']) + wholine.push(data.whox_querytype.empty() || data.whox_querytype.length() > 3 ? "0" : data.whox_querytype); + + // Include the first channel name. + if (data.whox_fields['c']) + wholine.push(memb ? memb->chan->name : "*"); + + // Include the user's ident. + if (data.whox_fields['u']) + wholine.push(user->ident); + + // Include the user's IP address. + if (data.whox_fields['i']) + wholine.push(source_can_see_target ? user->GetIPString() : "255.255.255.255"); + + // Include the user's hostname. + if (data.whox_fields['h']) + wholine.push(user->GetHost(source_can_see_target && data.flags['x'])); + + // Include the server name. + if (data.whox_fields['s']) + { + if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x'])) + wholine.push(user->server->GetName()); + else + wholine.push(ServerInstance->Config->HideServer); + } + + // Include the user's nickname. + if (data.whox_fields['n']) + wholine.push(user->nick); + + // Include the user's flags. + if (data.whox_fields['f']) + { + // Away state. + std::string flags(user->IsAway() ? "G" : "H"); + + // Operator status. + if (user->IsOper()) + flags.push_back('*'); + + // Membership prefix. + if (memb) + { + char prefix = memb->GetPrefixChar(); + if (prefix) + flags.push_back(prefix); + } + + wholine.push(flags); + } + + // Include the number of hops between the users. + if (data.whox_fields['d']) + wholine.push("0"); + + // Include the user's idle time. + if (data.whox_fields['l']) + { + LocalUser* lu = IS_LOCAL(user); + unsigned long idle = lu ? ServerInstance->Time() - lu->idle_lastmsg : 0; + wholine.push(ConvToStr(idle)); + } + + // Include the user's account name. + if (data.whox_fields['a']) + { + const AccountExtItem* accountext = GetAccountExtItem(); + const std::string* account = accountext ? accountext->get(user) : NULL; + wholine.push(account ? *account : "0"); + } + + // Include the user's operator rank level. + if (data.whox_fields['o']) + wholine.push(memb ? ConvToStr(memb->getRank()) : "0"); + + // Include the user's real name. + if (data.whox_fields['r']) + wholine.push(user->GetRealName()); + } + else + { + // We are not using WHOX so we just send a plain RFC response. + + // Include the channel name. + wholine.push(memb ? memb->chan->name : "*"); + + // Include the user's ident. + wholine.push(user->ident); + + // Include the user's hostname. + wholine.push(user->GetHost(source_can_see_target && data.flags['x'])); + + // Include the server name. + if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x'])) + wholine.push(user->server->GetName()); + else + wholine.push(ServerInstance->Config->HideServer); + + // Include the user's nick. + wholine.push(user->nick); + + // Include the user's flags. + { + // Away state. + std::string flags(user->IsAway() ? "G" : "H"); + + // Operator status. + if (user->IsOper()) + flags.push_back('*'); + + // Membership prefix. + if (memb) + { + char prefix = memb->GetPrefixChar(); + if (prefix) + flags.push_back(prefix); + } + + wholine.push(flags); + } + + // Include the number of hops between the users and the user's real name. + wholine.push("0 "); + wholine.GetParams().back().append(user->GetRealName()); + } + + ModResult res; + FIRST_MOD_RESULT(OnSendWhoLine, res, (source, parameters, user, memb, wholine)); + if (res != MOD_RES_DENY) + data.results.push_back(wholine); +} + +CmdResult CommandWho::HandleLocal(LocalUser* user, const Params& parameters) +{ + WhoData data(parameters); + + // Is the source running a WHO on a channel? + Channel* chan = ServerInstance->FindChan(data.matchtext); + if (chan) + WhoChannel(user, parameters, chan, data); + + // If we only want to match against opers we only have to iterate the oper list. + else if (data.flags['o']) + WhoUsers(user, parameters, ServerInstance->Users->all_opers, data); + + // Otherwise we have to use the global user list. + else + WhoUsers(user, parameters, ServerInstance->Users->GetUsers(), data); + + // Send the results to the source. + for (std::vector<Numeric::Numeric>::const_iterator n = data.results.begin(); n != data.results.end(); ++n) + user->WriteNumeric(*n); + user->WriteNumeric(RPL_ENDOFWHO, (data.matchtext.empty() ? "*" : data.matchtext.c_str()), "End of /WHO list."); + + // Penalize the source a bit for large queries with one unit of penalty per 200 results. + user->CommandFloodPenalty += data.results.size() * 5; + return CMD_SUCCESS; +} + +class CoreModWho : public Module +{ + private: + CommandWho cmd; + + public: + CoreModWho() + : cmd(this) + { + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + tokens["WHOX"]; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the WHO command", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModWho) diff --git a/src/coremods/core_whois.cpp b/src/coremods/core_whois.cpp new file mode 100644 index 000000000..7fbaf2226 --- /dev/null +++ b/src/coremods/core_whois.cpp @@ -0,0 +1,370 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/whois.h" + +enum +{ + // From RFC 1459. + RPL_WHOISUSER = 311, + RPL_WHOISOPERATOR = 313, + RPL_WHOISIDLE = 317, + RPL_WHOISCHANNELS = 319, + + // From UnrealIRCd. + RPL_WHOISHOST = 378, + RPL_WHOISMODES = 379, + + // InspIRCd-specific. + RPL_CHANNELSMSG = 651 +}; + +enum SplitWhoisState +{ + // Don't split private/secret channels into a separate RPL_WHOISCHANNELS numeric. + SPLITWHOIS_NONE, + + // Split private/secret channels into a separate RPL_WHOISCHANNELS numeric. + SPLITWHOIS_SPLIT, + + // Split private/secret channels into a separate RPL_WHOISCHANNELS numeric with RPL_CHANNELSMSG to explain the split. + SPLITWHOIS_SPLITMSG +}; + +class WhoisContextImpl : public Whois::Context +{ + Events::ModuleEventProvider& lineevprov; + + public: + WhoisContextImpl(LocalUser* src, User* targ, Events::ModuleEventProvider& evprov) + : Whois::Context(src, targ) + , lineevprov(evprov) + { + } + + using Whois::Context::SendLine; + void SendLine(Numeric::Numeric& numeric) CXX11_OVERRIDE; +}; + +void WhoisContextImpl::SendLine(Numeric::Numeric& numeric) +{ + ModResult MOD_RESULT; + FIRST_MOD_RESULT_CUSTOM(lineevprov, Whois::LineEventListener, OnWhoisLine, MOD_RESULT, (*this, numeric)); + + if (MOD_RESULT != MOD_RES_DENY) + source->WriteNumeric(numeric); +} + +/** Handle /WHOIS. + */ +class CommandWhois : public SplitCommand +{ + ChanModeReference secretmode; + ChanModeReference privatemode; + UserModeReference snomaskmode; + Events::ModuleEventProvider evprov; + Events::ModuleEventProvider lineevprov; + + void DoWhois(LocalUser* user, User* dest, time_t signon, unsigned long idle); + void SendChanList(WhoisContextImpl& whois); + + public: + SplitWhoisState splitwhois; + + /** Constructor for whois. + */ + CommandWhois(Module* parent) + : SplitCommand(parent, "WHOIS", 1) + , secretmode(parent, "secret") + , privatemode(parent, "private") + , snomaskmode(parent, "snomask") + , evprov(parent, "event/whois") + , lineevprov(parent, "event/whoisline") + { + Penalty = 2; + syntax = "<nick>{,<nick>}"; + } + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; + CmdResult HandleRemote(RemoteUser* target, const Params& parameters) CXX11_OVERRIDE; +}; + +class WhoisNumericSink +{ + WhoisContextImpl& whois; + public: + WhoisNumericSink(WhoisContextImpl& whoisref) + : whois(whoisref) + { + } + + void operator()(Numeric::Numeric& numeric) const + { + whois.SendLine(numeric); + } +}; + +class WhoisChanListNumericBuilder : public Numeric::GenericBuilder<' ', false, WhoisNumericSink> +{ + public: + WhoisChanListNumericBuilder(WhoisContextImpl& whois) + : Numeric::GenericBuilder<' ', false, WhoisNumericSink>(WhoisNumericSink(whois), RPL_WHOISCHANNELS, false, whois.GetSource()->nick.size() + whois.GetTarget()->nick.size() + 1) + { + GetNumeric().push(whois.GetTarget()->nick).push(std::string()); + } +}; + +class WhoisChanList +{ + const SplitWhoisState& splitwhois; + WhoisChanListNumericBuilder num; + WhoisChanListNumericBuilder secretnum; + std::string prefixstr; + + void AddMember(Membership* memb, WhoisChanListNumericBuilder& out) + { + prefixstr.clear(); + const char prefix = memb->GetPrefixChar(); + if (prefix) + prefixstr.push_back(prefix); + out.Add(prefixstr, memb->chan->name); + } + + public: + WhoisChanList(WhoisContextImpl& whois, const SplitWhoisState& sws) + : splitwhois(sws) + , num(whois) + , secretnum(whois) + { + } + + void AddVisible(Membership* memb) + { + AddMember(memb, num); + } + + void AddHidden(Membership* memb) + { + AddMember(memb, splitwhois == SPLITWHOIS_NONE ? num : secretnum); + } + + void Flush(WhoisContextImpl& whois) + { + num.Flush(); + if (!secretnum.IsEmpty() && splitwhois == SPLITWHOIS_SPLITMSG) + whois.SendLine(RPL_CHANNELSMSG, "is on private/secret channels:"); + secretnum.Flush(); + } +}; + +void CommandWhois::SendChanList(WhoisContextImpl& whois) +{ + WhoisChanList chanlist(whois, splitwhois); + + User* const target = whois.GetTarget(); + bool hasoperpriv = whois.GetSource()->HasPrivPermission("users/channel-spy"); + for (User::ChanList::iterator i = target->chans.begin(); i != target->chans.end(); ++i) + { + Membership* memb = *i; + Channel* c = memb->chan; + + // Anyone can view channels which are not private or secret. + if (!c->IsModeSet(privatemode) && !c->IsModeSet(secretmode)) + chanlist.AddVisible(memb); + + // Hidden channels are visible when the following conditions are true: + // (1) The source user and the target user are the same. + // (2) The source user is a member of the hidden channel. + // (3) The source user is an oper with the users/channel-spy privilege. + else if (whois.IsSelfWhois() || c->HasUser(whois.GetSource()) || hasoperpriv) + chanlist.AddHidden(memb); + } + + chanlist.Flush(whois); +} + +void CommandWhois::DoWhois(LocalUser* user, User* dest, time_t signon, unsigned long idle) +{ + WhoisContextImpl whois(user, dest, lineevprov); + + whois.SendLine(RPL_WHOISUSER, dest->ident, dest->GetDisplayedHost(), '*', dest->GetRealName()); + if (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex")) + { + whois.SendLine(RPL_WHOISHOST, InspIRCd::Format("is connecting from %s@%s %s", dest->ident.c_str(), dest->GetRealHost().c_str(), dest->GetIPString().c_str())); + } + + SendChanList(whois); + + if (!whois.IsSelfWhois() && !ServerInstance->Config->HideServer.empty() && !user->HasPrivPermission("servers/auspex")) + { + whois.SendLine(RPL_WHOISSERVER, ServerInstance->Config->HideServer, ServerInstance->Config->Network); + } + else + { + whois.SendLine(RPL_WHOISSERVER, dest->server->GetName(), dest->server->GetDesc()); + } + + if (dest->IsAway()) + { + whois.SendLine(RPL_AWAY, dest->awaymsg); + } + + if (dest->IsOper()) + { + if (ServerInstance->Config->GenericOper) + whois.SendLine(RPL_WHOISOPERATOR, "is an IRC operator"); + else + whois.SendLine(RPL_WHOISOPERATOR, InspIRCd::Format("is %s %s on %s", (strchr("AEIOUaeiou",dest->oper->name[0]) ? "an" : "a"), dest->oper->name.c_str(), ServerInstance->Config->Network.c_str())); + } + + if (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex")) + { + if (dest->IsModeSet(snomaskmode)) + { + whois.SendLine(RPL_WHOISMODES, InspIRCd::Format("is using modes %s %s", dest->GetModeLetters().c_str(), snomaskmode->GetUserParameter(dest).c_str())); + } + else + { + whois.SendLine(RPL_WHOISMODES, InspIRCd::Format("is using modes %s", dest->GetModeLetters().c_str())); + } + } + + FOREACH_MOD_CUSTOM(evprov, Whois::EventListener, OnWhois, (whois)); + + /* + * We only send these if we've been provided them. That is, if hideserver is turned off, and user is local, or + * if remote whois is queried, too. This is to keep the user hidden, and also since you can't reliably tell remote time. -- w00t + */ + if ((idle) || (signon)) + { + whois.SendLine(RPL_WHOISIDLE, idle, signon, "seconds idle, signon time"); + } + + whois.SendLine(RPL_ENDOFWHOIS, "End of /WHOIS list."); +} + +CmdResult CommandWhois::HandleRemote(RemoteUser* target, const Params& parameters) +{ + if (parameters.size() < 2) + return CMD_FAILURE; + + User* user = ServerInstance->FindUUID(parameters[0]); + if (!user) + return CMD_FAILURE; + + // User doing the whois must be on this server + LocalUser* localuser = IS_LOCAL(user); + if (!localuser) + return CMD_FAILURE; + + unsigned long idle = ConvToNum<unsigned long>(parameters.back()); + DoWhois(localuser, target, target->signon, idle); + + return CMD_SUCCESS; +} + +CmdResult CommandWhois::HandleLocal(LocalUser* user, const Params& parameters) +{ + User *dest; + unsigned int userindex = 0; + unsigned long idle = 0; + time_t signon = 0; + + if (CommandParser::LoopCall(user, this, parameters, 0)) + return CMD_SUCCESS; + + /* + * If 2 paramters are specified (/whois nick nick), ignore the first one like spanningtree + * does, and use the second one, otherwise, use the only paramter. -- djGrrr + */ + if (parameters.size() > 1) + userindex = 1; + + dest = ServerInstance->FindNickOnly(parameters[userindex]); + + if ((dest) && (dest->registered == REG_ALL)) + { + /* + * Okay. Umpteenth attempt at doing this, so let's re-comment... + * For local users (/w localuser), we show idletime if hideserver is disabled + * For local users (/w localuser localuser), we always show idletime, hence parameters.size() > 1 check. + * For remote users (/w remoteuser), we do NOT show idletime + * For remote users (/w remoteuser remoteuser), spanningtree will handle calling do_whois, so we can ignore this case. + * Thanks to djGrrr for not being impatient while I have a crap day coding. :p -- w00t + */ + LocalUser* localuser = IS_LOCAL(dest); + if (localuser && (ServerInstance->Config->HideServer.empty() || parameters.size() > 1)) + { + idle = labs((long)((localuser->idle_lastmsg)-ServerInstance->Time())); + signon = dest->signon; + } + + DoWhois(user,dest,signon,idle); + } + else + { + /* no such nick/channel */ + user->WriteNumeric(Numerics::NoSuchNick(!parameters[userindex].empty() ? parameters[userindex] : "*")); + user->WriteNumeric(RPL_ENDOFWHOIS, (!parameters[userindex].empty() ? parameters[userindex] : "*"), "End of /WHOIS list."); + return CMD_FAILURE; + } + + return CMD_SUCCESS; +} + +class CoreModWhois : public Module +{ + private: + CommandWhois cmd; + + public: + CoreModWhois() + : cmd(this) + { + } + + void ReadConfig(ConfigStatus&) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("options"); + const std::string splitwhois = tag->getString("splitwhois", "no"); + if (stdalgo::string::equalsci(splitwhois, "no")) + cmd.splitwhois = SPLITWHOIS_NONE; + else if (stdalgo::string::equalsci(splitwhois, "split")) + cmd.splitwhois = SPLITWHOIS_SPLIT; + else if (stdalgo::string::equalsci(splitwhois, "splitmsg")) + cmd.splitwhois = SPLITWHOIS_SPLITMSG; + else + throw ModuleException(splitwhois + " is an invalid <security:splitwhois> value, at " + tag->getTagLocation()); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the WHOIS command", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModWhois) diff --git a/src/coremods/core_whowas.cpp b/src/coremods/core_whowas.cpp new file mode 100644 index 000000000..65c83e08b --- /dev/null +++ b/src/coremods/core_whowas.cpp @@ -0,0 +1,310 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "commands/cmd_whowas.h" +#include "modules/stats.h" + +enum +{ + // From RFC 1459. + RPL_WHOWASUSER = 314, + RPL_ENDOFWHOWAS = 369, + + // InspIRCd-specific. + RPL_WHOWASIP = 652 +}; + +CommandWhowas::CommandWhowas( Module* parent) + : Command(parent, "WHOWAS", 1) +{ + syntax = "<nick>{,<nick>}"; + Penalty = 2; +} + +CmdResult CommandWhowas::Handle(User* user, const Params& parameters) +{ + /* if whowas disabled in config */ + if (!manager.IsEnabled()) + { + user->WriteNumeric(ERR_UNKNOWNCOMMAND, name, "This command has been disabled."); + return CMD_FAILURE; + } + + const WhoWas::Nick* const nick = manager.FindNick(parameters[0]); + if (!nick) + { + user->WriteNumeric(ERR_WASNOSUCHNICK, parameters[0], "There was no such nickname"); + } + else + { + const WhoWas::Nick::List& list = nick->entries; + for (WhoWas::Nick::List::const_iterator i = list.begin(); i != list.end(); ++i) + { + WhoWas::Entry* u = *i; + + user->WriteNumeric(RPL_WHOWASUSER, parameters[0], u->ident, u->dhost, '*', u->real); + + if (user->HasPrivPermission("users/auspex")) + user->WriteNumeric(RPL_WHOWASIP, parameters[0], InspIRCd::Format("was connecting from *@%s", u->host.c_str())); + + std::string signon = InspIRCd::TimeString(u->signon); + bool hide_server = (!ServerInstance->Config->HideServer.empty() && !user->HasPrivPermission("servers/auspex")); + user->WriteNumeric(RPL_WHOISSERVER, parameters[0], (hide_server ? ServerInstance->Config->HideServer : u->server), signon); + } + } + + user->WriteNumeric(RPL_ENDOFWHOWAS, parameters[0], "End of WHOWAS"); + return CMD_SUCCESS; +} + +WhoWas::Manager::Manager() + : GroupSize(0), MaxGroups(0), MaxKeep(0) +{ +} + +const WhoWas::Nick* WhoWas::Manager::FindNick(const std::string& nickname) const +{ + whowas_users::const_iterator it = whowas.find(nickname); + if (it == whowas.end()) + return NULL; + return it->second; +} + +WhoWas::Manager::Stats WhoWas::Manager::GetStats() const +{ + size_t entrycount = 0; + for (whowas_users::const_iterator i = whowas.begin(); i != whowas.end(); ++i) + { + WhoWas::Nick::List& list = i->second->entries; + entrycount += list.size(); + } + + Stats stats; + stats.entrycount = entrycount; + return stats; +} + +void WhoWas::Manager::Add(User* user) +{ + if (!IsEnabled()) + return; + + // Insert nick if it doesn't exist + // 'first' will point to the newly inserted element or to the existing element with an equivalent key + std::pair<whowas_users::iterator, bool> ret = whowas.insert(std::make_pair(user->nick, static_cast<WhoWas::Nick*>(NULL))); + + if (ret.second) // If inserted + { + // This nick is new, create a list for it and add the first record to it + WhoWas::Nick* nick = new WhoWas::Nick(ret.first->first); + nick->entries.push_back(new Entry(user)); + ret.first->second = nick; + + // Add this nick to the fifo too + whowas_fifo.push_back(nick); + + if (whowas.size() > this->MaxGroups) + { + // Too many nicks, remove the nick which was inserted the longest time ago from both the map and the fifo + PurgeNick(whowas_fifo.front()); + } + } + else + { + // We've met this nick before, add a new record to the list + WhoWas::Nick::List& list = ret.first->second->entries; + list.push_back(new Entry(user)); + + // If there are too many records for this nick, remove the oldest (front) + if (list.size() > this->GroupSize) + { + delete list.front(); + list.pop_front(); + } + } +} + +/* on rehash, refactor maps according to new conf values */ +void WhoWas::Manager::Prune() +{ + time_t min = ServerInstance->Time() - this->MaxKeep; + + /* first cut the list to new size (maxgroups) and also prune entries that are timed out. */ + while (!whowas_fifo.empty()) + { + WhoWas::Nick* nick = whowas_fifo.front(); + if ((whowas_fifo.size() > this->MaxGroups) || (nick->addtime < min)) + PurgeNick(nick); + else + break; + } + + /* Then cut the whowas sets to new size (groupsize) */ + for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); ) + { + WhoWas::Nick::List& list = i->second->entries; + while (list.size() > this->GroupSize) + { + delete list.front(); + list.pop_front(); + } + + if (list.empty()) + PurgeNick(i++); + else + ++i; + } +} + +/* call maintain once an hour to remove expired nicks */ +void WhoWas::Manager::Maintain() +{ + time_t min = ServerInstance->Time() - this->MaxKeep; + for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); ) + { + WhoWas::Nick::List& list = i->second->entries; + while (!list.empty() && list.front()->signon < min) + { + delete list.front(); + list.pop_front(); + } + + if (list.empty()) + PurgeNick(i++); + else + ++i; + } +} + +WhoWas::Manager::~Manager() +{ + for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); ++i) + { + WhoWas::Nick* nick = i->second; + delete nick; + } +} + +bool WhoWas::Manager::IsEnabled() const +{ + return ((GroupSize != 0) && (MaxGroups != 0)); +} + +void WhoWas::Manager::UpdateConfig(unsigned int NewGroupSize, unsigned int NewMaxGroups, unsigned int NewMaxKeep) +{ + if ((NewGroupSize == GroupSize) && (NewMaxGroups == MaxGroups) && (NewMaxKeep == MaxKeep)) + return; + + GroupSize = NewGroupSize; + MaxGroups = NewMaxGroups; + MaxKeep = NewMaxKeep; + Prune(); +} + +void WhoWas::Manager::PurgeNick(whowas_users::iterator it) +{ + WhoWas::Nick* nick = it->second; + whowas_fifo.erase(nick); + whowas.erase(it); + delete nick; +} + +void WhoWas::Manager::PurgeNick(WhoWas::Nick* nick) +{ + whowas_users::iterator it = whowas.find(nick->nick); + if (it == whowas.end()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in whowas database, please report"); + return; + } + PurgeNick(it); +} + +WhoWas::Entry::Entry(User* user) + : host(user->GetRealHost()) + , dhost(user->GetDisplayedHost()) + , ident(user->ident) + , server(user->server->GetName()) + , real(user->GetRealName()) + , signon(user->signon) +{ +} + +WhoWas::Nick::Nick(const std::string& nickname) + : addtime(ServerInstance->Time()) + , nick(nickname) +{ +} + +WhoWas::Nick::~Nick() +{ + stdalgo::delete_all(entries); +} + +class ModuleWhoWas : public Module, public Stats::EventListener +{ + CommandWhowas cmd; + + public: + ModuleWhoWas() + : Stats::EventListener(this) + , cmd(this) + { + } + + void OnGarbageCollect() CXX11_OVERRIDE + { + // Remove all entries older than MaxKeep + cmd.manager.Maintain(); + } + + void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE + { + cmd.manager.Add(user); + } + + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE + { + if (stats.GetSymbol() == 'z') + stats.AddRow(249, "Whowas entries: "+ConvToStr(cmd.manager.GetStats().entrycount)); + + return MOD_RES_PASSTHRU; + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("whowas"); + unsigned int NewGroupSize = tag->getUInt("groupsize", 10, 0, 10000); + unsigned int NewMaxGroups = tag->getUInt("maxgroups", 10240, 0, 1000000); + unsigned int NewMaxKeep = tag->getDuration("maxkeep", 3600, 3600); + + cmd.manager.UpdateConfig(NewGroupSize, NewMaxGroups, NewMaxKeep); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("WHOWAS", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleWhoWas) diff --git a/src/commands/cmd_eline.cpp b/src/coremods/core_xline/cmd_eline.cpp index ca39f9061..0cb52298c 100644 --- a/src/commands/cmd_eline.cpp +++ b/src/coremods/core_xline/cmd_eline.cpp @@ -20,30 +20,18 @@ #include "inspircd.h" #include "xline.h" +#include "core_xline.h" -/** Handle /ELINE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandEline : public Command +CommandEline::CommandEline(Module* parent) + : Command(parent, "ELINE", 1, 3) { - public: - /** Constructor for eline. - */ - CommandEline ( Module* parent) : Command(parent,"ELINE",1,3) { flags_needed = 'o'; syntax = "<ident@host> [<duration> :<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; + flags_needed = 'o'; + syntax = "<ident@host> [<duration> :<reason>]"; +} /** Handle /ELINE */ -CmdResult CommandEline::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandEline::Handle(User* user, const Params& parameters) { std::string target = parameters[0]; @@ -60,17 +48,17 @@ CmdResult CommandEline::Handle (const std::vector<std::string>& parameters, User else ih = ServerInstance->XLines->IdentSplit(target); - if (ih.first.empty()) - { - user->WriteServ("NOTICE %s :*** Target not found", user->nick.c_str()); - return CMD_FAILURE; - } - - if (ServerInstance->HostMatchesEveryone(ih.first+"@"+ih.second,user)) + if (ih.first.empty()) + { + user->WriteNotice("*** Target not found"); return CMD_FAILURE; + } - long duration = ServerInstance->Duration(parameters[1].c_str()); + InsaneBan::IPHostMatcher matcher; + if (InsaneBan::MatchesEveryone(ih.first+"@"+ih.second, matcher, user, "E", "hostmasks")) + return CMD_FAILURE; + unsigned long duration = InspIRCd::Duration(parameters[1]); ELine* el = new ELine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ih.first.c_str(), ih.second.c_str()); if (ServerInstance->XLines->AddLine(el, user)) { @@ -81,7 +69,7 @@ CmdResult CommandEline::Handle (const std::vector<std::string>& parameters, User else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x',"%s added timed E-line for %s, expires on %s: %s",user->nick.c_str(),target.c_str(), timestr.c_str(), parameters[2].c_str()); } @@ -89,7 +77,7 @@ CmdResult CommandEline::Handle (const std::vector<std::string>& parameters, User else { delete el; - user->WriteServ("NOTICE %s :*** E-Line for %s already exists",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** E-Line for " + target + " already exists"); } } else @@ -100,11 +88,9 @@ CmdResult CommandEline::Handle (const std::vector<std::string>& parameters, User } else { - user->WriteServ("NOTICE %s :*** E-Line %s not found in list, try /stats e.",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** E-Line " + target + " not found in list, try /stats e"); } } return CMD_SUCCESS; } - -COMMAND_INIT(CommandEline) diff --git a/src/commands/cmd_gline.cpp b/src/coremods/core_xline/cmd_gline.cpp index 6505b7464..79b3ce21f 100644 --- a/src/commands/cmd_gline.cpp +++ b/src/coremods/core_xline/cmd_gline.cpp @@ -20,31 +20,18 @@ #include "inspircd.h" #include "xline.h" +#include "core_xline.h" -/** Handle /GLINE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandGline : public Command +CommandGline::CommandGline(Module* parent) + : Command(parent, "GLINE", 1, 3) { - public: - /** Constructor for gline. - */ - CommandGline (Module* parent) : Command(parent,"GLINE",1,3) { flags_needed = 'o'; Penalty = 0; syntax = "<ident@host> [<duration> :<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - + flags_needed = 'o'; + syntax = "<ident@host> [<duration> :<reason>]"; +} /** Handle /GLINE */ -CmdResult CommandGline::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandGline::Handle(User* user, const Params& parameters) { std::string target = parameters[0]; @@ -63,20 +50,21 @@ CmdResult CommandGline::Handle (const std::vector<std::string>& parameters, User if (ih.first.empty()) { - user->WriteServ("NOTICE %s :*** Target not found", user->nick.c_str()); + user->WriteNotice("*** Target not found"); return CMD_FAILURE; } - if (ServerInstance->HostMatchesEveryone(ih.first+"@"+ih.second,user)) + InsaneBan::IPHostMatcher matcher; + if (InsaneBan::MatchesEveryone(ih.first+"@"+ih.second, matcher, user, "G", "hostmasks")) return CMD_FAILURE; else if (target.find('!') != std::string::npos) { - user->WriteServ("NOTICE %s :*** G-Line cannot operate on nick!user@host masks",user->nick.c_str()); + user->WriteNotice("*** G-Line cannot operate on nick!user@host masks"); return CMD_FAILURE; } - long duration = ServerInstance->Duration(parameters[1].c_str()); + unsigned long duration = InspIRCd::Duration(parameters[1]); GLine* gl = new GLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ih.first.c_str(), ih.second.c_str()); if (ServerInstance->XLines->AddLine(gl, user)) { @@ -87,7 +75,7 @@ CmdResult CommandGline::Handle (const std::vector<std::string>& parameters, User else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x',"%s added timed G-line for %s, expires on %s: %s",user->nick.c_str(),target.c_str(), timestr.c_str(), parameters[2].c_str()); } @@ -97,7 +85,7 @@ CmdResult CommandGline::Handle (const std::vector<std::string>& parameters, User else { delete gl; - user->WriteServ("NOTICE %s :*** G-Line for %s already exists",user->nick.c_str(),target.c_str()); + user->WriteNotice("** G-Line for " + target + " already exists"); } } @@ -109,11 +97,9 @@ CmdResult CommandGline::Handle (const std::vector<std::string>& parameters, User } else { - user->WriteServ("NOTICE %s :*** G-line %s not found in list, try /stats g.",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** G-Line " + target + " not found in list, try /stats g."); } } return CMD_SUCCESS; } - -COMMAND_INIT(CommandGline) diff --git a/src/commands/cmd_kline.cpp b/src/coremods/core_xline/cmd_kline.cpp index ce3642f91..0f04224d6 100644 --- a/src/commands/cmd_kline.cpp +++ b/src/coremods/core_xline/cmd_kline.cpp @@ -20,33 +20,20 @@ #include "inspircd.h" #include "xline.h" +#include "core_xline.h" -/** Handle /KLINE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandKline : public Command +CommandKline::CommandKline(Module* parent) + : Command(parent, "KLINE", 1, 3) { - public: - /** Constructor for kline. - */ - CommandKline ( Module* parent) : Command(parent,"KLINE",1,3) { flags_needed = 'o'; Penalty = 0; syntax = "<ident@host> [<duration> :<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - + flags_needed = 'o'; + syntax = "<ident@host> [<duration> :<reason>]"; +} /** Handle /KLINE */ -CmdResult CommandKline::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandKline::Handle(User* user, const Params& parameters) { - std::string target = parameters[0]; + std::string target = parameters[0]; if (parameters.size() >= 3) { @@ -61,22 +48,23 @@ CmdResult CommandKline::Handle (const std::vector<std::string>& parameters, User else ih = ServerInstance->XLines->IdentSplit(target); - if (ih.first.empty()) - { - user->WriteServ("NOTICE %s :*** Target not found", user->nick.c_str()); - return CMD_FAILURE; - } + if (ih.first.empty()) + { + user->WriteNotice("*** Target not found"); + return CMD_FAILURE; + } - if (ServerInstance->HostMatchesEveryone(ih.first+"@"+ih.second,user)) + InsaneBan::IPHostMatcher matcher; + if (InsaneBan::MatchesEveryone(ih.first+"@"+ih.second, matcher, user, "K", "hostmasks")) return CMD_FAILURE; if (target.find('!') != std::string::npos) { - user->WriteServ("NOTICE %s :*** K-Line cannot operate on nick!user@host masks",user->nick.c_str()); + user->WriteNotice("*** K-Line cannot operate on nick!user@host masks"); return CMD_FAILURE; } - long duration = ServerInstance->Duration(parameters[1].c_str()); + unsigned long duration = InspIRCd::Duration(parameters[1]); KLine* kl = new KLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ih.first.c_str(), ih.second.c_str()); if (ServerInstance->XLines->AddLine(kl,user)) { @@ -87,7 +75,7 @@ CmdResult CommandKline::Handle (const std::vector<std::string>& parameters, User else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x',"%s added timed K-line for %s, expires on %s: %s",user->nick.c_str(),target.c_str(), timestr.c_str(), parameters[2].c_str()); } @@ -97,7 +85,7 @@ CmdResult CommandKline::Handle (const std::vector<std::string>& parameters, User else { delete kl; - user->WriteServ("NOTICE %s :*** K-Line for %s already exists",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** K-Line for " + target + " already exists"); } } else @@ -108,11 +96,9 @@ CmdResult CommandKline::Handle (const std::vector<std::string>& parameters, User } else { - user->WriteServ("NOTICE %s :*** K-Line %s not found in list, try /stats k.",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** K-Line " + target + " not found in list, try /stats k."); } } return CMD_SUCCESS; } - -COMMAND_INIT(CommandKline) diff --git a/src/commands/cmd_qline.cpp b/src/coremods/core_xline/cmd_qline.cpp index 3118798e6..b75973861 100644 --- a/src/commands/cmd_qline.cpp +++ b/src/coremods/core_xline/cmd_qline.cpp @@ -21,38 +21,30 @@ #include "inspircd.h" #include "xline.h" +#include "core_xline.h" -/** Handle /QLINE. */ -class CommandQline : public Command +CommandQline::CommandQline(Module* parent) + : Command(parent, "QLINE", 1, 3) { - public: - /** Constructor for qline. - */ - CommandQline ( Module* parent) : Command(parent,"QLINE",1,3) { flags_needed = 'o'; Penalty = 0; syntax = "<nick> [<duration> :<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to the command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; - + flags_needed = 'o'; + syntax = "<nick> [<duration> :<reason>]"; +} -CmdResult CommandQline::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandQline::Handle(User* user, const Params& parameters) { if (parameters.size() >= 3) { - if (ServerInstance->NickMatchesEveryone(parameters[0],user)) + NickMatcher matcher; + if (InsaneBan::MatchesEveryone(parameters[0], matcher, user, "Q", "nickmasks")) return CMD_FAILURE; if (parameters[0].find('@') != std::string::npos || parameters[0].find('!') != std::string::npos || parameters[0].find('.') != std::string::npos) { - user->WriteServ("NOTICE %s :*** A Q-Line only bans a nick pattern, not a nick!user@host pattern.",user->nick.c_str()); + user->WriteNotice("*** A Q-Line only bans a nick pattern, not a nick!user@host pattern."); return CMD_FAILURE; } - long duration = ServerInstance->Duration(parameters[1].c_str()); + unsigned long duration = InspIRCd::Duration(parameters[1]); QLine* ql = new QLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), parameters[0].c_str()); if (ServerInstance->XLines->AddLine(ql,user)) { @@ -63,7 +55,7 @@ CmdResult CommandQline::Handle (const std::vector<std::string>& parameters, User else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x',"%s added timed Q-line for %s, expires on %s: %s",user->nick.c_str(),parameters[0].c_str(), timestr.c_str(), parameters[2].c_str()); } @@ -72,7 +64,7 @@ CmdResult CommandQline::Handle (const std::vector<std::string>& parameters, User else { delete ql; - user->WriteServ("NOTICE %s :*** Q-Line for %s already exists",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** Q-Line for " + parameters[0] + " already exists"); } } else @@ -83,7 +75,7 @@ CmdResult CommandQline::Handle (const std::vector<std::string>& parameters, User } else { - user->WriteServ("NOTICE %s :*** Q-Line %s not found in list, try /stats q.",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** Q-Line " + parameters[0] + " not found in list, try /stats q."); return CMD_FAILURE; } } @@ -91,5 +83,7 @@ CmdResult CommandQline::Handle (const std::vector<std::string>& parameters, User return CMD_SUCCESS; } - -COMMAND_INIT(CommandQline) +bool CommandQline::NickMatcher::Check(User* user, const std::string& nick) const +{ + return InspIRCd::Match(user->nick, nick); +} diff --git a/src/commands/cmd_zline.cpp b/src/coremods/core_xline/cmd_zline.cpp index 91d9c6255..350f3270c 100644 --- a/src/commands/cmd_zline.cpp +++ b/src/coremods/core_xline/cmd_zline.cpp @@ -21,27 +21,16 @@ #include "inspircd.h" #include "xline.h" -/** Handle /ZLINE. These command handlers can be reloaded by the core, - * and handle basic RFC1459 commands. Commands within modules work - * the same way, however, they can be fully unloaded, where these - * may not. - */ -class CommandZline : public Command +#include "core_xline.h" + +CommandZline::CommandZline(Module* parent) + : Command(parent, "ZLINE", 1, 3) { - public: - /** Constructor for zline. - */ - CommandZline ( Module* parent) : Command(parent,"ZLINE",1,3) { flags_needed = 'o'; Penalty = 0; syntax = "<ipmask> [<duration> :<reason>]"; } - /** Handle command. - * @param parameters The parameters to the comamnd - * @param pcnt The number of parameters passed to teh command - * @param user The user issuing the command - * @return A value from CmdResult to indicate command success or failure. - */ - CmdResult Handle(const std::vector<std::string>& parameters, User *user); -}; + flags_needed = 'o'; + syntax = "<ipmask> [<duration> :<reason>]"; +} -CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandZline::Handle(User* user, const Params& parameters) { std::string target = parameters[0]; @@ -49,7 +38,7 @@ CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User { if (target.find('!') != std::string::npos) { - user->WriteServ("NOTICE %s :*** You cannot include a nickname in a zline, a zline must ban only an IP mask",user->nick.c_str()); + user->WriteNotice("*** You cannot include a nickname in a zline, a zline must ban only an IP mask"); return CMD_FAILURE; } @@ -69,11 +58,11 @@ CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User ipaddr++; } - if (ServerInstance->IPMatchesEveryone(ipaddr,user)) + IPMatcher matcher; + if (InsaneBan::MatchesEveryone(ipaddr, matcher, user, "Z", "ipmasks")) return CMD_FAILURE; - long duration = ServerInstance->Duration(parameters[1].c_str()); - + unsigned long duration = InspIRCd::Duration(parameters[1]); ZLine* zl = new ZLine(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), ipaddr); if (ServerInstance->XLines->AddLine(zl,user)) { @@ -84,7 +73,7 @@ CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x',"%s added timed Z-line for %s, expires on %s: %s",user->nick.c_str(),ipaddr, timestr.c_str(), parameters[2].c_str()); } @@ -93,7 +82,7 @@ CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User else { delete zl; - user->WriteServ("NOTICE %s :*** Z-Line for %s already exists",user->nick.c_str(),ipaddr); + user->WriteNotice("*** Z-Line for " + std::string(ipaddr) + " already exists"); } } else @@ -104,7 +93,7 @@ CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User } else { - user->WriteServ("NOTICE %s :*** Z-Line %s not found in list, try /stats Z.",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** Z-Line " + target + " not found in list, try /stats Z."); return CMD_FAILURE; } } @@ -112,4 +101,7 @@ CmdResult CommandZline::Handle (const std::vector<std::string>& parameters, User return CMD_SUCCESS; } -COMMAND_INIT(CommandZline) +bool CommandZline::IPMatcher::Check(User* user, const std::string& ip) const +{ + return InspIRCd::MatchCIDR(user->GetIPString(), ip, ascii_case_insensitive_map); +} diff --git a/src/coremods/core_xline/core_xline.cpp b/src/coremods/core_xline/core_xline.cpp new file mode 100644 index 000000000..ab80ab4ed --- /dev/null +++ b/src/coremods/core_xline/core_xline.cpp @@ -0,0 +1,93 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "xline.h" +#include "core_xline.h" + +bool InsaneBan::MatchesEveryone(const std::string& mask, MatcherBase& test, User* user, const char* bantype, const char* confkey) +{ + ConfigTag* insane = ServerInstance->Config->ConfValue("insane"); + + if (insane->getBool(confkey)) + return false; + + float itrigger = insane->getFloat("trigger", 95.5, 0.0, 100.0); + + long matches = test.Run(mask); + + if (!matches) + return false; + + float percent = ((float)matches / (float)ServerInstance->Users->GetUsers().size()) * 100; + if (percent > itrigger) + { + ServerInstance->SNO->WriteToSnoMask('a', "\2WARNING\2: %s tried to set a %s-line mask of %s, which covers %.2f%% of the network!", user->nick.c_str(), bantype, mask.c_str(), percent); + return true; + } + return false; +} + +bool InsaneBan::IPHostMatcher::Check(User* user, const std::string& mask) const +{ + return ((InspIRCd::MatchCIDR(user->MakeHost(), mask, ascii_case_insensitive_map)) || + (InspIRCd::MatchCIDR(user->MakeHostIP(), mask, ascii_case_insensitive_map))); +} + +class CoreModXLine : public Module +{ + CommandEline cmdeline; + CommandGline cmdgline; + CommandKline cmdkline; + CommandQline cmdqline; + CommandZline cmdzline; + + public: + CoreModXLine() + : cmdeline(this), cmdgline(this), cmdkline(this), cmdqline(this), cmdzline(this) + { + } + + ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) CXX11_OVERRIDE + { + // Check Q-Lines (for local nick changes only, remote servers have our Q-Lines to enforce themselves) + + XLine* xline = ServerInstance->XLines->MatchesLine("Q", newnick); + if (!xline) + return MOD_RES_PASSTHRU; // No match + + // A Q-Line matched the new nick, tell opers if the user is registered + if (user->registered == REG_ALL) + { + ServerInstance->SNO->WriteGlobalSno('a', "Q-Lined nickname %s from %s: %s", + newnick.c_str(), user->GetFullRealHost().c_str(), xline->reason.c_str()); + } + + // Send a numeric because if we deny then the core doesn't reply anything + user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, InspIRCd::Format("Invalid nickname: %s", xline->reason.c_str())); + return MOD_RES_DENY; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the ELINE, GLINE, KLINE, QLINE, and ZLINE commands", VF_VENDOR|VF_CORE); + } +}; + +MODULE_INIT(CoreModXLine) diff --git a/src/coremods/core_xline/core_xline.h b/src/coremods/core_xline/core_xline.h new file mode 100644 index 000000000..1e0bc74cc --- /dev/null +++ b/src/coremods/core_xline/core_xline.h @@ -0,0 +1,163 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "inspircd.h" + +class InsaneBan +{ + public: + class MatcherBase + { + public: + virtual long Run(const std::string& mask) = 0; + }; + + template <typename T> + class Matcher : public MatcherBase + { + public: + long Run(const std::string& mask) CXX11_OVERRIDE + { + long matches = 0; + const T* c = static_cast<T*>(this); + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i) + { + if (c->Check(i->second, mask)) + matches++; + } + return matches; + } + }; + + class IPHostMatcher : public Matcher<IPHostMatcher> + { + public: + bool Check(User* user, const std::string& mask) const; + }; + + /** Check if the given mask matches too many users according to the config, send an announcement if yes + * @param mask A mask to match against + * @param test The test that determines if a user matches the mask or not + * @param user A user whose nick will be included in the announcement if one is made + * @param bantype Type of the ban being set, will be used in the announcement if one is made + * @param confkey Name of the config key (inside the insane tag) which if false disables any checking + * @return True if the given mask matches too many users, false if not + */ + static bool MatchesEveryone(const std::string& mask, MatcherBase& test, User* user, const char* bantype, const char* confkey); +}; + +/** Handle /ELINE. + */ +class CommandEline : public Command +{ + public: + /** Constructor for eline. + */ + CommandEline(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /GLINE. + */ +class CommandGline : public Command +{ + public: + /** Constructor for gline. + */ + CommandGline(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /KLINE. + */ +class CommandKline : public Command +{ + public: + /** Constructor for kline. + */ + CommandKline(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /QLINE. + */ +class CommandQline : public Command +{ + class NickMatcher : public InsaneBan::Matcher<NickMatcher> + { + public: + bool Check(User* user, const std::string& mask) const; + }; + + public: + /** Constructor for qline. + */ + CommandQline(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +/** Handle /ZLINE. + */ +class CommandZline : public Command +{ + class IPMatcher : public InsaneBan::Matcher<IPMatcher> + { + public: + bool Check(User* user, const std::string& mask) const; + }; + + public: + /** Constructor for zline. + */ + CommandZline(Module* parent); + + /** Handle command. + * @param parameters The parameters to the command + * @param user The user issuing the command + * @return A value from CmdResult to indicate command success or failure. + */ + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; +}; diff --git a/src/cull_list.cpp b/src/cull_list.cpp index 956ed3494..73f2def51 100644 --- a/src/cull_list.cpp +++ b/src/cull_list.cpp @@ -21,7 +21,9 @@ #include "inspircd.h" +#ifdef INSPIRCD_ENABLE_RTTI #include <typeinfo> +#endif void CullList::Apply() { @@ -46,14 +48,18 @@ void CullList::Apply() classbase* c = list[i]; if (gone.insert(c).second) { - ServerInstance->Logs->Log("CULLLIST", DEBUG, "Deleting %s @%p", typeid(*c).name(), +#ifdef INSPIRCD_ENABLE_RTTI + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "Deleting %s @%p", typeid(*c).name(), (void*)c); +#else + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "Deleting @%p", (void*)c); +#endif c->cull(); queue.push_back(c); } else { - ServerInstance->Logs->Log("CULLLIST",DEBUG, "WARNING: Object @%p culled twice!", + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "WARNING: Object @%p culled twice!", (void*)c); } } @@ -65,7 +71,7 @@ void CullList::Apply() } if (list.size()) { - ServerInstance->Logs->Log("CULLLIST",DEBUG, "WARNING: Objects added to cull list in a destructor"); + ServerInstance->Logs->Log("CULLLIST", LOG_DEBUG, "WARNING: Objects added to cull list in a destructor"); Apply(); } } diff --git a/src/dns.cpp b/src/dns.cpp deleted file mode 100644 index 3d3bc947f..000000000 --- a/src/dns.cpp +++ /dev/null @@ -1,1120 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2012 William Pitcock <nenolod@dereferenced.org> - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2006, 2009 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2007, 2009 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * Copyright (C) 2005-2007 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -/* $Core */ - -/* -dns.cpp - Nonblocking DNS functions. -Very very loosely based on the firedns library, -Copyright (C) 2002 Ian Gulliver. This file is no -longer anything like firedns, there are many major -differences between this code and the original. -Please do not assume that firedns works like this, -looks like this, walks like this or tastes like this. -*/ - -#ifndef _WIN32 -#include <sys/types.h> -#include <sys/socket.h> -#include <errno.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#else -#include "inspircd_win32wrapper.h" -#endif - -#include "inspircd.h" -#include "socketengine.h" -#include "configreader.h" -#include "socket.h" - -#define DN_COMP_BITMASK 0xC000 /* highest 6 bits in a DN label header */ - -/** Masks to mask off the responses we get from the DNSRequest methods - */ -enum QueryInfo -{ - ERROR_MASK = 0x10000 /* Result is an error */ -}; - -/** Flags which can be ORed into a request or reply for different meanings - */ -enum QueryFlags -{ - FLAGS_MASK_RD = 0x01, /* Recursive */ - FLAGS_MASK_TC = 0x02, - FLAGS_MASK_AA = 0x04, /* Authoritative */ - FLAGS_MASK_OPCODE = 0x78, - FLAGS_MASK_QR = 0x80, - FLAGS_MASK_RCODE = 0x0F, /* Request */ - FLAGS_MASK_Z = 0x70, - FLAGS_MASK_RA = 0x80 -}; - - -/** Represents a dns resource record (rr) - */ -struct ResourceRecord -{ - QueryType type; /* Record type */ - unsigned int rr_class; /* Record class */ - unsigned long ttl; /* Time to live */ - unsigned int rdlength; /* Record length */ -}; - -/** Represents a dns request/reply header, and its payload as opaque data. - */ -class DNSHeader -{ - public: - unsigned char id[2]; /* Request id */ - unsigned int flags1; /* Flags */ - unsigned int flags2; /* Flags */ - unsigned int qdcount; - unsigned int ancount; /* Answer count */ - unsigned int nscount; /* Nameserver count */ - unsigned int arcount; - unsigned char payload[512]; /* Packet payload */ -}; - -class DNSRequest -{ - public: - unsigned char id[2]; /* Request id */ - unsigned char* res; /* Result processing buffer */ - unsigned int rr_class; /* Request class */ - QueryType type; /* Request type */ - DNS* dnsobj; /* DNS caller (where we get our FD from) */ - unsigned long ttl; /* Time to live */ - std::string orig; /* Original requested name/ip */ - - DNSRequest(DNS* dns, int id, const std::string &original); - ~DNSRequest(); - DNSInfo ResultIsReady(DNSHeader &h, unsigned length); - int SendRequests(const DNSHeader *header, const int length, QueryType qt); -}; - -class CacheTimer : public Timer -{ - private: - DNS* dns; - public: - CacheTimer(DNS* thisdns) - : Timer(5*60, ServerInstance->Time(), true), dns(thisdns) { } - - virtual void Tick(time_t) - { - dns->PruneCache(); - } -}; - -class RequestTimeout : public Timer -{ - DNSRequest* watch; - int watchid; - public: - RequestTimeout(unsigned long n, DNSRequest* watching, int id) : Timer(n, ServerInstance->Time()), watch(watching), watchid(id) - { - } - ~RequestTimeout() - { - if (ServerInstance->Res) - Tick(0); - } - - void Tick(time_t) - { - if (ServerInstance->Res->requests[watchid] == watch) - { - /* Still exists, whack it */ - if (ServerInstance->Res->Classes[watchid]) - { - ServerInstance->Res->Classes[watchid]->OnError(RESOLVER_TIMEOUT, "Request timed out"); - delete ServerInstance->Res->Classes[watchid]; - ServerInstance->Res->Classes[watchid] = NULL; - } - ServerInstance->Res->requests[watchid] = NULL; - delete watch; - } - } -}; - -CachedQuery::CachedQuery(const std::string &res, QueryType qt, unsigned int ttl) : data(res), type(qt) -{ - if (ttl > 5*60) - ttl = 5*60; - expires = ServerInstance->Time() + ttl; -} - -int CachedQuery::CalcTTLRemaining() -{ - int n = expires - ServerInstance->Time(); - return (n < 0 ? 0 : n); -} - -/* Allocate the processing buffer */ -DNSRequest::DNSRequest(DNS* dns, int rid, const std::string &original) : dnsobj(dns) -{ - /* hardening against overflow here: make our work buffer twice the theoretical - * maximum size so that hostile input doesn't screw us over. - */ - res = new unsigned char[sizeof(DNSHeader) * 2]; - *res = 0; - orig = original; - RequestTimeout* RT = new RequestTimeout(ServerInstance->Config->dns_timeout ? ServerInstance->Config->dns_timeout : 5, this, rid); - ServerInstance->Timers->AddTimer(RT); /* The timer manager frees this */ -} - -/* Deallocate the processing buffer */ -DNSRequest::~DNSRequest() -{ - delete[] res; -} - -/** Fill a ResourceRecord class based on raw data input */ -inline void DNS::FillResourceRecord(ResourceRecord* rr, const unsigned char *input) -{ - rr->type = (QueryType)((input[0] << 8) + input[1]); - rr->rr_class = (input[2] << 8) + input[3]; - rr->ttl = (input[4] << 24) + (input[5] << 16) + (input[6] << 8) + input[7]; - rr->rdlength = (input[8] << 8) + input[9]; -} - -/** Fill a DNSHeader class based on raw data input of a given length */ -inline void DNS::FillHeader(DNSHeader *header, const unsigned char *input, const int length) -{ - header->id[0] = input[0]; - header->id[1] = input[1]; - header->flags1 = input[2]; - header->flags2 = input[3]; - header->qdcount = (input[4] << 8) + input[5]; - header->ancount = (input[6] << 8) + input[7]; - header->nscount = (input[8] << 8) + input[9]; - header->arcount = (input[10] << 8) + input[11]; - memcpy(header->payload,&input[12],length); -} - -/** Empty a DNSHeader class out into raw data, ready for transmission */ -inline void DNS::EmptyHeader(unsigned char *output, const DNSHeader *header, const int length) -{ - output[0] = header->id[0]; - output[1] = header->id[1]; - output[2] = header->flags1; - output[3] = header->flags2; - output[4] = header->qdcount >> 8; - output[5] = header->qdcount & 0xFF; - output[6] = header->ancount >> 8; - output[7] = header->ancount & 0xFF; - output[8] = header->nscount >> 8; - output[9] = header->nscount & 0xFF; - output[10] = header->arcount >> 8; - output[11] = header->arcount & 0xFF; - memcpy(&output[12],header->payload,length); -} - -/** Send requests we have previously built down the UDP socket */ -int DNSRequest::SendRequests(const DNSHeader *header, const int length, QueryType qt) -{ - ServerInstance->Logs->Log("RESOLVER", DEBUG,"DNSRequest::SendRequests"); - - unsigned char payload[sizeof(DNSHeader)]; - - this->rr_class = 1; - this->type = qt; - - DNS::EmptyHeader(payload,header,length); - - if (ServerInstance->SE->SendTo(dnsobj, payload, length + 12, 0, &(dnsobj->myserver.sa), sa_size(dnsobj->myserver)) != length+12) - return -1; - - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Sent OK"); - return 0; -} - -/** Add a query with a predefined header, and allocate an ID for it. */ -DNSRequest* DNS::AddQuery(DNSHeader *header, int &id, const char* original) -{ - /* Is the DNS connection down? */ - if (this->GetFd() == -1) - return NULL; - - /* Create an id */ - unsigned int tries = 0; - do { - id = ServerInstance->GenRandomInt(DNS::MAX_REQUEST_ID); - if (++tries == DNS::MAX_REQUEST_ID*5) - { - // If we couldn't find an empty slot this many times, do a sequential scan as a last - // resort. If an empty slot is found that way, go on, otherwise throw an exception - id = -1; - for (int i = 0; i < DNS::MAX_REQUEST_ID; i++) - { - if (!requests[i]) - { - id = i; - break; - } - } - - if (id == -1) - throw ModuleException("DNS: All ids are in use"); - - break; - } - } while (requests[id]); - - DNSRequest* req = new DNSRequest(this, id, original); - - header->id[0] = req->id[0] = id >> 8; - header->id[1] = req->id[1] = id & 0xFF; - header->flags1 = FLAGS_MASK_RD; - header->flags2 = 0; - header->qdcount = 1; - header->ancount = 0; - header->nscount = 0; - header->arcount = 0; - - /* At this point we already know the id doesnt exist, - * so there needs to be no second check for the ::end() - */ - requests[id] = req; - - /* According to the C++ spec, new never returns NULL. */ - return req; -} - -int DNS::ClearCache() -{ - /* This ensures the buckets are reset to sane levels */ - int rv = this->cache->size(); - delete this->cache; - this->cache = new dnscache(); - return rv; -} - -int DNS::PruneCache() -{ - int n = 0; - dnscache* newcache = new dnscache(); - for (dnscache::iterator i = this->cache->begin(); i != this->cache->end(); i++) - /* Dont include expired items (theres no point) */ - if (i->second.CalcTTLRemaining()) - newcache->insert(*i); - else - n++; - - delete this->cache; - this->cache = newcache; - return n; -} - -void DNS::Rehash() -{ - if (this->GetFd() > -1) - { - ServerInstance->SE->DelFd(this); - ServerInstance->SE->Shutdown(this, 2); - ServerInstance->SE->Close(this); - this->SetFd(-1); - - /* Rehash the cache */ - this->PruneCache(); - } - else - { - /* Create initial dns cache */ - this->cache = new dnscache(); - } - - irc::sockets::aptosa(ServerInstance->Config->DNSServer, DNS::QUERY_PORT, myserver); - - /* Initialize mastersocket */ - int s = socket(myserver.sa.sa_family, SOCK_DGRAM, 0); - this->SetFd(s); - - /* Have we got a socket and is it nonblocking? */ - if (this->GetFd() != -1) - { - ServerInstance->SE->SetReuse(s); - ServerInstance->SE->NonBlocking(s); - irc::sockets::sockaddrs bindto; - memset(&bindto, 0, sizeof(bindto)); - bindto.sa.sa_family = myserver.sa.sa_family; - if (ServerInstance->SE->Bind(this->GetFd(), bindto) < 0) - { - /* Failed to bind */ - ServerInstance->Logs->Log("RESOLVER",SPARSE,"Error binding dns socket - hostnames will NOT resolve"); - ServerInstance->SE->Shutdown(this, 2); - ServerInstance->SE->Close(this); - this->SetFd(-1); - } - else if (!ServerInstance->SE->AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE)) - { - ServerInstance->Logs->Log("RESOLVER",SPARSE,"Internal error starting DNS - hostnames will NOT resolve."); - ServerInstance->SE->Shutdown(this, 2); - ServerInstance->SE->Close(this); - this->SetFd(-1); - } - } - else - { - ServerInstance->Logs->Log("RESOLVER",SPARSE,"Error creating DNS socket - hostnames will NOT resolve"); - } -} - -/** Initialise the DNS UDP socket so that we can send requests */ -DNS::DNS() -{ - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::DNS"); - /* Clear the Resolver class table */ - memset(Classes,0,sizeof(Classes)); - - /* Clear the requests class table */ - memset(requests,0,sizeof(requests)); - - /* DNS::Rehash() sets this to a valid ptr - */ - this->cache = NULL; - - /* Again, DNS::Rehash() sets this to a - * valid value - */ - this->SetFd(-1); - - /* Actually read the settings - */ - this->Rehash(); - - this->PruneTimer = new CacheTimer(this); - - ServerInstance->Timers->AddTimer(this->PruneTimer); -} - -/** Build a payload to be placed after the header, based upon input data, a resource type, a class and a pointer to a buffer */ -int DNS::MakePayload(const char * const name, const QueryType rr, const unsigned short rr_class, unsigned char * const payload) -{ - short payloadpos = 0; - const char* tempchr, *tempchr2 = name; - unsigned short length; - - /* split name up into labels, create query */ - while ((tempchr = strchr(tempchr2,'.')) != NULL) - { - length = tempchr - tempchr2; - if (payloadpos + length + 1 > 507) - return -1; - payload[payloadpos++] = length; - memcpy(&payload[payloadpos],tempchr2,length); - payloadpos += length; - tempchr2 = &tempchr[1]; - } - length = strlen(tempchr2); - if (length) - { - if (payloadpos + length + 2 > 507) - return -1; - payload[payloadpos++] = length; - memcpy(&payload[payloadpos],tempchr2,length); - payloadpos += length; - payload[payloadpos++] = 0; - } - if (payloadpos > 508) - return -1; - length = htons(rr); - memcpy(&payload[payloadpos],&length,2); - length = htons(rr_class); - memcpy(&payload[payloadpos + 2],&length,2); - return payloadpos + 4; -} - -/** Start lookup of an hostname to an IP address */ -int DNS::GetIP(const char *name) -{ - DNSHeader h; - int id; - int length; - - if ((length = this->MakePayload(name, DNS_QUERY_A, 1, (unsigned char*)&h.payload)) == -1) - return -1; - - DNSRequest* req = this->AddQuery(&h, id, name); - - if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_A) == -1)) - return -1; - - return id; -} - -/** Start lookup of an hostname to an IPv6 address */ -int DNS::GetIP6(const char *name) -{ - DNSHeader h; - int id; - int length; - - if ((length = this->MakePayload(name, DNS_QUERY_AAAA, 1, (unsigned char*)&h.payload)) == -1) - return -1; - - DNSRequest* req = this->AddQuery(&h, id, name); - - if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_AAAA) == -1)) - return -1; - - return id; -} - -/** Start lookup of a cname to another name */ -int DNS::GetCName(const char *alias) -{ - DNSHeader h; - int id; - int length; - - if ((length = this->MakePayload(alias, DNS_QUERY_CNAME, 1, (unsigned char*)&h.payload)) == -1) - return -1; - - DNSRequest* req = this->AddQuery(&h, id, alias); - - if ((!req) || (req->SendRequests(&h, length, DNS_QUERY_CNAME) == -1)) - return -1; - - return id; -} - -/** Start lookup of an IP address to a hostname */ -int DNS::GetNameForce(const char *ip, ForceProtocol fp) -{ - char query[128]; - DNSHeader h; - int id; - int length; - - if (fp == PROTOCOL_IPV6) - { - in6_addr i; - if (inet_pton(AF_INET6, ip, &i) > 0) - { - DNS::MakeIP6Int(query, &i); - } - else - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce IPv6 bad format for '%s'", ip); - /* Invalid IP address */ - return -1; - } - } - else - { - in_addr i; - if (inet_aton(ip, &i)) - { - unsigned char* c = (unsigned char*)&i.s_addr; - sprintf(query,"%d.%d.%d.%d.in-addr.arpa",c[3],c[2],c[1],c[0]); - } - else - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce IPv4 bad format for '%s'", ip); - /* Invalid IP address */ - return -1; - } - } - - length = this->MakePayload(query, DNS_QUERY_PTR, 1, (unsigned char*)&h.payload); - if (length == -1) - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce can't query '%s' using '%s' because it's too long", ip, query); - return -1; - } - - DNSRequest* req = this->AddQuery(&h, id, ip); - - if (!req) - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce can't add query (resolver down?)"); - return -1; - } - - if (req->SendRequests(&h, length, DNS_QUERY_PTR) == -1) - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS::GetNameForce can't send (firewall?)"); - return -1; - } - - return id; -} - -/** Build an ipv6 reverse domain from an in6_addr - */ -void DNS::MakeIP6Int(char* query, const in6_addr *ip) -{ - const char* hex = "0123456789abcdef"; - for (int index = 31; index >= 0; index--) /* for() loop steps twice per byte */ - { - if (index % 2) - /* low nibble */ - *query++ = hex[ip->s6_addr[index / 2] & 0x0F]; - else - /* high nibble */ - *query++ = hex[(ip->s6_addr[index / 2] & 0xF0) >> 4]; - *query++ = '.'; /* Seperator */ - } - strcpy(query,"ip6.arpa"); /* Suffix the string */ -} - -/** Return the next id which is ready, and the result attached to it */ -DNSResult DNS::GetResult() -{ - /* Fetch dns query response and decide where it belongs */ - DNSHeader header; - DNSRequest *req; - unsigned char buffer[sizeof(DNSHeader)]; - irc::sockets::sockaddrs from; - memset(&from, 0, sizeof(from)); - socklen_t x = sizeof(from); - - int length = ServerInstance->SE->RecvFrom(this, (char*)buffer, sizeof(DNSHeader), 0, &from.sa, &x); - - /* Did we get the whole header? */ - if (length < 12) - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"GetResult didn't get a full packet (len=%d)", length); - /* Nope - something screwed up. */ - return DNSResult(-1,"",0,""); - } - - /* Check wether the reply came from a different DNS - * server to the one we sent it to, or the source-port - * is not 53. - * A user could in theory still spoof dns packets anyway - * but this is less trivial than just sending garbage - * to the server, which is possible without this check. - * - * -- Thanks jilles for pointing this one out. - */ - if (from != myserver) - { - std::string server1 = from.str(); - std::string server2 = myserver.str(); - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Got a result from the wrong server! Bad NAT or DNS forging attempt? '%s' != '%s'", - server1.c_str(), server2.c_str()); - return DNSResult(-1,"",0,""); - } - - /* Put the read header info into a header class */ - DNS::FillHeader(&header,buffer,length - 12); - - /* Get the id of this request. - * Its a 16 bit value stored in two char's, - * so we use logic shifts to create the value. - */ - unsigned long this_id = header.id[1] + (header.id[0] << 8); - - /* Do we have a pending request matching this id? */ - if (!requests[this_id]) - { - /* Somehow we got a DNS response for a request we never made... */ - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Hmm, got a result that we didn't ask for (id=%lx). Ignoring.", this_id); - return DNSResult(-1,"",0,""); - } - else - { - /* Remove the query from the list of pending queries */ - req = requests[this_id]; - requests[this_id] = NULL; - } - - /* Inform the DNSRequest class that it has a result to be read. - * When its finished it will return a DNSInfo which is a pair of - * unsigned char* resource record data, and an error message. - */ - DNSInfo data = req->ResultIsReady(header, length); - std::string resultstr; - - /* Check if we got a result, if we didnt, its an error */ - if (data.first == NULL) - { - /* An error. - * Mask the ID with the value of ERROR_MASK, so that - * the dns_deal_with_classes() function knows that its - * an error response and needs to be treated uniquely. - * Put the error message in the second field. - */ - std::string ro = req->orig; - delete req; - return DNSResult(this_id | ERROR_MASK, data.second, 0, ro); - } - else - { - unsigned long ttl = req->ttl; - char formatted[128]; - - /* Forward lookups come back as binary data. We must format them into ascii */ - switch (req->type) - { - case DNS_QUERY_A: - snprintf(formatted,16,"%u.%u.%u.%u",data.first[0],data.first[1],data.first[2],data.first[3]); - resultstr = formatted; - break; - - case DNS_QUERY_AAAA: - { - if (!inet_ntop(AF_INET6, data.first, formatted, sizeof(formatted))) - { - std::string ro = req->orig; - delete req; - return DNSResult(this_id | ERROR_MASK, "inet_ntop() failed", 0, ro); - } - - resultstr = formatted; - - /* Special case. Sending ::1 around between servers - * and to clients is dangerous, because the : on the - * start makes the client or server interpret the IP - * as the last parameter on the line with a value ":1". - */ - if (*formatted == ':') - resultstr.insert(0, "0"); - } - break; - - case DNS_QUERY_CNAME: - /* Identical handling to PTR */ - - case DNS_QUERY_PTR: - { - /* Reverse lookups just come back as char* */ - resultstr = std::string((const char*)data.first); - if (resultstr.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") != std::string::npos) - { - std::string ro = req->orig; - delete req; - return DNSResult(this_id | ERROR_MASK, "Invalid char(s) in reply", 0, ro); - } - } - break; - - default: - break; - } - - /* Build the reply with the id and hostname/ip in it */ - std::string ro = req->orig; - DNSResult result = DNSResult(this_id,resultstr,ttl,ro,req->type); - delete req; - return result; - } -} - -/** A result is ready, process it */ -DNSInfo DNSRequest::ResultIsReady(DNSHeader &header, unsigned length) -{ - unsigned i = 0, o; - int q = 0; - int curanswer; - ResourceRecord rr; - unsigned short ptr; - - /* This is just to keep _FORTIFY_SOURCE happy */ - rr.type = DNS_QUERY_NONE; - rr.rdlength = 0; - rr.ttl = 1; /* GCC is a whiney bastard -- see the XXX below. */ - rr.rr_class = 0; /* Same for VC++ */ - - if (!(header.flags1 & FLAGS_MASK_QR)) - return std::make_pair((unsigned char*)NULL,"Not a query result"); - - if (header.flags1 & FLAGS_MASK_OPCODE) - return std::make_pair((unsigned char*)NULL,"Unexpected value in DNS reply packet"); - - if (header.flags2 & FLAGS_MASK_RCODE) - return std::make_pair((unsigned char*)NULL,"Domain name not found"); - - if (header.ancount < 1) - return std::make_pair((unsigned char*)NULL,"No resource records returned"); - - /* Subtract the length of the header from the length of the packet */ - length -= 12; - - while ((unsigned int)q < header.qdcount && i < length) - { - if (header.payload[i] > 63) - { - i += 6; - q++; - } - else - { - if (header.payload[i] == 0) - { - q++; - i += 5; - } - else i += header.payload[i] + 1; - } - } - curanswer = 0; - while ((unsigned)curanswer < header.ancount) - { - q = 0; - while (q == 0 && i < length) - { - if (header.payload[i] > 63) - { - i += 2; - q = 1; - } - else - { - if (header.payload[i] == 0) - { - i++; - q = 1; - } - else i += header.payload[i] + 1; /* skip length and label */ - } - } - if (static_cast<int>(length - i) < 10) - return std::make_pair((unsigned char*)NULL,"Incorrectly sized DNS reply"); - - /* XXX: We actually initialise 'rr' here including its ttl field */ - DNS::FillResourceRecord(&rr,&header.payload[i]); - - i += 10; - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Resolver: rr.type is %d and this.type is %d rr.class %d this.class %d", rr.type, this->type, rr.rr_class, this->rr_class); - if (rr.type != this->type) - { - curanswer++; - i += rr.rdlength; - continue; - } - if (rr.rr_class != this->rr_class) - { - curanswer++; - i += rr.rdlength; - continue; - } - break; - } - if ((unsigned int)curanswer == header.ancount) - return std::make_pair((unsigned char*)NULL,"No A, AAAA or PTR type answers (" + ConvToStr(header.ancount) + " answers)"); - - if (i + rr.rdlength > (unsigned int)length) - return std::make_pair((unsigned char*)NULL,"Resource record larger than stated"); - - if (rr.rdlength > 1023) - return std::make_pair((unsigned char*)NULL,"Resource record too large"); - - this->ttl = rr.ttl; - - switch (rr.type) - { - /* - * CNAME and PTR are compressed. We need to decompress them. - */ - case DNS_QUERY_CNAME: - case DNS_QUERY_PTR: - { - unsigned short lowest_pos = length; - o = 0; - q = 0; - while (q == 0 && i < length && o + 256 < 1023) - { - /* DN label found (byte over 63) */ - if (header.payload[i] > 63) - { - memcpy(&ptr,&header.payload[i],2); - - i = ntohs(ptr); - - /* check that highest two bits are set. if not, we've been had */ - if ((i & DN_COMP_BITMASK) != DN_COMP_BITMASK) - return std::make_pair((unsigned char *) NULL, "DN label decompression header is bogus"); - - /* mask away the two highest bits. */ - i &= ~DN_COMP_BITMASK; - - /* and decrease length by 12 bytes. */ - i -= 12; - - if (i >= lowest_pos) - return std::make_pair((unsigned char *) NULL, "Invalid decompression pointer"); - lowest_pos = i; - } - else - { - if (header.payload[i] == 0) - { - q = 1; - } - else - { - res[o] = 0; - if (o != 0) - res[o++] = '.'; - - if (o + header.payload[i] > sizeof(DNSHeader)) - return std::make_pair((unsigned char *) NULL, "DN label decompression is impossible -- malformed/hostile packet?"); - - memcpy(&res[o], &header.payload[i + 1], header.payload[i]); - o += header.payload[i]; - i += header.payload[i] + 1; - } - } - } - res[o] = 0; - } - break; - case DNS_QUERY_AAAA: - if (rr.rdlength != sizeof(struct in6_addr)) - return std::make_pair((unsigned char *) NULL, "rr.rdlength is larger than 16 bytes for an ipv6 entry -- malformed/hostile packet?"); - - memcpy(res,&header.payload[i],rr.rdlength); - res[rr.rdlength] = 0; - break; - case DNS_QUERY_A: - if (rr.rdlength != sizeof(struct in_addr)) - return std::make_pair((unsigned char *) NULL, "rr.rdlength is larger than 4 bytes for an ipv4 entry -- malformed/hostile packet?"); - - memcpy(res,&header.payload[i],rr.rdlength); - res[rr.rdlength] = 0; - break; - default: - return std::make_pair((unsigned char *) NULL, "don't know how to handle undefined type (" + ConvToStr(rr.type) + ") -- rejecting"); - break; - } - return std::make_pair(res,"No error"); -} - -/** Close the master socket */ -DNS::~DNS() -{ - ServerInstance->SE->Shutdown(this, 2); - ServerInstance->SE->Close(this); - ServerInstance->Timers->DelTimer(this->PruneTimer); - if (cache) - delete cache; -} - -CachedQuery* DNS::GetCache(const std::string &source) -{ - dnscache::iterator x = cache->find(source.c_str()); - if (x != cache->end()) - return &(x->second); - else - return NULL; -} - -void DNS::DelCache(const std::string &source) -{ - cache->erase(source.c_str()); -} - -void Resolver::TriggerCachedResult() -{ - if (CQ) - OnLookupComplete(CQ->data, time_left, true); -} - -/** High level abstraction of dns used by application at large */ -Resolver::Resolver(const std::string &source, QueryType qt, bool &cached, Module* creator) : Creator(creator), input(source), querytype(qt) -{ - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Resolver::Resolver"); - cached = false; - - CQ = ServerInstance->Res->GetCache(source); - if (CQ) - { - time_left = CQ->CalcTTLRemaining(); - if (!time_left) - { - ServerInstance->Res->DelCache(source); - } - else if (CQ->type == qt) - { - cached = true; - return; - } - CQ = NULL; - } - - switch (querytype) - { - case DNS_QUERY_A: - this->myid = ServerInstance->Res->GetIP(source.c_str()); - break; - - case DNS_QUERY_PTR4: - querytype = DNS_QUERY_PTR; - this->myid = ServerInstance->Res->GetNameForce(source.c_str(), PROTOCOL_IPV4); - break; - - case DNS_QUERY_PTR6: - querytype = DNS_QUERY_PTR; - this->myid = ServerInstance->Res->GetNameForce(source.c_str(), PROTOCOL_IPV6); - break; - - case DNS_QUERY_AAAA: - this->myid = ServerInstance->Res->GetIP6(source.c_str()); - break; - - case DNS_QUERY_CNAME: - this->myid = ServerInstance->Res->GetCName(source.c_str()); - break; - - default: - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS request with unknown query type %d", querytype); - this->myid = -1; - break; - } - if (this->myid == -1) - { - throw ModuleException("Resolver: Couldn't get an id to make a request"); - } - else - { - ServerInstance->Logs->Log("RESOLVER",DEBUG,"DNS request id %d", this->myid); - } -} - -/** Called when an error occurs */ -void Resolver::OnError(ResolverError, const std::string&) -{ - /* Nothing in here */ -} - -/** Destroy a resolver */ -Resolver::~Resolver() -{ - /* Nothing here (yet) either */ -} - -/** Get the request id associated with this class */ -int Resolver::GetId() -{ - return this->myid; -} - -Module* Resolver::GetCreator() -{ - return this->Creator; -} - -/** Process a socket read event */ -void DNS::HandleEvent(EventType, int) -{ - /* Fetch the id and result of the next available packet */ - DNSResult res(0,"",0,""); - res.id = 0; - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Handle DNS event"); - - res = this->GetResult(); - - ServerInstance->Logs->Log("RESOLVER",DEBUG,"Result id %d", res.id); - - /* Is there a usable request id? */ - if (res.id != -1) - { - /* Its an error reply */ - if (res.id & ERROR_MASK) - { - /* Mask off the error bit */ - res.id -= ERROR_MASK; - /* Marshall the error to the correct class */ - if (Classes[res.id]) - { - if (ServerInstance && ServerInstance->stats) - ServerInstance->stats->statsDnsBad++; - Classes[res.id]->OnError(RESOLVER_NXDOMAIN, res.result); - delete Classes[res.id]; - Classes[res.id] = NULL; - } - return; - } - else - { - /* It is a non-error result, marshall the result to the correct class */ - if (Classes[res.id]) - { - if (ServerInstance && ServerInstance->stats) - ServerInstance->stats->statsDnsGood++; - - if (!this->GetCache(res.original.c_str())) - { - if (cache->size() >= MAX_CACHE_SIZE) - cache->clear(); - this->cache->insert(std::make_pair(res.original.c_str(), CachedQuery(res.result, res.type, res.ttl))); - } - - Classes[res.id]->OnLookupComplete(res.result, res.ttl, false); - delete Classes[res.id]; - Classes[res.id] = NULL; - } - } - - if (ServerInstance && ServerInstance->stats) - ServerInstance->stats->statsDns++; - } -} - -/** Add a derived Resolver to the working set */ -bool DNS::AddResolverClass(Resolver* r) -{ - ServerInstance->Logs->Log("RESOLVER",DEBUG,"AddResolverClass 0x%08lx", (unsigned long)r); - /* Check the pointers validity and the id's validity */ - if ((r) && (r->GetId() > -1)) - { - /* Check the slot isnt already occupied - - * This should NEVER happen unless we have - * a severely broken DNS server somewhere - */ - if (!Classes[r->GetId()]) - { - /* Set up the pointer to the class */ - Classes[r->GetId()] = r; - return true; - } - } - - /* Pointer or id not valid, or duplicate id. - * Free the item and return - */ - delete r; - return false; -} - -void DNS::CleanResolvers(Module* module) -{ - for (int i = 0; i < MAX_REQUEST_ID; i++) - { - if (Classes[i]) - { - if (Classes[i]->GetCreator() == module) - { - Classes[i]->OnError(RESOLVER_FORCEUNLOAD, "Parent module is unloading"); - delete Classes[i]; - Classes[i] = NULL; - } - } - } -} diff --git a/src/dynamic.cpp b/src/dynamic.cpp index d111da548..08564a0cd 100644 --- a/src/dynamic.cpp +++ b/src/dynamic.cpp @@ -22,7 +22,7 @@ #include "inspircd.h" -#include "dynamic.h" + #ifndef _WIN32 #include <dlfcn.h> #else @@ -83,7 +83,7 @@ std::string DLLManager::GetVersion() const char* srcver = (char*)dlsym(h, "inspircd_src_version"); if (srcver) return srcver; - return "Unversioned module"; + return ""; } void DLLManager::RetrieveLastError() diff --git a/src/filelogger.cpp b/src/filelogger.cpp index 0575256d0..5786758da 100644 --- a/src/filelogger.cpp +++ b/src/filelogger.cpp @@ -19,15 +19,10 @@ */ -/* $Core */ - #include "inspircd.h" #include <fstream> -#include "socketengine.h" -#include "filelogger.h" -FileLogStream::FileLogStream(int loglevel, FileWriter *fw) - : LogStream(loglevel), f(fw) +FileLogStream::FileLogStream(LogLevel loglevel, FileWriter *fw) : LogStream(loglevel), f(fw) { ServerInstance->Logs->AddLoggerRef(f); } @@ -38,9 +33,9 @@ FileLogStream::~FileLogStream() ServerInstance->Logs->DelLoggerRef(f); } -void FileLogStream::OnLog(int loglevel, const std::string &type, const std::string &text) +void FileLogStream::OnLog(LogLevel loglevel, const std::string &type, const std::string &text) { - static char TIMESTR[26]; + static std::string TIMESTR; static time_t LAST = 0; if (loglevel < this->loglvl) @@ -50,14 +45,9 @@ void FileLogStream::OnLog(int loglevel, const std::string &type, const std::stri if (ServerInstance->Time() != LAST) { - time_t local = ServerInstance->Time(); - struct tm *timeinfo = localtime(&local); - - strlcpy(TIMESTR,asctime(timeinfo),26); - TIMESTR[24] = ':'; + TIMESTR = InspIRCd::TimeString(ServerInstance->Time()); LAST = ServerInstance->Time(); } - std::string out = std::string(TIMESTR) + " " + text.c_str() + "\n"; - this->f->WriteLogLine(out); + this->f->WriteLogLine(TIMESTR + " " + type + ": " + text + "\n"); } diff --git a/src/fileutils.cpp b/src/fileutils.cpp new file mode 100644 index 000000000..731e4ea01 --- /dev/null +++ b/src/fileutils.cpp @@ -0,0 +1,102 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include <fstream> + +FileReader::FileReader(const std::string& filename) +{ + Load(filename); +} + +void FileReader::Load(const std::string& filename) +{ + // If the file is stored in the file cache then we used that version instead. + ConfigFileCache::const_iterator it = ServerInstance->Config->Files.find(filename); + if (it != ServerInstance->Config->Files.end()) + { + this->lines = it->second; + } + else + { + const std::string realName = ServerInstance->Config->Paths.PrependConfig(filename); + lines.clear(); + + std::ifstream stream(realName.c_str()); + if (!stream.is_open()) + throw CoreException(filename + " does not exist or is not readable!"); + + std::string line; + while (std::getline(stream, line)) + { + lines.push_back(line); + totalSize += line.size() + 2; + } + + stream.close(); + } +} + +std::string FileReader::GetString() const +{ + std::string buffer; + for (file_cache::const_iterator it = this->lines.begin(); it != this->lines.end(); ++it) + { + buffer.append(*it); + buffer.append("\r\n"); + } + return buffer; +} + +std::string FileSystem::ExpandPath(const std::string& base, const std::string& fragment) +{ + // The fragment is an absolute path, don't modify it. + if (fragment[0] == '/' || FileSystem::StartsWithWindowsDriveLetter(fragment)) + return fragment; + + return base + '/' + fragment; +} + +bool FileSystem::FileExists(const std::string& file) +{ + struct stat sb; + if (stat(file.c_str(), &sb) == -1) + return false; + + if ((sb.st_mode & S_IFDIR) > 0) + return false; + + return !access(file.c_str(), F_OK); +} + +std::string FileSystem::GetFileName(const std::string& name) +{ +#ifdef _WIN32 + size_t pos = name.find_last_of("\\/"); +#else + size_t pos = name.rfind('/'); +#endif + return pos == std::string::npos ? name : name.substr(++pos); +} + +bool FileSystem::StartsWithWindowsDriveLetter(const std::string& path) +{ + return (path.length() > 2 && isalpha(path[0]) && path[1] == ':'); +} diff --git a/src/hashcomp.cpp b/src/hashcomp.cpp index e0347421b..8febcbb5f 100644 --- a/src/hashcomp.cpp +++ b/src/hashcomp.cpp @@ -20,11 +20,7 @@ */ -/* $Core */ - #include "inspircd.h" -#include "hashcomp.h" -#include "hash_map.h" /****************************************************** * @@ -35,7 +31,7 @@ * scene spend a lot of time debating (arguing) about * the best way to write hash functions to hash irc * nicknames, channels etc. - * We are lucky as C++ developers as hash_map does + * We are lucky as C++ developers as unordered_map does * a lot of this for us. It does intellegent memory * requests, bucketing, search functions, insertion * and deletion etc. All we have to do is write some @@ -51,111 +47,78 @@ * ******************************************************/ -/** A mapping of uppercase to lowercase, including scandinavian - * 'oddities' as specified by RFC1459, e.g. { -> [, and | -> \ - */ -unsigned const char rfc_case_insensitive_map[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, /* 0-19 */ - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, /* 20-39 */ - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, /* 40-59 */ - 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, /* 60-79 */ - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 94, 95, 96, 97, 98, 99, /* 80-99 */ - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, /* 100-119 */ - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, /* 120-139 */ - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, /* 140-159 */ - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, /* 160-179 */ - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, /* 180-199 */ - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, /* 200-219 */ - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, /* 220-239 */ - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 /* 240-255 */ -}; -/** Case insensitive map, ASCII rules. - * That is; - * [ != {, but A == a. +/** + * A case insensitive mapping of characters from upper case to lower case for + * the ASCII character set. */ unsigned const char ascii_case_insensitive_map[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, /* 0-19 */ - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, /* 20-39 */ - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, /* 40-59 */ - 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, /* 60-79 */ - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, /* 80-99 */ - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, /* 100-119 */ - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, /* 120-139 */ - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, /* 140-159 */ - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, /* 160-179 */ - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, /* 180-199 */ - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, /* 200-219 */ - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, /* 220-239 */ - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 /* 240-255 */ -}; - -/** Case sensitive map. - * Can technically also be used for ASCII case sensitive comparisons, as [ != {, etc. - */ -unsigned const char rfc_case_sensitive_map[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, - 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, - 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, - 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, - 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, - 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, - 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, - 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // 0-9 + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, // 10-19 + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, // 20-29 + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, // 30-39 + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, // 40-49 + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, // 50-59 + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, // 60-69 + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 70-79 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, // 80-89 + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, // 90-99 + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100-109 + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110-119 + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, // 120-129 + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, // 130-139 + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, // 140-149 + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 150-159 + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, // 160-169 + 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, // 170-179 + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, // 180-189 + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, // 190-199 + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, // 200-209 + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, // 210-219 + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, // 220-229 + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 230-249 + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, // 240-249 + 250, 251, 252, 253, 254, 255, // 250-255 }; -/* convert a string to lowercase. Note following special circumstances - * taken from RFC 1459. Many "official" server branches still hold to this - * rule so i will too; - * - * Because of IRC's scandanavian origin, the characters {}| are - * considered to be the lower case equivalents of the characters []\, - * respectively. This is a critical issue when determining the - * equivalence of two nicknames. - */ -void nspace::strlower(char *n) -{ - if (n) - { - for (char* t = n; *t; t++) - *t = national_case_insensitive_map[(unsigned char)*t]; - } -} - -#ifdef HASHMAP_DEPRECATED - size_t CoreExport nspace::insensitive::operator()(const std::string &s) const -#else - size_t nspace::hash<std::string>::operator()(const std::string &s) const -#endif - -{ - /* XXX: NO DATA COPIES! :) - * The hash function here is practically - * a copy of the one in STL's hash_fun.h, - * only with *x replaced with national_case_insensitive_map[*x]. - * This avoids a copy to use hash<const char*> - */ - size_t t = 0; - for (std::string::const_iterator x = s.begin(); x != s.end(); ++x) /* ++x not x++, as its faster */ - t = 5 * t + national_case_insensitive_map[(unsigned char)*x]; - return t; -} -size_t CoreExport irc::hash::operator()(const irc::string &s) const -{ - size_t t = 0; - for (irc::string::const_iterator x = s.begin(); x != s.end(); ++x) /* ++x not x++, as its faster */ - t = 5 * t + national_case_insensitive_map[(unsigned char)*x]; - return t; -} +/** + * A case insensitive mapping of characters from upper case to lower case for + * the character set of RFC 1459. This is identical to ASCII with the small + * exception of {}| being considered to be the lower case equivalents of the + * characters []\ respectively. + */ +unsigned const char rfc_case_insensitive_map[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // 0-9 + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, // 10-19 + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, // 20-29 + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, // 30-39 + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, // 40-49 + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, // 50-59 + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, // 60-69 + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, // 70-79 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, // 80-89 + 122, 123, 124, 125, 94, 95, 96, 97, 98, 99, // 90-99 + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100-109 + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110-119 + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, // 120-129 + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, // 130-139 + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, // 140-149 + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, // 150-159 + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, // 160-169 + 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, // 170-179 + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, // 180-189 + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, // 190-199 + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, // 200-209 + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, // 210-219 + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, // 220-229 + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, // 230-239 + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, // 240-249 + 250, 251, 252, 253, 254, 255, // 250-255 +}; -bool irc::StrHashComp::operator()(const std::string& s1, const std::string& s2) const +bool irc::equals(const std::string& s1, const std::string& s2) { const unsigned char* n1 = (const unsigned char*)s1.c_str(); const unsigned char* n2 = (const unsigned char*)s2.c_str(); @@ -165,315 +128,163 @@ bool irc::StrHashComp::operator()(const std::string& s1, const std::string& s2) return (national_case_insensitive_map[*n1] == national_case_insensitive_map[*n2]); } -/****************************************************** - * - * This is the implementation of our special irc::string - * class which is a case-insensitive equivalent to - * std::string which is not only case-insensitive but - * can also do scandanavian comparisons, e.g. { = [, etc. - * - * This class depends on the const array 'national_case_insensitive_map'. - * - ******************************************************/ - -bool irc::irc_char_traits::eq(char c1st, char c2nd) -{ - return national_case_insensitive_map[(unsigned char)c1st] == national_case_insensitive_map[(unsigned char)c2nd]; -} - -bool irc::irc_char_traits::ne(char c1st, char c2nd) -{ - return national_case_insensitive_map[(unsigned char)c1st] != national_case_insensitive_map[(unsigned char)c2nd]; -} - -bool irc::irc_char_traits::lt(char c1st, char c2nd) +size_t irc::find(const std::string& haystack, const std::string& needle) { - return national_case_insensitive_map[(unsigned char)c1st] < national_case_insensitive_map[(unsigned char)c2nd]; -} + // The haystack can't contain the needle if it is smaller than it. + if (needle.length() > haystack.length()) + return std::string::npos; -int irc::irc_char_traits::compare(const char* str1, const char* str2, size_t n) -{ - for(unsigned int i = 0; i < n; i++) + // The inner loop checks the characters between haystack_last and the end of the haystack. + size_t haystack_last = haystack.length() - needle.length(); + for (size_t hpos = 0; hpos <= haystack_last; ++hpos) { - if(national_case_insensitive_map[(unsigned char)*str1] > national_case_insensitive_map[(unsigned char)*str2]) - return 1; - - if(national_case_insensitive_map[(unsigned char)*str1] < national_case_insensitive_map[(unsigned char)*str2]) - return -1; - - if(*str1 == 0 || *str2 == 0) - return 0; + // Check for the needle at the current haystack position. + bool found = true; + for (size_t npos = 0; npos < needle.length(); ++npos) + { + if (national_case_insensitive_map[(unsigned char)needle[npos]] != national_case_insensitive_map[(unsigned char)haystack[hpos + npos]]) + { + // Uh-oh, characters at the current haystack position don't match. + found = false; + break; + } + } - str1++; - str2++; + // The entire needle was found in the haystack! + if (found) + return hpos; } - return 0; -} -const char* irc::irc_char_traits::find(const char* s1, int n, char c) -{ - while(n-- > 0 && national_case_insensitive_map[(unsigned char)*s1] != national_case_insensitive_map[(unsigned char)c]) - s1++; - return (n >= 0) ? s1 : NULL; -} - -irc::tokenstream::tokenstream(const std::string &source) : tokens(source), last_pushed(false) -{ - /* Record starting position and current position */ - last_starting_position = tokens.begin(); - n = tokens.begin(); + // We didn't find anything. + return std::string::npos; } -irc::tokenstream::~tokenstream() -{ -} -bool irc::tokenstream::GetToken(std::string &token) +bool irc::insensitive_swo::operator()(const std::string& a, const std::string& b) const { - std::string::iterator lsp = last_starting_position; + const unsigned char* charmap = national_case_insensitive_map; + std::string::size_type asize = a.size(); + std::string::size_type bsize = b.size(); + std::string::size_type maxsize = std::min(asize, bsize); - while (n != tokens.end()) + for (std::string::size_type i = 0; i < maxsize; i++) { - /** Skip multi space, converting " " into " " - */ - while ((n+1 != tokens.end()) && (*n == ' ') && (*(n+1) == ' ')) - n++; - - if ((last_pushed) && (*n == ':')) - { - /* If we find a token thats not the first and starts with :, - * this is the last token on the line - */ - std::string::iterator curr = ++n; - n = tokens.end(); - token = std::string(curr, tokens.end()); + unsigned char A = charmap[(unsigned char)a[i]]; + unsigned char B = charmap[(unsigned char)b[i]]; + if (A > B) + return false; + else if (A < B) return true; - } - - last_pushed = false; - - if ((*n == ' ') || (n+1 == tokens.end())) - { - /* If we find a space, or end of string, this is the end of a token. - */ - last_starting_position = n+1; - last_pushed = *n == ' '; - - std::string strip(lsp, n+1 == tokens.end() ? n+1 : n++); - while ((strip.length()) && (strip.find_last_of(' ') == strip.length() - 1)) - strip.erase(strip.end() - 1); - - token = strip; - return !token.empty(); - } - - n++; } - token.clear(); - return false; -} - -bool irc::tokenstream::GetToken(irc::string &token) -{ - std::string stdstring; - bool returnval = GetToken(stdstring); - token = assign(stdstring); - return returnval; + return (asize < bsize); } -bool irc::tokenstream::GetToken(int &token) +size_t irc::insensitive::operator()(const std::string &s) const { - std::string tok; - bool returnval = GetToken(tok); - token = ConvToInt(tok); - return returnval; -} - -bool irc::tokenstream::GetToken(long &token) -{ - std::string tok; - bool returnval = GetToken(tok); - token = ConvToInt(tok); - return returnval; + /* XXX: NO DATA COPIES! :) + * The hash function here is practically + * a copy of the one in STL's hash_fun.h, + * only with *x replaced with national_case_insensitive_map[*x]. + * This avoids a copy to use hash<const char*> + */ + size_t t = 0; + for (std::string::const_iterator x = s.begin(); x != s.end(); ++x) /* ++x not x++, as its faster */ + t = 5 * t + national_case_insensitive_map[(unsigned char)*x]; + return t; } -irc::sepstream::sepstream(const std::string &source, char seperator) : tokens(source), sep(seperator) +irc::tokenstream::tokenstream(const std::string& msg, size_t start) + : message(msg, start) + , position(0) { - last_starting_position = tokens.begin(); - n = tokens.begin(); } -bool irc::sepstream::GetToken(std::string &token) +bool irc::tokenstream::GetMiddle(std::string& token) { - std::string::iterator lsp = last_starting_position; - - while (n != tokens.end()) + // If we are past the end of the string we can't do anything. + if (position >= message.length()) { - if ((*n == sep) || (n+1 == tokens.end())) - { - last_starting_position = n+1; - token = std::string(lsp, n+1 == tokens.end() ? n+1 : n++); - - while ((token.length()) && (token.find_last_of(sep) == token.length() - 1)) - token.erase(token.end() - 1); - - if (token.empty()) - n++; - - return n == tokens.end() ? false : true; - } - - n++; + token.clear(); + return false; } - token.clear(); - return false; -} - -const std::string irc::sepstream::GetRemaining() -{ - return std::string(n, tokens.end()); -} - -bool irc::sepstream::StreamEnd() -{ - return ((n + 1) == tokens.end()); -} - -irc::sepstream::~sepstream() -{ -} - -std::string irc::hex(const unsigned char *raw, size_t rawsz) -{ - if (!rawsz) - return ""; - - /* EWW! This used to be using sprintf, which is WAY inefficient. -Special */ - - const char *hex = "0123456789abcdef"; - static char hexbuf[MAXBUF]; - - size_t i, j; - for (i = 0, j = 0; j < rawsz; ++j) + // If we can't find another separator this is the last token in the message. + size_t separator = message.find(' ', position); + if (separator == std::string::npos) { - hexbuf[i++] = hex[raw[j] / 16]; - hexbuf[i++] = hex[raw[j] % 16]; + token.assign(message, position, std::string::npos); + position = message.length(); + return true; } - hexbuf[i] = 0; - return hexbuf; + token.assign(message, position, separator - position); + position = message.find_first_not_of(' ', separator); + return true; } -CoreExport const char* irc::Spacify(const char* n) +bool irc::tokenstream::GetTrailing(std::string& token) { - static char x[MAXBUF]; - strlcpy(x,n,MAXBUF); - for (char* y = x; *y; y++) - if (*y == '_') - *y = ' '; - return x; -} - - -irc::modestacker::modestacker(bool add) : adding(add) -{ - sequence.clear(); - sequence.push_back(""); -} - -void irc::modestacker::Push(char modeletter, const std::string ¶meter) -{ - *(sequence.begin()) += modeletter; - sequence.push_back(parameter); -} + // If we are past the end of the string we can't do anything. + if (position >= message.length()) + { + token.clear(); + return false; + } -void irc::modestacker::Push(char modeletter) -{ - this->Push(modeletter,""); -} + // If this is true then we have a <trailing> token! + if (message[position] == ':') + { + token.assign(message, position + 1, std::string::npos); + position = message.length(); + return true; + } -void irc::modestacker::PushPlus() -{ - this->Push('+',""); + // There is no <trailing> token so it must be a <middle> token. + return GetMiddle(token); } -void irc::modestacker::PushMinus() +irc::sepstream::sepstream(const std::string& source, char separator, bool allowempty) + : tokens(source), sep(separator), pos(0), allow_empty(allowempty) { - this->Push('-',""); } -int irc::modestacker::GetStackedLine(std::vector<std::string> &result, int max_line_size) +bool irc::sepstream::GetToken(std::string &token) { - if (sequence.empty()) + if (this->StreamEnd()) { - return 0; + token.clear(); + return false; } - unsigned int n = 0; - int size = 1; /* Account for initial +/- char */ - int nextsize = 0; - int start = result.size(); - std::string modeline = adding ? "+" : "-"; - result.push_back(modeline); - - if (sequence.size() > 1) - nextsize = sequence[1].length() + 2; - - while (!sequence[0].empty() && (sequence.size() > 1) && (n < ServerInstance->Config->Limits.MaxModes) && ((size + nextsize) < max_line_size)) + if (!this->allow_empty) { - modeline += *(sequence[0].begin()); - if (!sequence[1].empty()) + this->pos = this->tokens.find_first_not_of(this->sep, this->pos); + if (this->pos == std::string::npos) { - result.push_back(sequence[1]); - size += nextsize; /* Account for mode character and whitespace */ + this->pos = this->tokens.length() + 1; + token.clear(); + return false; } - sequence[0].erase(sequence[0].begin()); - sequence.erase(sequence.begin() + 1); - - if (sequence.size() > 1) - nextsize = sequence[1].length() + 2; - - n++; } - result[start] = modeline; - return n; -} + size_t p = this->tokens.find(this->sep, this->pos); + if (p == std::string::npos) + p = this->tokens.length(); -irc::stringjoiner::stringjoiner(const std::string &seperator, const std::vector<std::string> &sequence, int begin, int end) -{ - if (end < begin) - return; // nothing to do here - - for (int v = begin; v < end; v++) - joined.append(sequence[v]).append(seperator); - joined.append(sequence[end]); -} - -irc::stringjoiner::stringjoiner(const std::string &seperator, const std::deque<std::string> &sequence, int begin, int end) -{ - if (end < begin) - return; // nothing to do here + token.assign(tokens, this->pos, p - this->pos); + this->pos = p + 1; - for (int v = begin; v < end; v++) - joined.append(sequence[v]).append(seperator); - joined.append(sequence[end]); + return true; } -irc::stringjoiner::stringjoiner(const std::string &seperator, const char* const* sequence, int begin, int end) +const std::string irc::sepstream::GetRemaining() { - if (end < begin) - return; // nothing to do here - - for (int v = begin; v < end; v++) - joined.append(sequence[v]).append(seperator); - joined.append(sequence[end]); + return !this->StreamEnd() ? this->tokens.substr(this->pos) : ""; } -std::string& irc::stringjoiner::GetJoined() +bool irc::sepstream::StreamEnd() { - return joined; + return this->pos > this->tokens.length(); } irc::portparser::portparser(const std::string &source, bool allow_overlapped) @@ -528,10 +339,9 @@ long irc::portparser::GetToken() std::string::size_type dash = x.rfind('-'); if (dash != std::string::npos) { - std::string sbegin = x.substr(0, dash); - std::string send = x.substr(dash+1, x.length()); + std::string sbegin(x, 0, dash); range_begin = atoi(sbegin.c_str()); - range_end = atoi(send.c_str()); + range_end = atoi(x.c_str()+dash+1); if ((range_begin > 0) && (range_end > 0) && (range_begin < 65536) && (range_end < 65536) && (range_begin < range_end)) { @@ -549,25 +359,3 @@ long irc::portparser::GetToken() return atoi(x.c_str()); } } - -/*const std::basic_string& SearchAndReplace(std::string& text, const std::string& pattern, const std::string& replace) -{ - std::string replacement; - if ((!pattern.empty()) && (!text.empty())) - { - for (std::string::size_type n = 0; n != text.length(); ++n) - { - if (text.length() >= pattern.length() && text.substr(n, pattern.length()) == pattern) - { - replacement.append(replace); - n = n + pattern.length() - 1; - } - else - { - replacement += text[n]; - } - } - } - text = replacement; - return text; -}*/ diff --git a/src/helperfuncs.cpp b/src/helperfuncs.cpp index 439320c1e..6830612c7 100644 --- a/src/helperfuncs.cpp +++ b/src/helperfuncs.cpp @@ -22,8 +22,6 @@ */ -/* $Core */ - #ifdef _WIN32 #define _CRT_RAND_S #include <stdlib.h> @@ -34,66 +32,19 @@ #include "exitcodes.h" #include <iostream> -std::string InspIRCd::GetServerDescription(const std::string& servername) -{ - std::string description; - - FOREACH_MOD(I_OnGetServerDescription,OnGetServerDescription(servername,description)); - - if (!description.empty()) - { - return description; - } - else - { - // not a remote server that can be found, it must be me. - return Config->ServerDesc; - } -} - /* Find a user record by nickname and return a pointer to it */ User* InspIRCd::FindNick(const std::string &nick) { if (!nick.empty() && isdigit(*nick.begin())) return FindUUID(nick); - - user_hash::iterator iter = this->Users->clientlist->find(nick); - - if (iter == this->Users->clientlist->end()) - /* Couldn't find it */ - return NULL; - - return iter->second; -} - -User* InspIRCd::FindNick(const char* nick) -{ - if (isdigit(*nick)) - return FindUUID(nick); - - user_hash::iterator iter = this->Users->clientlist->find(nick); - - if (iter == this->Users->clientlist->end()) - return NULL; - - return iter->second; + return FindNickOnly(nick); } User* InspIRCd::FindNickOnly(const std::string &nick) { - user_hash::iterator iter = this->Users->clientlist->find(nick); - - if (iter == this->Users->clientlist->end()) - return NULL; - - return iter->second; -} - -User* InspIRCd::FindNickOnly(const char* nick) -{ - user_hash::iterator iter = this->Users->clientlist->find(nick); + user_hash::iterator iter = this->Users->clientlist.find(nick); - if (iter == this->Users->clientlist->end()) + if (iter == this->Users->clientlist.end()) return NULL; return iter->second; @@ -101,66 +52,26 @@ User* InspIRCd::FindNickOnly(const char* nick) User *InspIRCd::FindUUID(const std::string &uid) { - user_hash::iterator finduuid = this->Users->uuidlist->find(uid); + user_hash::iterator finduuid = this->Users->uuidlist.find(uid); - if (finduuid == this->Users->uuidlist->end()) + if (finduuid == this->Users->uuidlist.end()) return NULL; return finduuid->second; } - -User *InspIRCd::FindUUID(const char *uid) -{ - return FindUUID(std::string(uid)); -} - /* find a channel record by channel name and return a pointer to it */ -Channel* InspIRCd::FindChan(const char* chan) -{ - chan_hash::iterator iter = chanlist->find(chan); - - if (iter == chanlist->end()) - /* Couldn't find it */ - return NULL; - - return iter->second; -} Channel* InspIRCd::FindChan(const std::string &chan) { - chan_hash::iterator iter = chanlist->find(chan); + chan_hash::iterator iter = chanlist.find(chan); - if (iter == chanlist->end()) + if (iter == chanlist.end()) /* Couldn't find it */ return NULL; return iter->second; } -/* Send an error notice to all users, registered or not */ -void InspIRCd::SendError(const std::string &s) -{ - for (LocalUserList::const_iterator i = this->Users->local_users.begin(); i != this->Users->local_users.end(); i++) - { - User* u = *i; - if (u->registered == REG_ALL) - { - u->WriteServ("NOTICE %s :%s",u->nick.c_str(),s.c_str()); - } - else - { - /* Unregistered connections receive ERROR, not a NOTICE */ - u->Write("ERROR :" + s); - } - } -} - -/* return channel count */ -long InspIRCd::ChannelCount() -{ - return chanlist->size(); -} - bool InspIRCd::IsValidMask(const std::string &mask) { const char* dest = mask.c_str(); @@ -190,7 +101,7 @@ bool InspIRCd::IsValidMask(const std::string &mask) if (exclamation != 1 || atsign != 1) return false; - if (mask.length() > 250) + if (mask.length() > ServerInstance->Config->Limits.GetMaxMask()) return false; return true; @@ -216,7 +127,8 @@ void InspIRCd::StripColor(std::string &sentence) else seq = 0; - if (seq || ((*i == 2) || (*i == 15) || (*i == 22) || (*i == 21) || (*i == 31))) + // Strip all control codes too except \001 for CTCP + if (seq || ((*i >= 0) && (*i < 32) && (*i != 1))) i = sentence.erase(i); else ++i; @@ -234,19 +146,20 @@ void InspIRCd::ProcessColors(file_cache& input) { std::string character; std::string replace; - special_chars(const std::string &c, const std::string &r) : character(c), replace(r) { } - } - - special[] = { - special_chars("\\002", "\002"), // Bold - special_chars("\\037", "\037"), // underline - special_chars("\\003", "\003"), // Color - special_chars("\\017", "\017"), // Stop colors - special_chars("\\u", "\037"), // Alias for underline - special_chars("\\b", "\002"), // Alias for Bold - special_chars("\\x", "\017"), // Alias for stop - special_chars("\\c", "\003"), // Alias for color - special_chars("", "") + special_chars(const std::string& c, const std::string& r) + : character(c) + , replace(r) + { + } + } special[] = { + special_chars("\\b", "\x02"), // Bold + special_chars("\\c", "\x03"), // Color + special_chars("\\i", "\x1D"), // Italic + special_chars("\\m", "\x11"), // Monospace + special_chars("\\s", "\x1E"), // Strikethrough + special_chars("\\u", "\x1F"), // Underline + special_chars("\\x", "\x0F"), // Reset + special_chars("\\", "") }; for(file_cache::iterator it = input.begin(), it_end = input.end(); it != it_end; it++) @@ -281,47 +194,35 @@ void InspIRCd::ProcessColors(file_cache& input) } /* true for valid channel name, false else */ -bool IsChannelHandler::Call(const char *chname, size_t max) +bool InspIRCd::DefaultIsChannel(const std::string& chname) { - const char *c = chname + 1; + if (chname.empty() || chname.length() > ServerInstance->Config->Limits.ChanMax) + return false; - /* check for no name - don't check for !*chname, as if it is empty, it won't be '#'! */ - if (!chname || *chname != '#') - { + if (chname[0] != '#') return false; - } - while (*c) + for (std::string::const_iterator i = chname.begin()+1; i != chname.end(); ++i) { - switch (*c) + switch (*i) { case ' ': case ',': case 7: return false; } - - c++; - } - - size_t len = c - chname; - /* too long a name - note funky pointer arithmetic here. */ - if (len > max) - { - return false; } return true; } /* true for valid nickname, false else */ -bool IsNickHandler::Call(const char* n, size_t max) +bool InspIRCd::DefaultIsNick(const std::string& n) { - if (!n || !*n) + if (n.empty() || n.length() > ServerInstance->Config->Limits.NickMax) return false; - unsigned int p = 0; - for (const char* i = n; *i; i++, p++) + for (std::string::const_iterator i = n.begin(); i != n.end(); ++i) { if ((*i >= 'A') && (*i <= '}')) { @@ -329,7 +230,7 @@ bool IsNickHandler::Call(const char* n, size_t max) continue; } - if ((((*i >= '0') && (*i <= '9')) || (*i == '-')) && (i > n)) + if ((((*i >= '0') && (*i <= '9')) || (*i == '-')) && (i != n.begin())) { /* "0"-"9", "-" can occur anywhere BUT the first char of a nickname */ continue; @@ -339,17 +240,16 @@ bool IsNickHandler::Call(const char* n, size_t max) return false; } - /* too long? or not */ - return (p <= max); + return true; } /* return true for good ident, false else */ -bool IsIdentHandler::Call(const char* n) +bool InspIRCd::DefaultIsIdent(const std::string& n) { - if (!n || !*n) + if (n.empty()) return false; - for (const char* i = n; *i; i++) + for (std::string::const_iterator i = n.begin(); i != n.end(); ++i) { if ((*i >= 'A') && (*i <= '}')) { @@ -367,36 +267,73 @@ bool IsIdentHandler::Call(const char* n) return true; } -bool IsSIDHandler::Call(const std::string &str) +bool InspIRCd::IsHost(const std::string& host) { - /* Returns true if the string given is exactly 3 characters long, - * starts with a digit, and the other two characters are A-Z or digits - */ - return ((str.length() == 3) && isdigit(str[0]) && - ((str[1] >= 'A' && str[1] <= 'Z') || isdigit(str[1])) && - ((str[2] >= 'A' && str[2] <= 'Z') || isdigit(str[2]))); -} + // Hostnames must be non-empty and shorter than the maximum hostname length. + if (host.empty() || host.length() > ServerInstance->Config->Limits.MaxHost) + return false; -/* open the proper logfile */ -bool InspIRCd::OpenLog(char**, int) -{ - if (!Config->cmdline.writelog) return true; // Skip opening default log if -nolog + unsigned int numdashes = 0; + unsigned int numdots = 0; + bool seendot = false; + const std::string::const_iterator hostend = host.end() - 1; + for (std::string::const_iterator iter = host.begin(); iter != host.end(); ++iter) + { + unsigned char chr = static_cast<unsigned char>(*iter); + + // If the current character is a label separator. + if (chr == '.') + { + numdots++; - if (Config->cmdline.startup_log.empty()) - Config->cmdline.startup_log = LOG_PATH "/startup.log"; - FILE* startup = fopen(Config->cmdline.startup_log.c_str(), "a+"); + // Consecutive separators are not allowed and dashes can not exist at the start or end + // of labels and separators must only exist between labels. + if (seendot || numdashes || iter == host.begin() || iter == hostend) + return false; + + seendot = true; + continue; + } + + // If this point is reached then the character is not a dot. + seendot = false; + + // If the current character is a dash. + if (chr == '-') + { + // Consecutive separators are not allowed and dashes can not exist at the start or end + // of labels and separators must only exist between labels. + if (seendot || numdashes >= 2 || iter == host.begin() || iter == hostend) + return false; + + numdashes += 1; + continue; + } + + // If this point is reached then the character is not a dash. + numdashes = 0; + + // Alphanumeric characters are allowed at any position. + if ((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z')) + continue; - if (!startup) - { return false; } - FileWriter* fw = new FileWriter(startup); - FileLogStream *f = new FileLogStream((Config->cmdline.forcedebug ? DEBUG : DEFAULT), fw); - - this->Logs->AddLogType("*", f, true); + // Whilst simple hostnames (e.g. localhost) are valid we do not allow the server to use + // them to prevent issues with clients that differentiate between short client and server + // prefixes by checking whether the nickname contains a dot. + return numdots; +} - return true; +bool InspIRCd::IsSID(const std::string &str) +{ + /* Returns true if the string given is exactly 3 characters long, + * starts with a digit, and the other two characters are A-Z or digits + */ + return ((str.length() == 3) && isdigit(str[0]) && + ((str[1] >= 'A' && str[1] <= 'Z') || isdigit(str[1])) && + ((str[2] >= 'A' && str[2] <= 'Z') || isdigit(str[2]))); } void InspIRCd::CheckRoot() @@ -405,38 +342,16 @@ void InspIRCd::CheckRoot() if (geteuid() == 0) { std::cout << "ERROR: You are running an irc server as root! DO NOT DO THIS!" << std::endl << std::endl; - this->Logs->Log("STARTUP",DEFAULT,"Can't start as root"); + this->Logs->Log("STARTUP", LOG_DEFAULT, "Can't start as root"); Exit(EXIT_STATUS_ROOT); } #endif } -void InspIRCd::SendWhoisLine(User* user, User* dest, int numeric, const std::string &text) -{ - std::string copy_text = text; - - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnWhoisLine, MOD_RESULT, (user, dest, numeric, copy_text)); - - if (MOD_RESULT != MOD_RES_DENY) - user->WriteServ("%d %s", numeric, copy_text.c_str()); -} - -void InspIRCd::SendWhoisLine(User* user, User* dest, int numeric, const char* format, ...) -{ - char textbuffer[MAXBUF]; - va_list argsPtr; - va_start (argsPtr, format); - vsnprintf(textbuffer, MAXBUF, format, argsPtr); - va_end(argsPtr); - - this->SendWhoisLine(user, dest, numeric, std::string(textbuffer)); -} - /** Refactored by Brain, Jun 2009. Much faster with some clever O(1) array * lookups and pointer maths. */ -long InspIRCd::Duration(const std::string &str) +unsigned long InspIRCd::Duration(const std::string &str) { unsigned char multiplier = 0; long total = 0; @@ -476,31 +391,44 @@ long InspIRCd::Duration(const std::string &str) return total + subtotal; } -bool InspIRCd::ULine(const std::string& sserver) +std::string InspIRCd::Format(va_list& vaList, const char* formatString) { - if (sserver.empty()) - return true; + static std::vector<char> formatBuffer(1024); + + while (true) + { + va_list dst; + va_copy(dst, vaList); + + int vsnret = vsnprintf(&formatBuffer[0], formatBuffer.size(), formatString, dst); + va_end(dst); + + if (vsnret > 0 && static_cast<unsigned>(vsnret) < formatBuffer.size()) + { + break; + } - return (Config->ulines.find(sserver.c_str()) != Config->ulines.end()); + formatBuffer.resize(formatBuffer.size() * 2); + } + + return std::string(&formatBuffer[0]); } -bool InspIRCd::SilentULine(const std::string& sserver) +std::string InspIRCd::Format(const char* formatString, ...) { - std::map<irc::string,bool>::iterator n = Config->ulines.find(sserver.c_str()); - if (n != Config->ulines.end()) - return n->second; - else - return false; + std::string ret; + VAFORMAT(ret, formatString, formatString); + return ret; } -std::string InspIRCd::TimeString(time_t curtime) +std::string InspIRCd::TimeString(time_t curtime, const char* format, bool utc) { #ifdef _WIN32 if (curtime < 0) curtime = 0; #endif - struct tm* timeinfo = localtime(&curtime); + struct tm* timeinfo = utc ? gmtime(&curtime) : localtime(&curtime); if (!timeinfo) { curtime = 0; @@ -514,36 +442,24 @@ std::string InspIRCd::TimeString(time_t curtime) else if (timeinfo->tm_year + 1900 < 1000) timeinfo->tm_year = 0; - return std::string(asctime(timeinfo),24); -} + // This is the default format used by asctime without the terminating new line. + if (!format) + format = "%a %b %d %Y %H:%M:%S"; -// You should only pass a single character to this. -void InspIRCd::AddExtBanChar(char c) -{ - std::string &tok = Config->data005; - std::string::size_type ebpos = tok.find(" EXTBAN=,"); + char buffer[512]; + if (!strftime(buffer, sizeof(buffer), format, timeinfo)) + buffer[0] = '\0'; - if (ebpos == std::string::npos) - { - tok.append(" EXTBAN=,"); - tok.push_back(c); - } - else - { - ebpos += 9; - while (isalpha(tok[ebpos]) && tok[ebpos] < c) - ebpos++; - tok.insert(ebpos, 1, c); - } + return buffer; } -std::string InspIRCd::GenRandomStr(int length, bool printable) +std::string InspIRCd::GenRandomStr(unsigned int length, bool printable) { char* buf = new char[length]; GenRandom(buf, length); std::string rv; rv.resize(length); - for(int i=0; i < length; i++) + for(size_t i = 0; i < length; i++) rv[i] = printable ? 0x3F + (buf[i] & 0x3F) : buf[i]; delete[] buf; return rv; @@ -559,10 +475,13 @@ unsigned long InspIRCd::GenRandomInt(unsigned long max) } // This is overridden by a higher-quality algorithm when SSL support is loaded -void GenRandomHandler::Call(char *output, size_t max) +void InspIRCd::DefaultGenRandom(char* output, size_t max) { - for(unsigned int i=0; i < max; i++) -#ifdef _WIN32 +#if defined HAS_ARC4RANDOM_BUF + arc4random_buf(output, max); +#else + for (unsigned int i = 0; i < max; ++i) +# ifdef _WIN32 { unsigned int uTemp; if(rand_s(&uTemp) != 0) @@ -570,32 +489,8 @@ void GenRandomHandler::Call(char *output, size_t max) else output[i] = uTemp; } -#else +# else output[i] = random(); +# endif #endif } - -ModResult OnCheckExemptionHandler::Call(User* user, Channel* chan, const std::string& restriction) -{ - unsigned int mypfx = chan->GetPrefixValue(user); - char minmode = 0; - std::string current; - - irc::spacesepstream defaultstream(ServerInstance->Config->ConfValue("options")->getString("exemptchanops")); - - while (defaultstream.GetToken(current)) - { - std::string::size_type pos = current.find(':'); - if (pos == std::string::npos) - continue; - if (current.substr(0,pos) == restriction) - minmode = current[pos+1]; - } - - ModeHandler* mh = ServerInstance->Modes->FindMode(minmode, MODETYPE_CHANNEL); - if (mh && mypfx >= mh->GetPrefixRank()) - return MOD_RES_ALLOW; - if (mh || minmode == '*') - return MOD_RES_DENY; - return MOD_RES_PASSTHRU; -} diff --git a/src/inspircd.cpp b/src/inspircd.cpp index 66f9bfdc5..aa3fb9612 100644 --- a/src/inspircd.cpp +++ b/src/inspircd.cpp @@ -26,9 +26,7 @@ */ -/* $Core */ #include "inspircd.h" -#include "inspircd_version.h" #include <signal.h> #ifndef _WIN32 @@ -37,12 +35,6 @@ #include <sys/resource.h> #include <dlfcn.h> #include <getopt.h> - - /* Some systems don't define RUSAGE_SELF. This should fix them. */ - #ifndef RUSAGE_SELF - #define RUSAGE_SELF 0 - #endif - #include <pwd.h> // setuid #include <grp.h> // setgid #else @@ -54,12 +46,7 @@ #include <fstream> #include <iostream> #include "xline.h" -#include "bancache.h" -#include "socketengine.h" -#include "socket.h" -#include "command_parse.h" #include "exitcodes.h" -#include "caller.h" #include "testsuite.h" InspIRCd* ServerInstance = NULL; @@ -78,28 +65,26 @@ unsigned const char *national_case_insensitive_map = rfc_case_insensitive_map; */ const char* ExitCodes[] = { - "No error", /* 0 */ - "DIE command", /* 1 */ - "execv() failed", /* 2 */ - "Internal error", /* 3 */ - "Config file error", /* 4 */ - "Logfile error", /* 5 */ - "POSIX fork failed", /* 6 */ - "Bad commandline parameters", /* 7 */ - "No ports could be bound", /* 8 */ - "Can't write PID file", /* 9 */ - "SocketEngine could not initialize", /* 10 */ - "Refusing to start up as root", /* 11 */ - "Found a <die> tag!", /* 12 */ - "Couldn't load module on startup", /* 13 */ - "Could not create windows forked process", /* 14 */ - "Received SIGTERM", /* 15 */ - "Bad command handler loaded", /* 16 */ - "RegisterServiceCtrlHandler failed", /* 17 */ - "UpdateSCMStatus failed", /* 18 */ - "CreateEvent failed" /* 19 */ + "No error", // 0 + "DIE command", // 1 + "Config file error", // 2 + "Logfile error", // 3 + "POSIX fork failed", // 4 + "Bad commandline parameters", // 5 + "Can't write PID file", // 6 + "SocketEngine could not initialize", // 7 + "Refusing to start up as root", // 8 + "Couldn't load module on startup", // 9 + "Received SIGTERM" // 10 }; +#ifdef INSPIRCD_ENABLE_TESTSUITE +/** True if we have been told to run the testsuite from the commandline, + * rather than entering the mainloop. + */ +static int do_testsuite = 0; +#endif + template<typename T> static void DeleteZero(T*&n) { T* t = n; @@ -109,125 +94,29 @@ template<typename T> static void DeleteZero(T*&n) void InspIRCd::Cleanup() { + // Close all listening sockets for (unsigned int i = 0; i < ports.size(); i++) { - /* This calls the constructor and closes the listening socket */ ports[i]->cull(); delete ports[i]; } ports.clear(); - /* Close all client sockets, or the new process inherits them */ - LocalUserList::reverse_iterator i = Users->local_users.rbegin(); - while (i != this->Users->local_users.rend()) - { - User* u = *i++; - Users->QuitUser(u, "Server shutdown"); - } - GlobalCulls.Apply(); Modules->UnloadAll(); /* Delete objects dynamically allocated in constructor (destructor would be more appropriate, but we're likely exiting) */ /* Must be deleted before modes as it decrements modelines */ if (FakeClient) + { + delete FakeClient->server; FakeClient->cull(); - if (Res) - Res->cull(); + } DeleteZero(this->FakeClient); - DeleteZero(this->Users); - DeleteZero(this->Modes); DeleteZero(this->XLines); - DeleteZero(this->Parser); - DeleteZero(this->stats); - DeleteZero(this->Modules); - DeleteZero(this->BanCache); - DeleteZero(this->SNO); DeleteZero(this->Config); - DeleteZero(this->Res); - DeleteZero(this->chanlist); - DeleteZero(this->PI); - DeleteZero(this->Threads); - DeleteZero(this->Timers); - DeleteZero(this->SE); - /* Close logging */ - this->Logs->CloseLogs(); - DeleteZero(this->Logs); -} - -void InspIRCd::Restart(const std::string &reason) -{ - /* SendError flushes each client's queue, - * regardless of writeability state - */ - this->SendError(reason); - - /* Figure out our filename (if theyve renamed it, we're boned) */ - std::string me; - - char** argv = Config->cmdline.argv; - -#ifdef _WIN32 - char module[MAX_PATH]; - if (GetModuleFileNameA(NULL, module, MAX_PATH)) - me = module; -#else - me = argv[0]; -#endif - - this->Cleanup(); - - if (execv(me.c_str(), argv) == -1) - { - /* Will raise a SIGABRT if not trapped */ - throw CoreException(std::string("Failed to execv()! error: ") + strerror(errno)); - } -} - -void InspIRCd::ResetMaxBans() -{ - for (chan_hash::const_iterator i = chanlist->begin(); i != chanlist->end(); i++) - i->second->ResetMaxBans(); -} - -/** Because hash_map doesn't free its buckets when we delete items, we occasionally - * recreate the hash to free them up. - * We do this by copying the entries from the old hash to a new hash, causing all - * empty buckets to be weeded out of the hash. - * Since this is quite expensive, it's not done very often. - */ -void InspIRCd::RehashUsersAndChans() -{ - user_hash* old_users = Users->clientlist; - Users->clientlist = new user_hash; - for (user_hash::const_iterator n = old_users->begin(); n != old_users->end(); n++) - Users->clientlist->insert(*n); - delete old_users; - - user_hash* old_uuid = Users->uuidlist; - Users->uuidlist = new user_hash; - for (user_hash::const_iterator n = old_uuid->begin(); n != old_uuid->end(); n++) - Users->uuidlist->insert(*n); - delete old_uuid; - - chan_hash* old_chans = chanlist; - chanlist = new chan_hash; - for (chan_hash::const_iterator n = old_chans->begin(); n != old_chans->end(); n++) - chanlist->insert(*n); - delete old_chans; - - // Reset the already_sent IDs so we don't wrap it around and drop a message - LocalUser::already_sent_id = 0; - for (LocalUserList::const_iterator i = Users->local_users.begin(); i != Users->local_users.end(); i++) - { - (**i).already_sent = 0; - (**i).RemoveExpiredInvites(); - } - - // HACK: ELines are not expired properly at the moment but it can't be fixed as - // the 2.0 XLine system is a spaghetti nightmare. Instead we skip over expired - // ELines in XLineManager::CheckELines() and expire them here instead. - ServerInstance->XLines->GetAll("E"); + SocketEngine::Deinit(); + Logs->CloseLogs(); } void InspIRCd::SetSignals() @@ -243,15 +132,10 @@ void InspIRCd::SetSignals() signal(SIGTERM, InspIRCd::SetSignal); } -void InspIRCd::QuickExit(int status) -{ - exit(status); -} - // Required for returning the proper value of EXIT_SUCCESS for the parent process static void VoidSignalHandler(int signalreceived) { - exit(0); + exit(EXIT_STATUS_NOERROR); } bool InspIRCd::DaemonSeed() @@ -260,11 +144,11 @@ bool InspIRCd::DaemonSeed() std::cout << "InspIRCd Process ID: " << con_green << GetCurrentProcessId() << con_reset << std::endl; return true; #else - // Do not use QuickExit here: It will exit with status SIGTERM which would break e.g. daemon scripts + // Do not use exit() here: It will exit with status SIGTERM which would break e.g. daemon scripts signal(SIGTERM, VoidSignalHandler); - int childpid; - if ((childpid = fork ()) < 0) + int childpid = fork(); + if (childpid < 0) return false; else if (childpid > 0) { @@ -277,7 +161,7 @@ bool InspIRCd::DaemonSeed() */ while (kill(childpid, 0) != -1) sleep(1); - exit(0); + exit(EXIT_STATUS_NOERROR); } setsid (); std::cout << "InspIRCd Process ID: " << con_green << getpid() << con_reset << std::endl; @@ -287,13 +171,13 @@ bool InspIRCd::DaemonSeed() rlimit rl; if (getrlimit(RLIMIT_CORE, &rl) == -1) { - this->Logs->Log("STARTUP",DEFAULT,"Failed to getrlimit()!"); + this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to getrlimit()!"); return false; } rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_CORE, &rl) == -1) - this->Logs->Log("STARTUP",DEFAULT,"setrlimit() failed, cannot increase coredump size."); + this->Logs->Log("STARTUP", LOG_DEFAULT, "setrlimit() failed, cannot increase coredump size."); return true; #endif @@ -302,9 +186,15 @@ bool InspIRCd::DaemonSeed() void InspIRCd::WritePID(const std::string& filename, bool exitonfail) { #ifndef _WIN32 + if (!ServerInstance->Config->cmdline.writepid) + { + this->Logs->Log("STARTUP", LOG_DEFAULT, "--nopid specified on command line; PID file not written."); + return; + } + std::string fname(filename); if (fname.empty()) - fname = DATA_PATH "/inspircd.pid"; + fname = ServerInstance->Config->Paths.PrependData("inspircd.pid"); std::ofstream outfile(fname.c_str()); if (outfile.is_open()) { @@ -312,93 +202,50 @@ void InspIRCd::WritePID(const std::string& filename, bool exitonfail) outfile.close(); } else - {
- if (exitonfail)
+ { + if (exitonfail) std::cout << "Failed to write PID-file '" << fname << "', exiting." << std::endl; - this->Logs->Log("STARTUP",DEFAULT,"Failed to write PID-file '%s'%s",fname.c_str(), (exitonfail ? ", exiting." : ""));
+ this->Logs->Log("STARTUP", LOG_DEFAULT, "Failed to write PID-file '%s'%s", fname.c_str(), (exitonfail ? ", exiting." : "")); if (exitonfail) - Exit(EXIT_STATUS_PID);
+ Exit(EXIT_STATUS_PID); } #endif } InspIRCd::InspIRCd(int argc, char** argv) : - ConfigFileName(CONFIG_PATH "/inspircd.conf"), + ConfigFileName(INSPIRCD_CONFIG_PATH "/inspircd.conf"), + PI(&DefaultProtocolInterface), /* Functor pointer initialisation. * * THIS MUST MATCH THE ORDER OF DECLARATION OF THE FUNCTORS, e.g. the methods * themselves within the class. */ - NICKForced("NICKForced", NULL), - OperQuit("OperQuit", NULL), - GenRandom(&HandleGenRandom), - IsChannel(&HandleIsChannel), - IsSID(&HandleIsSID), - Rehash(&HandleRehash), - IsNick(&HandleIsNick), - IsIdent(&HandleIsIdent), - FloodQuitUser(&HandleFloodQuitUser), - OnCheckExemption(&HandleOnCheckExemption) + GenRandom(&DefaultGenRandom), + IsChannel(&DefaultIsChannel), + IsNick(&DefaultIsNick), + IsIdent(&DefaultIsIdent) { ServerInstance = this; - Extensions.Register(&NICKForced); - Extensions.Register(&OperQuit); - FailedPortList pl; + // Flag variables passed to getopt_long() later int do_version = 0, do_nofork = 0, do_debug = 0, - do_nolog = 0, do_root = 0, do_testsuite = 0; /* flag variables */ - int c = 0; + do_nolog = 0, do_nopid = 0, do_root = 0; // Initialize so that if we exit before proper initialization they're not deleted - this->Logs = 0; - this->Threads = 0; - this->PI = 0; - this->Users = 0; - this->chanlist = 0; this->Config = 0; - this->SNO = 0; - this->BanCache = 0; - this->Modules = 0; - this->stats = 0; - this->Timers = 0; - this->Parser = 0; this->XLines = 0; - this->Modes = 0; - this->Res = 0; this->ConfigThread = NULL; this->FakeClient = NULL; UpdateTime(); this->startup_time = TIME.tv_sec; - // This must be created first, so other parts of Insp can use it while starting up - this->Logs = new LogManager; - - SE = CreateSocketEngine(); - - this->Threads = new ThreadEngine; - - /* Default implementation does nothing */ - this->PI = new ProtocolInterface; - - this->s_signal = 0; - - // Create base manager classes early, so nothing breaks - this->Users = new UserManager; - - this->Users->clientlist = new user_hash(); - this->Users->uuidlist = new user_hash(); - this->chanlist = new chan_hash(); + SocketEngine::Init(); this->Config = new ServerConfig; - this->SNO = new SnomaskManager; - this->BanCache = new BanCacheManager; - this->Modules = new ModuleManager(); - this->stats = new serverstats(); - this->Timers = new TimerManager; - this->Parser = new CommandParser; + dynamic_reference_base::reset_all(); this->XLines = new XLineManager; this->Config->cmdline.argv = argv; @@ -424,31 +271,49 @@ InspIRCd::InspIRCd(int argc, char** argv) : srandom(TIME.tv_nsec ^ TIME.tv_sec); #endif + { + ServiceProvider* provs[] = + { + &rfcevents.numeric, &rfcevents.join, &rfcevents.part, &rfcevents.kick, &rfcevents.quit, &rfcevents.nick, + &rfcevents.mode, &rfcevents.topic, &rfcevents.privmsg, &rfcevents.invite, &rfcevents.ping, &rfcevents.pong, + &rfcevents.error + }; + Modules.AddServices(provs, sizeof(provs)/sizeof(provs[0])); + } + struct option longopts[] = { { "nofork", no_argument, &do_nofork, 1 }, - { "logfile", required_argument, NULL, 'f' }, { "config", required_argument, NULL, 'c' }, { "debug", no_argument, &do_debug, 1 }, { "nolog", no_argument, &do_nolog, 1 }, + { "nopid", no_argument, &do_nopid, 1 }, { "runasroot", no_argument, &do_root, 1 }, { "version", no_argument, &do_version, 1 }, +#ifdef INSPIRCD_ENABLE_TESTSUITE { "testsuite", no_argument, &do_testsuite, 1 }, +#endif { 0, 0, 0, 0 } }; + int c; int index; - while ((c = getopt_long(argc, argv, ":c:f:", longopts, &index)) != -1) + while ((c = getopt_long(argc, argv, ":c:", longopts, &index)) != -1) { switch (c) { - case 'f': - /* Log filename was set */ - Config->cmdline.startup_log = optarg; - break; case 'c': /* Config filename was set */ ConfigFileName = optarg; +#ifdef _WIN32 + TCHAR configPath[MAX_PATH + 1]; + if (GetFullPathName(optarg, MAX_PATH, configPath, NULL) > 0) + ConfigFileName = configPath; +#else + char configPath[PATH_MAX + 1]; + if (realpath(optarg, configPath)) + ConfigFileName = configPath; +#endif break; case 0: /* getopt_long_only() set an int variable, just keep going */ @@ -458,19 +323,21 @@ InspIRCd::InspIRCd(int argc, char** argv) : default: /* Fall through to handle other weird values too */ std::cout << "Unknown parameter '" << argv[optind-1] << "'" << std::endl; - std::cout << "Usage: " << argv[0] << " [--nofork] [--nolog] [--debug] [--logfile <filename>] " << std::endl << - std::string(static_cast<int>(8+strlen(argv[0])), ' ') << "[--runasroot] [--version] [--config <config>] [--testsuite]" << std::endl; + std::cout << "Usage: " << argv[0] << " [--nofork] [--nolog] [--nopid] [--debug] [--config <config>]" << std::endl << + std::string(static_cast<size_t>(8+strlen(argv[0])), ' ') << "[--runasroot] [--version]" << std::endl; Exit(EXIT_STATUS_ARGV); break; } } +#ifdef INSPIRCD_ENABLE_TESTSUITE if (do_testsuite) do_nofork = do_debug = true; +#endif if (do_version) { - std::cout << std::endl << VERSION << " r" << REVISION << std::endl; + std::cout << std::endl << INSPIRCD_VERSION << std::endl; Exit(EXIT_STATUS_NOERROR); } @@ -484,28 +351,23 @@ InspIRCd::InspIRCd(int argc, char** argv) : Config->cmdline.nofork = (do_nofork != 0); Config->cmdline.forcedebug = (do_debug != 0); Config->cmdline.writelog = !do_nolog; - Config->cmdline.TestSuite = (do_testsuite != 0); + Config->cmdline.writepid = !do_nopid; if (do_debug) { - FileWriter* fw = new FileWriter(stdout); - FileLogStream* fls = new FileLogStream(RAWIO, fw); + FileWriter* fw = new FileWriter(stdout, 1); + FileLogStream* fls = new FileLogStream(LOG_RAWIO, fw); Logs->AddLogTypes("*", fls, true); } - else if (!this->OpenLog(argv, argc)) - { - std::cout << "ERROR: Could not open initial logfile " << Config->cmdline.startup_log << ": " << strerror(errno) << std::endl << std::endl; - Exit(EXIT_STATUS_LOG); - } - if (!ServerConfig::FileExists(ConfigFileName.c_str())) + if (!FileSystem::FileExists(ConfigFileName)) { #ifdef _WIN32 /* Windows can (and defaults to) hide file extensions, so let's play a bit nice for windows users. */ std::string txtconf = this->ConfigFileName; txtconf.append(".txt"); - if (ServerConfig::FileExists(txtconf.c_str())) + if (FileSystem::FileExists(txtconf)) { ConfigFileName = txtconf; } @@ -513,34 +375,27 @@ InspIRCd::InspIRCd(int argc, char** argv) : #endif { std::cout << "ERROR: Cannot open config file: " << ConfigFileName << std::endl << "Exiting..." << std::endl; - this->Logs->Log("STARTUP",DEFAULT,"Unable to open config file %s", ConfigFileName.c_str()); + this->Logs->Log("STARTUP", LOG_DEFAULT, "Unable to open config file %s", ConfigFileName.c_str()); Exit(EXIT_STATUS_CONFIG); } } - std::cout << con_green << "Inspire Internet Relay Chat Server" << con_reset << ", compiled on " __DATE__ " at " __TIME__ << std::endl; - std::cout << con_green << "(C) InspIRCd Development Team." << con_reset << std::endl << std::endl; - std::cout << "Developers:" << std::endl; - std::cout << con_green << "\tBrain, FrostyCoolSlug, w00t, Om, Special, peavey" << std::endl; - std::cout << "\taquanight, psychon, dz, danieldg, jackmcbarn" << std::endl;
- std::cout << "\tAttila" << con_reset << std::endl << std::endl; - std::cout << "Others:\t\t\t" << con_green << "See /INFO Output" << con_reset << std::endl; - - this->Modes = new ModeParser; + std::cout << con_green << "InspIRCd - Internet Relay Chat Daemon" << con_reset << std::endl; + std::cout << "For contributors & authors: " << con_green << "See /INFO Output" << con_reset << std::endl; #ifndef _WIN32 if (!do_root) this->CheckRoot(); else { - std::cout << "* WARNING * WARNING * WARNING * WARNING * WARNING *" << std::endl
- << "YOU ARE RUNNING INSPIRCD AS ROOT. THIS IS UNSUPPORTED" << std::endl
- << "AND IF YOU ARE HACKED, CRACKED, SPINDLED OR MUTILATED" << std::endl
- << "OR ANYTHING ELSE UNEXPECTED HAPPENS TO YOU OR YOUR" << std::endl
- << "SERVER, THEN IT IS YOUR OWN FAULT. IF YOU DID NOT MEAN" << std::endl
- << "TO START INSPIRCD AS ROOT, HIT CTRL+C NOW AND RESTART" << std::endl
- << "THE PROGRAM AS A NORMAL USER. YOU HAVE BEEN WARNED!" << std::endl << std::endl
- << "InspIRCd starting in 20 seconds, ctrl+c to abort..." << std::endl;
+ std::cout << "* WARNING * WARNING * WARNING * WARNING * WARNING *" << std::endl + << "YOU ARE RUNNING INSPIRCD AS ROOT. THIS IS UNSUPPORTED" << std::endl + << "AND IF YOU ARE HACKED, CRACKED, SPINDLED OR MUTILATED" << std::endl + << "OR ANYTHING ELSE UNEXPECTED HAPPENS TO YOU OR YOUR" << std::endl + << "SERVER, THEN IT IS YOUR OWN FAULT. IF YOU DID NOT MEAN" << std::endl + << "TO START INSPIRCD AS ROOT, HIT CTRL+C NOW AND RESTART" << std::endl + << "THE PROGRAM AS A NORMAL USER. YOU HAVE BEEN WARNED!" << std::endl << std::endl + << "InspIRCd starting in 20 seconds, ctrl+c to abort..." << std::endl; sleep(20); } #endif @@ -552,47 +407,31 @@ InspIRCd::InspIRCd(int argc, char** argv) : if (!this->DaemonSeed()) { std::cout << "ERROR: could not go into daemon mode. Shutting down." << std::endl; - Logs->Log("STARTUP", DEFAULT, "ERROR: could not go into daemon mode. Shutting down."); + Logs->Log("STARTUP", LOG_DEFAULT, "ERROR: could not go into daemon mode. Shutting down."); Exit(EXIT_STATUS_FORK); } } - SE->RecoverFromFork(); + SocketEngine::RecoverFromFork(); - /* During startup we don't actually initialize this - * in the thread engine. + /* During startup we read the configuration now, not in + * a seperate thread */ this->Config->Read(); this->Config->Apply(NULL, ""); Logs->OpenFileLogs(); - this->Res = new DNS(); - - /* - * Initialise SID/UID. - * For an explanation as to exactly how this works, and why it works this way, see GetUID(). - * -- w00t - */ + // If we don't have a SID, generate one based on the server name and the server description if (Config->sid.empty()) - { - // Generate one - unsigned int sid = 0; - char sidstr[4]; - - for (const char* x = Config->ServerName.c_str(); *x; ++x) - sid = 5 * sid + *x; - for (const char* y = Config->ServerDesc.c_str(); *y; ++y) - sid = 5 * sid + *y; - sprintf(sidstr, "%03d", sid % 1000); + Config->sid = UIDGenerator::GenerateSID(Config->ServerName, Config->ServerDesc); - Config->sid = sidstr; - } + // Initialize the UID generator with our sid + this->UIDGen.init(Config->sid); - /* set up fake client again this time with the correct uid */ - this->FakeClient = new FakeUser(Config->sid, Config->ServerName); + // Create the server user for this server + this->FakeClient = new FakeUser(Config->sid, Config->ServerName, Config->ServerDesc); - // Get XLine to do it's thing. - this->XLines->CheckELines(); + // This is needed as all new XLines are marked pending until ApplyLines() is called this->XLines->ApplyLines(); int bounditems = BindPorts(pl); @@ -601,9 +440,9 @@ InspIRCd::InspIRCd(int argc, char** argv) : this->Modules->LoadAll(); - /* Just in case no modules were loaded - fix for bug #101 */ - this->BuildISupport(); - Config->ApplyDisabledCommands(Config->DisabledCommands); + // Build ISupport as ModuleManager::LoadAll() does not do it + this->ISupport.Build(); + Config->ApplyDisabledCommands(); if (!pl.empty()) { @@ -613,13 +452,13 @@ InspIRCd::InspIRCd(int argc, char** argv) : int j = 1; for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++) { - std::cout << j << ".\tAddress: " << (i->first.empty() ? "<all>" : i->first) << " \tReason: " << i->second << std::endl; + std::cout << j << ".\tAddress: " << i->first.str() << " \tReason: " << strerror(i->second) << std::endl; } std::cout << std::endl << "Hint: Try using a public IP instead of blank or *" << std::endl; } - std::cout << "InspIRCd is now running as '" << Config->ServerName << "'[" << Config->GetSID() << "] with " << SE->GetMaxFds() << " max open sockets" << std::endl; + std::cout << "InspIRCd is now running as '" << Config->ServerName << "'[" << Config->GetSID() << "] with " << SocketEngine::GetMaxFds() << " max open sockets" << std::endl; #ifndef _WIN32 if (!Config->cmdline.nofork) @@ -627,7 +466,7 @@ InspIRCd::InspIRCd(int argc, char** argv) : if (kill(getppid(), SIGTERM) == -1) { std::cout << "Error killing parent process: " << strerror(errno) << std::endl; - Logs->Log("STARTUP", DEFAULT, "Error killing parent process: %s",strerror(errno)); + Logs->Log("STARTUP", LOG_DEFAULT, "Error killing parent process: %s",strerror(errno)); } } @@ -641,7 +480,7 @@ InspIRCd::InspIRCd(int argc, char** argv) : * * -- nenolod */ - if ((!do_nofork) && (!do_testsuite) && (!Config->cmdline.forcedebug)) + if ((!do_nofork) && (!Config->cmdline.forcedebug)) { int fd = open("/dev/null", O_RDWR); @@ -650,16 +489,16 @@ InspIRCd::InspIRCd(int argc, char** argv) : fclose(stdout); if (dup2(fd, STDIN_FILENO) < 0) - Logs->Log("STARTUP", DEFAULT, "Failed to dup /dev/null to stdin."); + Logs->Log("STARTUP", LOG_DEFAULT, "Failed to dup /dev/null to stdin."); if (dup2(fd, STDOUT_FILENO) < 0) - Logs->Log("STARTUP", DEFAULT, "Failed to dup /dev/null to stdout."); + Logs->Log("STARTUP", LOG_DEFAULT, "Failed to dup /dev/null to stdout."); if (dup2(fd, STDERR_FILENO) < 0) - Logs->Log("STARTUP", DEFAULT, "Failed to dup /dev/null to stderr."); + Logs->Log("STARTUP", LOG_DEFAULT, "Failed to dup /dev/null to stderr."); close(fd); } else { - Logs->Log("STARTUP", DEFAULT,"Keeping pseudo-tty open as we are running in the foreground."); + Logs->Log("STARTUP", LOG_DEFAULT, "Keeping pseudo-tty open as we are running in the foreground."); } #else /* Set win32 service as running, if we are running as a service */ @@ -671,68 +510,53 @@ InspIRCd::InspIRCd(int argc, char** argv) : FreeConsole(); } - QueryPerformanceFrequency(&stats->QPFrequency); + QueryPerformanceFrequency(&stats.QPFrequency); #endif - Logs->Log("STARTUP", DEFAULT, "Startup complete as '%s'[%s], %d max open sockets", Config->ServerName.c_str(),Config->GetSID().c_str(), SE->GetMaxFds()); + Logs->Log("STARTUP", LOG_DEFAULT, "Startup complete as '%s'[%s], %lu max open sockets", Config->ServerName.c_str(),Config->GetSID().c_str(), SocketEngine::GetMaxFds()); #ifndef _WIN32 - std::string SetUser = Config->ConfValue("security")->getString("runasuser"); - std::string SetGroup = Config->ConfValue("security")->getString("runasgroup"); + ConfigTag* security = Config->ConfValue("security"); + + const std::string SetGroup = security->getString("runasgroup"); if (!SetGroup.empty()) { - int ret; - - // setgroups - ret = setgroups(0, NULL); - - if (ret == -1) + errno = 0; + if (setgroups(0, NULL) == -1) { - this->Logs->Log("SETGROUPS", DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno)); - this->QuickExit(0); + this->Logs->Log("STARTUP", LOG_DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno)); + exit(EXIT_STATUS_CONFIG); } - // setgid - struct group *g; - - errno = 0; - g = getgrnam(SetGroup.c_str()); - + struct group* g = getgrnam(SetGroup.c_str()); if (!g) { - this->Logs->Log("SETGUID", DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno)); - this->QuickExit(0); + this->Logs->Log("STARTUP", LOG_DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno)); + exit(EXIT_STATUS_CONFIG); } - ret = setgid(g->gr_gid); - - if (ret == -1) + if (setgid(g->gr_gid) == -1) { - this->Logs->Log("SETGUID", DEFAULT, "setgid() failed (wrong group?): %s", strerror(errno)); - this->QuickExit(0); + this->Logs->Log("STARTUP", LOG_DEFAULT, "setgid(%d) failed (wrong group?): %s", g->gr_gid, strerror(errno)); + exit(EXIT_STATUS_CONFIG); } } + const std::string SetUser = security->getString("runasuser"); if (!SetUser.empty()) { - // setuid - struct passwd *u; - errno = 0; - u = getpwnam(SetUser.c_str()); - + struct passwd* u = getpwnam(SetUser.c_str()); if (!u) { - this->Logs->Log("SETGUID", DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno)); - this->QuickExit(0); + this->Logs->Log("STARTUP", LOG_DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno)); + exit(EXIT_STATUS_CONFIG); } - int ret = setuid(u->pw_uid); - - if (ret == -1) + if (setuid(u->pw_uid) == -1) { - this->Logs->Log("SETGUID", DEFAULT, "setuid() failed (wrong user?): %s", strerror(errno)); - this->QuickExit(0); + this->Logs->Log("STARTUP", LOG_DEFAULT, "setuid(%d) failed (wrong user?): %s", u->pw_uid, strerror(errno)); + exit(EXIT_STATUS_CONFIG); } } @@ -760,15 +584,17 @@ void InspIRCd::UpdateTime() #endif } -int InspIRCd::Run() +void InspIRCd::Run() { +#ifdef INSPIRCD_ENABLE_TESTSUITE /* See if we're supposed to be running the test suite rather than entering the mainloop */ - if (Config->cmdline.TestSuite) + if (do_testsuite) { TestSuite* ts = new TestSuite; delete ts; - Exit(0); + return; } +#endif UpdateTime(); time_t OLDTIME = TIME.tv_sec; @@ -783,7 +609,7 @@ int InspIRCd::Run() if (this->ConfigThread && this->ConfigThread->IsDone()) { /* Rehash has completed */ - this->Logs->Log("CONFIG",DEBUG,"Detected ConfigThread exiting, tidying up..."); + this->Logs->Log("CONFIG", LOG_DEBUG, "Detected ConfigThread exiting, tidying up..."); this->ConfigThread->Finish(); @@ -803,45 +629,49 @@ int InspIRCd::Run() { #ifndef _WIN32 getrusage(RUSAGE_SELF, &ru); - stats->LastSampled = TIME; - stats->LastCPU = ru.ru_utime; + stats.LastSampled = TIME; + stats.LastCPU = ru.ru_utime; #else - if(QueryPerformanceCounter(&stats->LastSampled)) + if(QueryPerformanceCounter(&stats.LastSampled)) { FILETIME CreationTime; FILETIME ExitTime; FILETIME KernelTime; FILETIME UserTime; GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime); - stats->LastCPU.dwHighDateTime = KernelTime.dwHighDateTime + UserTime.dwHighDateTime; - stats->LastCPU.dwLowDateTime = KernelTime.dwLowDateTime + UserTime.dwLowDateTime; + stats.LastCPU.dwHighDateTime = KernelTime.dwHighDateTime + UserTime.dwHighDateTime; + stats.LastCPU.dwLowDateTime = KernelTime.dwLowDateTime + UserTime.dwLowDateTime; } #endif /* Allow a buffer of two seconds drift on this so that ntpdate etc dont harass admins */ if (TIME.tv_sec < OLDTIME - 2) { - SNO->WriteToSnoMask('d', "\002EH?!\002 -- Time is flowing BACKWARDS in this dimension! Clock drifted backwards %lu secs.", (unsigned long)(OLDTIME-TIME.tv_sec)); + SNO->WriteToSnoMask('a', "\002EH?!\002 -- Time is flowing BACKWARDS in this dimension! Clock drifted backwards %lu secs.", (unsigned long)(OLDTIME-TIME.tv_sec)); } else if (TIME.tv_sec > OLDTIME + 2) { - SNO->WriteToSnoMask('d', "\002EH?!\002 -- Time is jumping FORWARDS! Clock skipped %lu secs.", (unsigned long)(TIME.tv_sec - OLDTIME)); + SNO->WriteToSnoMask('a', "\002EH?!\002 -- Time is jumping FORWARDS! Clock skipped %lu secs.", (unsigned long)(TIME.tv_sec - OLDTIME)); } -
+ OLDTIME = TIME.tv_sec; if ((TIME.tv_sec % 3600) == 0) { - this->RehashUsersAndChans(); - FOREACH_MOD(I_OnGarbageCollect, OnGarbageCollect()); + FOREACH_MOD(OnGarbageCollect, ()); + + // HACK: ELines are not expired properly at the moment but it can't be fixed as + // the 2.0 XLine system is a spaghetti nightmare. Instead we skip over expired + // ELines in XLineManager::CheckELines() and expire them here instead. + XLines->GetAll("E"); } - Timers->TickTimers(TIME.tv_sec); - this->DoBackgroundUserStuff(); + Timers.TickTimers(TIME.tv_sec); + Users->DoBackgroundUserStuff(); if ((TIME.tv_sec % 5) == 0) { - FOREACH_MOD(I_OnBackgroundTimer,OnBackgroundTimer(TIME.tv_sec)); + FOREACH_MOD(OnBackgroundTimer, (TIME.tv_sec)); SNO->FlushSnotices(); } } @@ -853,8 +683,8 @@ int InspIRCd::Run() * This will cause any read or write events to be * dispatched to their handlers. */ - this->SE->DispatchTrialWrites(); - this->SE->DispatchEvents(); + SocketEngine::DispatchTrialWrites(); + SocketEngine::DispatchEvents(); /* if any users were quit, take them out */ GlobalCulls.Apply(); @@ -866,25 +696,6 @@ int InspIRCd::Run() s_signal = 0; } } - - return 0; -} - -/**********************************************************************************/ - -/** - * An ircd in five lines! bwahahaha. ahahahahaha. ahahah *cough*. - */ - -/* this returns true when all modules are satisfied that the user should be allowed onto the irc server - * (until this returns true, a user will block in the waiting state, waiting to connect up to the - * registration timeout maximum seconds) - */ -bool InspIRCd::AllModulesReportReady(LocalUser* user) -{ - ModResult res; - FIRST_MOD_RESULT(OnCheckReady, res, (user)); - return (res == MOD_RES_PASSTHRU); } sig_atomic_t InspIRCd::s_signal = 0; diff --git a/src/inspsocket.cpp b/src/inspsocket.cpp index 356904f74..69c427212 100644 --- a/src/inspsocket.cpp +++ b/src/inspsocket.cpp @@ -23,17 +23,15 @@ #include "inspircd.h" -#include "socket.h" -#include "inspstring.h" -#include "socketengine.h" +#include "iohook.h" -#ifndef DISABLE_WRITEV -#include <sys/uio.h> -#endif - -#ifndef IOV_MAX -#define IOV_MAX 1024 -#endif +static IOHook* GetNextHook(IOHook* hook) +{ + IOHookMiddle* const iohm = IOHookMiddle::ToMiddleHook(hook); + if (iohm) + return iohm->GetNextHook(); + return NULL; +} BufferedSocket::BufferedSocket() { @@ -47,10 +45,10 @@ BufferedSocket::BufferedSocket(int newfd) this->fd = newfd; this->state = I_CONNECTED; if (fd > -1) - ServerInstance->SE->AddFd(this, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE); + SocketEngine::AddFd(this, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE); } -void BufferedSocket::DoConnect(const std::string &ipaddr, int aport, unsigned long maxtime, const std::string &connectbindip) +void BufferedSocket::DoConnect(const std::string& ipaddr, int aport, unsigned int maxtime, const std::string& connectbindip) { BufferedSocketError err = BeginConnect(ipaddr, aport, maxtime, connectbindip); if (err != I_ERR_NONE) @@ -61,12 +59,12 @@ void BufferedSocket::DoConnect(const std::string &ipaddr, int aport, unsigned lo } } -BufferedSocketError BufferedSocket::BeginConnect(const std::string &ipaddr, int aport, unsigned long maxtime, const std::string &connectbindip) +BufferedSocketError BufferedSocket::BeginConnect(const std::string& ipaddr, int aport, unsigned int maxtime, const std::string& connectbindip) { irc::sockets::sockaddrs addr, bind; if (!irc::sockets::aptosa(ipaddr, aport, addr)) { - ServerInstance->Logs->Log("SOCKET", DEBUG, "BUG: Hostname passed to BufferedSocket, rather than an IP address!"); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: Hostname passed to BufferedSocket, rather than an IP address!"); return I_ERR_CONNECT; } @@ -82,23 +80,23 @@ BufferedSocketError BufferedSocket::BeginConnect(const std::string &ipaddr, int return BeginConnect(addr, bind, maxtime); } -BufferedSocketError BufferedSocket::BeginConnect(const irc::sockets::sockaddrs& dest, const irc::sockets::sockaddrs& bind, unsigned long timeout) +BufferedSocketError BufferedSocket::BeginConnect(const irc::sockets::sockaddrs& dest, const irc::sockets::sockaddrs& bind, unsigned int timeout) { if (fd < 0) - fd = socket(dest.sa.sa_family, SOCK_STREAM, 0); + fd = socket(dest.family(), SOCK_STREAM, 0); if (fd < 0) return I_ERR_SOCKET; - if (bind.sa.sa_family != 0) + if (bind.family() != 0) { - if (ServerInstance->SE->Bind(fd, bind) < 0) + if (SocketEngine::Bind(fd, bind) < 0) return I_ERR_BIND; } - ServerInstance->SE->NonBlocking(fd); + SocketEngine::NonBlocking(fd); - if (ServerInstance->SE->Connect(this, &dest.sa, sa_size(dest)) == -1) + if (SocketEngine::Connect(this, dest) == -1) { if (errno != EINPROGRESS) return I_ERR_CONNECT; @@ -106,13 +104,13 @@ BufferedSocketError BufferedSocket::BeginConnect(const irc::sockets::sockaddrs& this->state = I_CONNECTING; - if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE | FD_WRITE_WILL_BLOCK)) + if (!SocketEngine::AddFd(this, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE | FD_WRITE_WILL_BLOCK)) return I_ERR_NOMOREFDS; - this->Timeout = new SocketTimeout(this->GetFd(), this, timeout, ServerInstance->Time()); - ServerInstance->Timers->AddTimer(this->Timeout); + this->Timeout = new SocketTimeout(this->GetFd(), this, timeout); + ServerInstance->Timers.AddTimer(this->Timeout); - ServerInstance->Logs->Log("SOCKET", DEBUG,"BufferedSocket::DoConnect success"); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BufferedSocket::DoConnect success"); return I_ERR_NONE; } @@ -122,23 +120,18 @@ void StreamSocket::Close() { // final chance, dump as much of the sendq as we can DoWrite(); - if (IOHook) + + IOHook* hook = GetIOHook(); + DelIOHook(); + while (hook) { - try - { - IOHook->OnStreamSocketClose(this); - } - catch (CoreException& modexcept) - { - ServerInstance->Logs->Log("SOCKET", DEFAULT,"%s threw an exception: %s", - modexcept.GetSource(), modexcept.GetReason()); - } - IOHook = NULL; + hook->OnStreamSocketClose(this); + IOHook* const nexthook = GetNextHook(hook); + delete hook; + hook = nexthook; } - ServerInstance->SE->Shutdown(this, 2); - ServerInstance->SE->DelFd(this); - ServerInstance->SE->Close(this); - fd = -1; + SocketEngine::Shutdown(this, 2); + SocketEngine::Close(this); } } @@ -153,67 +146,79 @@ bool StreamSocket::GetNextLine(std::string& line, char delim) std::string::size_type i = recvq.find(delim); if (i == std::string::npos) return false; - line = recvq.substr(0, i); - // TODO is this the most efficient way to split? - recvq = recvq.substr(i + 1); + line.assign(recvq, 0, i); + recvq.erase(0, i + 1); return true; } -void StreamSocket::DoRead() +int StreamSocket::HookChainRead(IOHook* hook, std::string& rq) { - if (IOHook) + if (!hook) + return ReadToRecvQ(rq); + + IOHookMiddle* const iohm = IOHookMiddle::ToMiddleHook(hook); + if (iohm) { - int rv = -1; - try - { - rv = IOHook->OnStreamSocketRead(this, recvq); - } - catch (CoreException& modexcept) - { - ServerInstance->Logs->Log("SOCKET", DEFAULT, "%s threw an exception: %s", - modexcept.GetSource(), modexcept.GetReason()); - return; - } - if (rv > 0) - OnDataReady(); - if (rv < 0) - SetError("Read Error"); // will not overwrite a better error message + // Call the next hook to put data into the recvq of the current hook + const int ret = HookChainRead(iohm->GetNextHook(), iohm->GetRecvQ()); + if (ret <= 0) + return ret; } - else + return hook->OnStreamSocketRead(this, rq); +} + +void StreamSocket::DoRead() +{ + const std::string::size_type prevrecvqsize = recvq.size(); + + const int result = HookChainRead(GetIOHook(), recvq); + if (result < 0) { + SetError("Read Error"); // will not overwrite a better error message + return; + } + + if (recvq.size() > prevrecvqsize) + OnDataReady(); +} + +int StreamSocket::ReadToRecvQ(std::string& rq) +{ char* ReadBuffer = ServerInstance->GetReadBuffer(); - int n = ServerInstance->SE->Recv(this, ReadBuffer, ServerInstance->Config->NetBufferSize, 0); + int n = SocketEngine::Recv(this, ReadBuffer, ServerInstance->Config->NetBufferSize, 0); if (n == ServerInstance->Config->NetBufferSize) { - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_READ | FD_ADD_TRIAL_READ); - recvq.append(ReadBuffer, n); - OnDataReady(); + SocketEngine::ChangeEventMask(this, FD_WANT_FAST_READ | FD_ADD_TRIAL_READ); + rq.append(ReadBuffer, n); } else if (n > 0) { - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_READ); - recvq.append(ReadBuffer, n); - OnDataReady(); + SocketEngine::ChangeEventMask(this, FD_WANT_FAST_READ); + rq.append(ReadBuffer, n); } else if (n == 0) { error = "Connection closed"; - ServerInstance->SE->ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); + return -1; } else if (SocketEngine::IgnoreError()) { - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_READ | FD_READ_WILL_BLOCK); + SocketEngine::ChangeEventMask(this, FD_WANT_FAST_READ | FD_READ_WILL_BLOCK); + return 0; } else if (errno == EINTR) { - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_READ | FD_ADD_TRIAL_READ); + SocketEngine::ChangeEventMask(this, FD_WANT_FAST_READ | FD_ADD_TRIAL_READ); + return 0; } else { error = SocketEngine::LastError(); - ServerInstance->SE->ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); + return -1; } - } + return n; } /* Don't try to prepare huge blobs of data to send to a blocked socket */ @@ -221,120 +226,56 @@ static const int MYIOV_MAX = IOV_MAX < 128 ? IOV_MAX : 128; void StreamSocket::DoWrite() { - if (sendq.empty()) + if (getSendQSize() == 0) return; - if (!error.empty() || fd < 0 || fd == INT_MAX) + if (!error.empty() || fd < 0) { - ServerInstance->Logs->Log("SOCKET", DEBUG, "DoWrite on errored or closed socket"); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "DoWrite on errored or closed socket"); return; } -#ifndef DISABLE_WRITEV - if (IOHook) -#endif + SendQueue* psendq = &sendq; + IOHook* hook = GetIOHook(); + while (hook) { - int rv = -1; - try - { - while (error.empty() && !sendq.empty()) - { - if (sendq.size() > 1 && sendq[0].length() < 1024) - { - // Avoid multiple repeated SSL encryption invocations - // This adds a single copy of the queue, but avoids - // much more overhead in terms of system calls invoked - // by the IOHook. - // - // The length limit of 1024 is to prevent merging strings - // more than once when writes begin to block. - std::string tmp; - tmp.reserve(1280); - while (!sendq.empty() && tmp.length() < 1024) - { - tmp.append(sendq.front()); - sendq.pop_front(); - } - sendq.push_front(tmp); - } - std::string& front = sendq.front(); - int itemlen = front.length(); - if (IOHook) - { - rv = IOHook->OnStreamSocketWrite(this, front); - if (rv > 0) - { - // consumed the entire string, and is ready for more - sendq_len -= itemlen; - sendq.pop_front(); - } - else if (rv == 0) - { - // socket has blocked. Stop trying to send data. - // IOHook has requested unblock notification from the socketengine + int rv = hook->OnStreamSocketWrite(this, *psendq); + psendq = NULL; - // Since it is possible that a partial write took place, adjust sendq_len - sendq_len = sendq_len - itemlen + front.length(); - return; - } - else - { - SetError("Write Error"); // will not overwrite a better error message - return; - } - } -#ifdef DISABLE_WRITEV - else - { - rv = ServerInstance->SE->Send(this, front.data(), itemlen, 0); - if (rv == 0) - { - SetError("Connection closed"); - return; - } - else if (rv < 0) - { - if (errno == EINTR || SocketEngine::IgnoreError()) - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_WRITE | FD_WRITE_WILL_BLOCK); - else - SetError(SocketEngine::LastError()); - return; - } - else if (rv < itemlen) - { - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_WRITE | FD_WRITE_WILL_BLOCK); - front = front.substr(rv); - sendq_len -= rv; - return; - } - else - { - sendq_len -= itemlen; - sendq.pop_front(); - if (sendq.empty()) - ServerInstance->SE->ChangeEventMask(this, FD_WANT_EDGE_WRITE); - } - } -#endif - } + // rv == 0 means the socket has blocked. Stop trying to send data. + // IOHook has requested unblock notification from the socketengine. + if (rv == 0) + break; + + if (rv < 0) + { + SetError("Write Error"); // will not overwrite a better error message + break; } - catch (CoreException& modexcept) + + IOHookMiddle* const iohm = IOHookMiddle::ToMiddleHook(hook); + hook = NULL; + if (iohm) { - ServerInstance->Logs->Log("SOCKET", DEBUG,"%s threw an exception: %s", - modexcept.GetSource(), modexcept.GetReason()); + psendq = &iohm->GetSendQ(); + hook = iohm->GetNextHook(); } } -#ifndef DISABLE_WRITEV - else - { + + if (psendq) + FlushSendQ(*psendq); +} + +void StreamSocket::FlushSendQ(SendQueue& sq) +{ // don't even try if we are known to be blocking if (GetEventMask() & FD_WRITE_WILL_BLOCK) return; // start out optimistic - we won't need to write any more int eventChange = FD_WANT_EDGE_WRITE; - while (error.empty() && sendq_len && eventChange == FD_WANT_EDGE_WRITE) + while (error.empty() && !sq.empty() && eventChange == FD_WANT_EDGE_WRITE) { // Prepare a writev() call to write all buffers efficiently - int bufcount = sendq.size(); + int bufcount = sq.size(); // cap the number of buffers at MYIOV_MAX if (bufcount > MYIOV_MAX) @@ -343,22 +284,25 @@ void StreamSocket::DoWrite() } int rv_max = 0; - iovec* iovecs = new iovec[bufcount]; - for(int i=0; i < bufcount; i++) + int rv; { - iovecs[i].iov_base = const_cast<char*>(sendq[i].data()); - iovecs[i].iov_len = sendq[i].length(); - rv_max += sendq[i].length(); + SocketEngine::IOVector iovecs[MYIOV_MAX]; + size_t j = 0; + for (SendQueue::const_iterator i = sq.begin(), end = i+bufcount; i != end; ++i, j++) + { + const SendQueue::Element& elem = *i; + iovecs[j].iov_base = const_cast<char*>(elem.data()); + iovecs[j].iov_len = elem.length(); + rv_max += iovecs[j].iov_len; + } + rv = SocketEngine::WriteV(this, iovecs, bufcount); } - int rv = writev(fd, iovecs, bufcount); - delete[] iovecs; - if (rv == (int)sendq_len) + if (rv == (int)sq.bytes()) { // it's our lucky day, everything got written out. Fast cleanup. // This won't ever happen if the number of buffers got capped. - sendq_len = 0; - sendq.clear(); + sq.clear(); } else if (rv > 0) { @@ -368,20 +312,19 @@ void StreamSocket::DoWrite() // it's going to block now eventChange = FD_WANT_FAST_WRITE | FD_WRITE_WILL_BLOCK; } - sendq_len -= rv; - while (rv > 0 && !sendq.empty()) + while (rv > 0 && !sq.empty()) { - std::string& front = sendq.front(); + const SendQueue::Element& front = sq.front(); if (front.length() <= (size_t)rv) { // this string got fully written out rv -= front.length(); - sendq.pop_front(); + sq.pop_front(); } else { // stopped in the middle of this string - front = front.substr(rv); + sq.erase_front(rv); rv = 0; } } @@ -407,38 +350,38 @@ void StreamSocket::DoWrite() if (!error.empty()) { // error - kill all events - ServerInstance->SE->ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE); } else { - ServerInstance->SE->ChangeEventMask(this, eventChange); + SocketEngine::ChangeEventMask(this, eventChange); } - } -#endif } void StreamSocket::WriteData(const std::string &data) { if (fd < 0) { - ServerInstance->Logs->Log("SOCKET", DEBUG, "Attempt to write data to dead socket: %s", + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Attempt to write data to dead socket: %s", data.c_str()); return; } /* Append the data to the back of the queue ready for writing */ sendq.push_back(data); - sendq_len += data.length(); - ServerInstance->SE->ChangeEventMask(this, FD_ADD_TRIAL_WRITE); + SocketEngine::ChangeEventMask(this, FD_ADD_TRIAL_WRITE); } -void SocketTimeout::Tick(time_t) +bool SocketTimeout::Tick(time_t) { - ServerInstance->Logs->Log("SOCKET", DEBUG,"SocketTimeout::Tick"); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "SocketTimeout::Tick"); - if (ServerInstance->SE->GetRef(this->sfd) != this->sock) - return; + if (SocketEngine::GetRef(this->sfd) != this->sock) + { + delete this; + return false; + } if (this->sock->state == I_CONNECTING) { @@ -454,90 +397,143 @@ void SocketTimeout::Tick(time_t) } this->sock->Timeout = NULL; + delete this; + return false; } void BufferedSocket::OnConnected() { } void BufferedSocket::OnTimeout() { return; } -void BufferedSocket::DoWrite() +void BufferedSocket::OnEventHandlerWrite() { if (state == I_CONNECTING) { state = I_CONNECTED; this->OnConnected(); - if (GetIOHook()) - GetIOHook()->OnStreamSocketConnect(this); - else - ServerInstance->SE->ChangeEventMask(this, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE); + if (!GetIOHook()) + SocketEngine::ChangeEventMask(this, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE); } - this->StreamSocket::DoWrite(); + this->StreamSocket::OnEventHandlerWrite(); } BufferedSocket::~BufferedSocket() { this->Close(); - if (Timeout) + // The timer is removed from the TimerManager in Timer::~Timer() + delete Timeout; +} + +void StreamSocket::OnEventHandlerError(int errornum) +{ + if (!error.empty()) + return; + + if (errornum == 0) + SetError("Connection closed"); + else + SetError(SocketEngine::GetError(errornum)); + + BufferedSocketError errcode = I_ERR_OTHER; + switch (errornum) { - ServerInstance->Timers->DelTimer(Timeout); - Timeout = NULL; + case ETIMEDOUT: + errcode = I_ERR_TIMEOUT; + break; + case ECONNREFUSED: + case 0: + errcode = I_ERR_CONNECT; + break; + case EADDRINUSE: + errcode = I_ERR_BIND; + break; + case EPIPE: + case EIO: + errcode = I_ERR_WRITE; + break; } + + // Log and call OnError() + CheckError(errcode); } -void StreamSocket::HandleEvent(EventType et, int errornum) +void StreamSocket::OnEventHandlerRead() { if (!error.empty()) return; - BufferedSocketError errcode = I_ERR_OTHER; - try { - switch (et) - { - case EVENT_ERROR: - { - if (errornum == 0) - SetError("Connection closed"); - else - SetError(SocketEngine::GetError(errornum)); - switch (errornum) - { - case ETIMEDOUT: - errcode = I_ERR_TIMEOUT; - break; - case ECONNREFUSED: - case 0: - errcode = I_ERR_CONNECT; - break; - case EADDRINUSE: - errcode = I_ERR_BIND; - break; - case EPIPE: - case EIO: - errcode = I_ERR_WRITE; - break; - } - break; - } - case EVENT_READ: - { - DoRead(); - break; - } - case EVENT_WRITE: - { - DoWrite(); - break; - } - } + + try + { + DoRead(); } catch (CoreException& ex) { - ServerInstance->Logs->Log("SOCKET", DEFAULT, "Caught exception in socket processing on FD %d - '%s'", - fd, ex.GetReason()); + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Caught exception in socket processing on FD %d - '%s'", fd, ex.GetReason().c_str()); SetError(ex.GetReason()); } + CheckError(I_ERR_OTHER); +} + +void StreamSocket::OnEventHandlerWrite() +{ + if (!error.empty()) + return; + + DoWrite(); + CheckError(I_ERR_OTHER); +} + +void StreamSocket::CheckError(BufferedSocketError errcode) +{ if (!error.empty()) { - ServerInstance->Logs->Log("SOCKET", DEBUG, "Error on FD %d - '%s'", fd, error.c_str()); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Error on FD %d - '%s'", fd, error.c_str()); OnError(errcode); } } +IOHook* StreamSocket::GetModHook(Module* mod) const +{ + for (IOHook* curr = GetIOHook(); curr; curr = GetNextHook(curr)) + { + if (curr->prov->creator == mod) + return curr; + } + return NULL; +} + +void StreamSocket::AddIOHook(IOHook* newhook) +{ + IOHook* curr = GetIOHook(); + if (!curr) + { + iohook = newhook; + return; + } + + IOHookMiddle* lasthook; + while (curr) + { + lasthook = IOHookMiddle::ToMiddleHook(curr); + if (!lasthook) + return; + curr = lasthook->GetNextHook(); + } + + lasthook->SetNextHook(newhook); +} + +size_t StreamSocket::getSendQSize() const +{ + size_t ret = sendq.bytes(); + IOHook* curr = GetIOHook(); + while (curr) + { + const IOHookMiddle* const iohm = IOHookMiddle::ToMiddleHook(curr); + if (!iohm) + break; + + ret += iohm->GetSendQ().bytes(); + curr = iohm->GetNextHook(); + } + return ret; +} diff --git a/src/inspstring.cpp b/src/inspstring.cpp index 534b7ce00..79aef52bd 100644 --- a/src/inspstring.cpp +++ b/src/inspstring.cpp @@ -21,140 +21,18 @@ #include "inspircd.h" -/* - * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL - * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef HAS_STRLCPY -CoreExport size_t strlcat(char *dst, const char *src, size_t siz) -{ - char *d = dst; - const char *s = src; - size_t n = siz, dlen; - - while (n-- != 0 && *d != '\0') - d++; - - dlen = d - dst; - n = siz - dlen; - - if (n == 0) - return(dlen + strlen(s)); - - while (*s != '\0') - { - if (n != 1) - { - *d++ = *s; - n--; - } - - s++; - } - - *d = '\0'; - return(dlen + (s - src)); /* count does not include NUL */ -} - -CoreExport size_t strlcpy(char *dst, const char *src, size_t siz) -{ - char *d = dst; - const char *s = src; - size_t n = siz; - - /* Copy as many bytes as will fit */ - if (n != 0 && --n != 0) - { - do - { - if ((*d++ = *s++) == 0) - break; - } while (--n != 0); - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) - { - if (siz != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++); - } - - return(s - src - 1); /* count does not include NUL */ -} -#endif - -CoreExport int charlcat(char* x,char y,int z) -{ - char* x__n = x; - int v = 0; - - while(*x__n++) - v++; - - if (v < z - 1) - { - *--x__n = y; - *++x__n = 0; - } - - return v; -} - -CoreExport bool charremove(char* mp, char remove) -{ - char* mptr = mp; - bool shift_down = false; - - while (*mptr) - { - if (*mptr == remove) - shift_down = true; - - if (shift_down) - *mptr = *(mptr+1); - - mptr++; - } - - return shift_down; -} - static const char hextable[] = "0123456789abcdef"; -std::string BinToHex(const std::string& data) +std::string BinToHex(const void* raw, size_t l) { - int l = data.length(); + const char* data = static_cast<const char*>(raw); std::string rv; rv.reserve(l * 2); - for(int i=0; i < l; i++) + for (size_t i = 0; i < l; i++) { unsigned char c = data[i]; - rv.append(1, hextable[c >> 4]); - rv.append(1, hextable[c & 0xF]); + rv.push_back(hextable[c >> 4]); + rv.push_back(hextable[c & 0xF]); } return rv; } @@ -230,3 +108,125 @@ std::string Base64ToBin(const std::string& data_str, const char* table) } return rv; } + +bool InspIRCd::TimingSafeCompare(const std::string& one, const std::string& two) +{ + if (one.length() != two.length()) + return false; + + unsigned int diff = 0; + for (std::string::const_iterator i = one.begin(), j = two.begin(); i != one.end(); ++i, ++j) + { + unsigned char a = static_cast<unsigned char>(*i); + unsigned char b = static_cast<unsigned char>(*j); + diff |= a ^ b; + } + + return (diff == 0); +} + +void TokenList::AddList(const std::string& tokenlist) +{ + std::string token; + irc::spacesepstream tokenstream(tokenlist); + while (tokenstream.GetToken(token)) + { + if (token[0] == '-') + Remove(token.substr(1)); + else + Add(token); + } + +} +void TokenList::Add(const std::string& token) +{ + // If the token is empty or contains just whitespace it is invalid. + if (token.empty() || token.find_first_not_of(" \t") == std::string::npos) + return; + + // If the token is a wildcard entry then permissive mode has been enabled. + if (token == "*") + { + permissive = true; + tokens.clear(); + return; + } + + // If we are in permissive mode then remove the token from the token list. + // Otherwise, add it to the token list. + if (permissive) + tokens.erase(token); + else + tokens.insert(token); +} + +void TokenList::Clear() +{ + permissive = false; + tokens.clear(); +} + +bool TokenList::Contains(const std::string& token) const +{ + // If we are in permissive mode and the token is in the list + // then we don't have it. + if (permissive && tokens.find(token) != tokens.end()) + return false; + + // If we are not in permissive mode and the token is not in + // the list then we don't have it. + if (!permissive && tokens.find(token) == tokens.end()) + return false; + + // We have the token! + return true; +} + +void TokenList::Remove(const std::string& token) +{ + // If the token is empty or contains just whitespace it is invalid. + if (token.empty() || token.find_first_not_of(" \t") == std::string::npos) + return; + + // If the token is a wildcard entry then permissive mode has been disabled. + if (token == "*") + { + permissive = false; + tokens.clear(); + return; + } + + // If we are in permissive mode then add the token to the token list. + // Otherwise, remove it from the token list. + if (permissive) + tokens.insert(token); + else + tokens.erase(token); +} + +std::string TokenList::ToString() const +{ + std::string buffer(permissive ? "* " : "-* "); + buffer.append(stdalgo::string::join(tokens)); + return buffer; +} + +bool TokenList::operator==(const TokenList& other) const +{ + // Both sets must be in the same mode to be equal. + if (permissive != other.permissive) + return false; + + // Both sets must be the same size to be equal. + if (tokens.size() != other.tokens.size()) + return false; + + for (insp::flat_set<std::string>::const_iterator iter = tokens.begin(); iter != tokens.end(); ++iter) + { + // Both sets must contain the same tokens to be equal. + if (other.tokens.find(*iter) == other.tokens.end()) + return false; + } + + return true; +} diff --git a/src/listensocket.cpp b/src/listensocket.cpp index ae11c3b48..60ee0b449 100644 --- a/src/listensocket.cpp +++ b/src/listensocket.cpp @@ -19,16 +19,17 @@ #include "inspircd.h" -#include "socket.h" -#include "socketengine.h" +#include "iohook.h" + +#ifndef _WIN32 +#include <netinet/tcp.h> +#endif ListenSocket::ListenSocket(ConfigTag* tag, const irc::sockets::sockaddrs& bind_to) : bind_tag(tag) + , bind_sa(bind_to) { - irc::sockets::satoap(bind_to, bind_addr, bind_port); - bind_desc = irc::sockets::satouser(bind_to); - - fd = socket(bind_to.sa.sa_family, SOCK_STREAM, 0); + fd = socket(bind_to.family(), SOCK_STREAM, 0); if (this->fd == -1) return; @@ -40,7 +41,7 @@ ListenSocket::ListenSocket(ConfigTag* tag, const irc::sockets::sockaddrs& bind_t * is "::" or an IPv6 address, disable support so that an IPv4 bind will * work on the port (by us or another application). */ - if (bind_to.sa.sa_family == AF_INET6) + if (bind_to.family() == AF_INET6) { std::string addr = tag->getString("address"); /* This must be >= sizeof(DWORD) on Windows */ @@ -51,23 +52,53 @@ ListenSocket::ListenSocket(ConfigTag* tag, const irc::sockets::sockaddrs& bind_t } #endif - ServerInstance->SE->SetReuse(fd); - int rv = ServerInstance->SE->Bind(this->fd, bind_to); + if (tag->getBool("free")) + { + socklen_t enable = 1; +#if defined IP_FREEBIND // Linux 2.4+ + setsockopt(fd, SOL_IP, IP_FREEBIND, &enable, sizeof(enable)); +#elif defined IP_BINDANY // FreeBSD + setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(enable)); +#elif defined SO_BINDANY // NetBSD/OpenBSD + setsockopt(fd, SOL_SOCKET, SO_BINDANY, &enable, sizeof(enable)); +#else + (void)enable; +#endif + } + + SocketEngine::SetReuse(fd); + int rv = SocketEngine::Bind(this->fd, bind_to); if (rv >= 0) - rv = ServerInstance->SE->Listen(this->fd, ServerInstance->Config->MaxConn); + rv = SocketEngine::Listen(this->fd, ServerInstance->Config->MaxConn); + + // Default defer to on for TLS listeners because in TLS the client always speaks first + int timeout = tag->getDuration("defer", (tag->getString("ssl").empty() ? 0 : 3)); + if (timeout && !rv) + { +#if defined TCP_DEFER_ACCEPT + setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, sizeof(timeout)); +#elif defined SO_ACCEPTFILTER + struct accept_filter_arg afa; + memset(&afa, 0, sizeof(afa)); + strcpy(afa.af_name, "dataready"); + setsockopt(fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)); +#endif + } if (rv < 0) { int errstore = errno; - ServerInstance->SE->Shutdown(this, 2); - ServerInstance->SE->Close(this); + SocketEngine::Shutdown(this, 2); + SocketEngine::Close(this->GetFd()); this->fd = -1; errno = errstore; } else { - ServerInstance->SE->NonBlocking(this->fd); - ServerInstance->SE->AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::NonBlocking(this->fd); + SocketEngine::AddFd(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + + this->ResetIOHookProvider(); } } @@ -75,58 +106,39 @@ ListenSocket::~ListenSocket() { if (this->GetFd() > -1) { - ServerInstance->SE->DelFd(this); - ServerInstance->Logs->Log("SOCKET", DEBUG,"Shut down listener on fd %d", this->fd); - ServerInstance->SE->Shutdown(this, 2); - if (ServerInstance->SE->Close(this) != 0) - ServerInstance->Logs->Log("SOCKET", DEBUG,"Failed to cancel listener: %s", strerror(errno)); - this->fd = -1; + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Shut down listener on fd %d", this->fd); + SocketEngine::Shutdown(this, 2); + + if (SocketEngine::Close(this) != 0) + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Failed to cancel listener: %s", strerror(errno)); + + if (bind_sa.family() == AF_UNIX && unlink(bind_sa.un.sun_path)) + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Failed to unlink UNIX socket: %s", strerror(errno)); } } -/* Just seperated into another func for tidiness really.. */ -void ListenSocket::AcceptInternal() +void ListenSocket::OnEventHandlerRead() { irc::sockets::sockaddrs client; - irc::sockets::sockaddrs server; + irc::sockets::sockaddrs server(bind_sa); socklen_t length = sizeof(client); - int incomingSockfd = ServerInstance->SE->Accept(this, &client.sa, &length); + int incomingSockfd = SocketEngine::Accept(this, &client.sa, &length); - ServerInstance->Logs->Log("SOCKET",DEBUG,"HandleEvent for Listensocket %s nfd=%d", bind_desc.c_str(), incomingSockfd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Accepting connection on socket %s fd %d", bind_sa.str().c_str(), incomingSockfd); if (incomingSockfd < 0) { - ServerInstance->stats->statsRefused++; + ServerInstance->stats.Refused++; return; } socklen_t sz = sizeof(server); if (getsockname(incomingSockfd, &server.sa, &sz)) { - ServerInstance->Logs->Log("SOCKET", DEBUG, "Can't get peername: %s", strerror(errno)); - irc::sockets::aptosa(bind_addr, bind_port, server); - } - - /* - * XXX - - * this is done as a safety check to keep the file descriptors within range of fd_ref_table. - * its a pretty big but for the moment valid assumption: - * file descriptors are handed out starting at 0, and are recycled as theyre freed. - * therefore if there is ever an fd over 65535, 65536 clients must be connected to the - * irc server at once (or the irc server otherwise initiating this many connections, files etc) - * which for the time being is a physical impossibility (even the largest networks dont have more - * than about 10,000 users on ONE server!) - */ - if (incomingSockfd >= ServerInstance->SE->GetMaxFds()) - { - ServerInstance->Logs->Log("SOCKET", DEBUG, "Server is full"); - ServerInstance->SE->Shutdown(incomingSockfd, 2); - ServerInstance->SE->Close(incomingSockfd); - ServerInstance->stats->statsRefused++; - return; + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Can't get peername: %s", strerror(errno)); } - if (client.sa.sa_family == AF_INET6) + if (client.family() == AF_INET6) { /* * This case is the be all and end all patch to catch and nuke 4in6 @@ -155,15 +167,23 @@ void ListenSocket::AcceptInternal() memcpy(&server.in4.sin_addr.s_addr, server.in6.sin6_addr.s6_addr + 12, sizeof(uint32_t)); } } + else if (client.family() == AF_UNIX) + { + // Clients connecting via UNIX sockets don't have paths so give them + // the server path as defined in RFC 1459 section 8.1.1. + // + // strcpy is safe here because sizeof(sockaddr_un.sun_path) is equal on both. + strcpy(client.un.sun_path, server.un.sun_path); + } - ServerInstance->SE->NonBlocking(incomingSockfd); + SocketEngine::NonBlocking(incomingSockfd); ModResult res; FIRST_MOD_RESULT(OnAcceptConnection, res, (incomingSockfd, this, &client, &server)); if (res == MOD_RES_PASSTHRU) { std::string type = bind_tag->getString("type", "clients"); - if (type == "clients") + if (stdalgo::string::equalsci(type, "clients")) { ServerInstance->Users->AddUser(incomingSockfd, this, &client, &server); res = MOD_RES_ALLOW; @@ -171,29 +191,34 @@ void ListenSocket::AcceptInternal() } if (res == MOD_RES_ALLOW) { - ServerInstance->stats->statsAccept++; + ServerInstance->stats.Accept++; } else { - ServerInstance->stats->statsRefused++; - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Refusing connection on %s - %s", - bind_desc.c_str(), res == MOD_RES_DENY ? "Connection refused by module" : "Module for this port not found"); - ServerInstance->SE->Close(incomingSockfd); + ServerInstance->stats.Refused++; + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Refusing connection on %s - %s", + bind_sa.str().c_str(), res == MOD_RES_DENY ? "Connection refused by module" : "Module for this port not found"); + SocketEngine::Close(incomingSockfd); } } -void ListenSocket::HandleEvent(EventType e, int err) +void ListenSocket::ResetIOHookProvider() { - switch (e) + iohookprovs[0].SetProvider(bind_tag->getString("hook")); + + // Check that all non-last hooks support being in the middle + for (IOHookProvList::iterator i = iohookprovs.begin(); i != iohookprovs.end()-1; ++i) { - case EVENT_ERROR: - ServerInstance->Logs->Log("SOCKET",DEFAULT,"ListenSocket::HandleEvent() received a socket engine error event! well shit! '%s'", strerror(err)); - break; - case EVENT_WRITE: - ServerInstance->Logs->Log("SOCKET",DEBUG,"*** BUG *** ListenSocket::HandleEvent() got a WRITE event!!!"); - break; - case EVENT_READ: - this->AcceptInternal(); - break; + IOHookProvRef& curr = *i; + // Ignore if cannot be in the middle + if ((curr) && (!curr->IsMiddle())) + curr.SetProvider(std::string()); } + + std::string provname = bind_tag->getString("ssl"); + if (!provname.empty()) + provname.insert(0, "ssl/"); + + // SSL should be the last + iohookprovs.back().SetProvider(provname); } diff --git a/src/listmode.cpp b/src/listmode.cpp new file mode 100644 index 000000000..74977b866 --- /dev/null +++ b/src/listmode.cpp @@ -0,0 +1,239 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "inspircd.h" +#include "listmode.h" + +ListModeBase::ListModeBase(Module* Creator, const std::string& Name, char modechar, const std::string &eolstr, unsigned int lnum, unsigned int eolnum, bool autotidy, const std::string &ctag) + : ModeHandler(Creator, Name, modechar, PARAM_ALWAYS, MODETYPE_CHANNEL, MC_LIST), + listnumeric(lnum), endoflistnumeric(eolnum), endofliststring(eolstr), tidy(autotidy), + configtag(ctag) + , extItem("listbase_mode_" + name + "_list", ExtensionItem::EXT_CHANNEL, Creator) +{ + list = true; +} + +void ListModeBase::DisplayList(User* user, Channel* channel) +{ + ChanData* cd = extItem.get(channel); + if (cd) + { + for (ModeList::const_iterator it = cd->list.begin(); it != cd->list.end(); ++it) + { + user->WriteNumeric(listnumeric, channel->name, it->mask, it->setter, (unsigned long) it->time); + } + } + user->WriteNumeric(endoflistnumeric, channel->name, endofliststring); +} + +void ListModeBase::DisplayEmptyList(User* user, Channel* channel) +{ + user->WriteNumeric(endoflistnumeric, channel->name, endofliststring); +} + +void ListModeBase::RemoveMode(Channel* channel, Modes::ChangeList& changelist) +{ + ChanData* cd = extItem.get(channel); + if (cd) + { + for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); it++) + { + changelist.push_remove(this, it->mask); + } + } +} + +void ListModeBase::DoRehash() +{ + ConfigTagList tags = ServerInstance->Config->ConfTags(configtag); + + limitlist oldlimits = chanlimits; + chanlimits.clear(); + + for (ConfigIter i = tags.first; i != tags.second; i++) + { + // For each <banlist> tag + ConfigTag* c = i->second; + ListLimit limit(c->getString("chan"), c->getUInt("limit", 0)); + + if (limit.mask.size() && limit.limit > 0) + chanlimits.push_back(limit); + } + + // Add the default entry. This is inserted last so if the user specifies a + // wildcard record in the config it will take precedence over this entry. + chanlimits.push_back(ListLimit("*", DEFAULT_LIST_SIZE)); + + // Most of the time our settings are unchanged, so we can avoid iterating the chanlist + if (oldlimits == chanlimits) + return; + + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) + { + ChanData* cd = extItem.get(i->second); + if (cd) + cd->maxitems = -1; + } +} + +unsigned int ListModeBase::FindLimit(const std::string& channame) +{ + for (limitlist::iterator it = chanlimits.begin(); it != chanlimits.end(); ++it) + { + if (InspIRCd::Match(channame, it->mask)) + { + // We have a pattern matching the channel + return it->limit; + } + } + return DEFAULT_LIST_SIZE; +} + +unsigned int ListModeBase::GetLimitInternal(const std::string& channame, ChanData* cd) +{ + if (cd->maxitems < 0) + cd->maxitems = FindLimit(channame); + return cd->maxitems; +} + +unsigned int ListModeBase::GetLimit(Channel* channel) +{ + ChanData* cd = extItem.get(channel); + if (!cd) // just find the limit + return FindLimit(channel->name); + + return GetLimitInternal(channel->name, cd); +} + +unsigned int ListModeBase::GetLowerLimit() +{ + unsigned int limit = UINT_MAX; + for (limitlist::iterator iter = chanlimits.begin(); iter != chanlimits.end(); ++iter) + { + if (iter->limit < limit) + limit = iter->limit; + } + return limit == UINT_MAX ? DEFAULT_LIST_SIZE : limit; +} + +ModeAction ListModeBase::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) +{ + // Try and grab the list + ChanData* cd = extItem.get(channel); + + if (adding) + { + if (tidy) + ModeParser::CleanMask(parameter); + + if (parameter.length() > 250) + return MODEACTION_DENY; + + // If there was no list + if (!cd) + { + // Make one + cd = new ChanData; + extItem.set(channel, cd); + } + + // Check if the item already exists in the list + for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); it++) + { + if (parameter == it->mask) + { + /* Give a subclass a chance to error about this */ + TellAlreadyOnList(source, channel, parameter); + + // it does, deny the change + return MODEACTION_DENY; + } + } + + if ((IS_LOCAL(source)) && (cd->list.size() >= GetLimitInternal(channel->name, cd))) + { + /* List is full, give subclass a chance to send a custom message */ + TellListTooLong(source, channel, parameter); + return MODEACTION_DENY; + } + + /* Ok, it *could* be allowed, now give someone subclassing us + * a chance to validate the parameter. + * The param is passed by reference, so they can both modify it + * and tell us if we allow it or not. + * + * eg, the subclass could: + * 1) allow + * 2) 'fix' parameter and then allow + * 3) deny + */ + if (ValidateParam(source, channel, parameter)) + { + // And now add the mask onto the list... + cd->list.push_back(ListItem(parameter, source->nick, ServerInstance->Time())); + return MODEACTION_ALLOW; + } + else + { + /* If they deny it they have the job of giving an error message */ + return MODEACTION_DENY; + } + } + else + { + // We're taking the mode off + if (cd) + { + for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); ++it) + { + if (parameter == it->mask) + { + stdalgo::vector::swaperase(cd->list, it); + return MODEACTION_ALLOW; + } + } + } + + /* Tried to remove something that wasn't set */ + TellNotSet(source, channel, parameter); + return MODEACTION_DENY; + } +} + +bool ListModeBase::ValidateParam(User*, Channel*, std::string&) +{ + return true; +} + +void ListModeBase::OnParameterMissing(User*, User*, Channel*) +{ +} + +void ListModeBase::TellListTooLong(User* source, Channel* channel, std::string& parameter) +{ + source->WriteNumeric(ERR_BANLISTFULL, channel->name, parameter, "Channel ban list is full"); +} + +void ListModeBase::TellAlreadyOnList(User*, Channel*, std::string&) +{ +} + +void ListModeBase::TellNotSet(User*, Channel*, std::string&) +{ +} diff --git a/src/logger.cpp b/src/logger.cpp index 89b2be019..b9f9bae0b 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -#include "filelogger.h" - /* * Suggested implementation... * class LogManager @@ -51,9 +49,12 @@ * */ +const char LogStream::LogHeader[] = + "Log started for " INSPIRCD_VERSION " (" MODULE_INIT_STR ")"; + LogManager::LogManager() + : Logging(false) { - Logging = false; } LogManager::~LogManager() @@ -76,49 +77,49 @@ void LogManager::OpenFileLogs() { ConfigTag* tag = i->second; std::string method = tag->getString("method"); - if (method != "file") + if (!stdalgo::string::equalsci(method, "file")) { continue; } std::string type = tag->getString("type"); std::string level = tag->getString("level"); - int loglevel = DEFAULT; - if (level == "rawio") + LogLevel loglevel = LOG_DEFAULT; + if (stdalgo::string::equalsci(level, "rawio")) { - loglevel = RAWIO; + loglevel = LOG_RAWIO; ServerInstance->Config->RawLog = true; } - else if (level == "debug") + else if (stdalgo::string::equalsci(level, "debug")) { - loglevel = DEBUG; + loglevel = LOG_DEBUG; } - else if (level == "verbose") + else if (stdalgo::string::equalsci(level, "verbose")) { - loglevel = VERBOSE; + loglevel = LOG_VERBOSE; } - else if (level == "default") + else if (stdalgo::string::equalsci(level, "default")) { - loglevel = DEFAULT; + loglevel = LOG_DEFAULT; } - else if (level == "sparse") + else if (stdalgo::string::equalsci(level, "sparse")) { - loglevel = SPARSE; + loglevel = LOG_SPARSE; } - else if (level == "none") + else if (stdalgo::string::equalsci(level, "none")) { - loglevel = NONE; + loglevel = LOG_NONE; } FileWriter* fw; - std::string target = tag->getString("target"); + std::string target = ServerInstance->Config->Paths.PrependLog(tag->getString("target")); std::map<std::string, FileWriter*>::iterator fwi = logmap.find(target); if (fwi == logmap.end()) { - char realtarget[MAXBUF]; + char realtarget[256]; time_t time = ServerInstance->Time(); struct tm *mytime = gmtime(&time); - strftime(realtarget, MAXBUF, target.c_str(), mytime); + strftime(realtarget, sizeof(realtarget), target.c_str(), mytime); FILE* f = fopen(realtarget, "a"); - fw = new FileWriter(f); + fw = new FileWriter(f, tag->getUInt("flush", 20, 1, UINT_MAX)); logmap.insert(std::make_pair(target, fw)); } else @@ -126,7 +127,7 @@ void LogManager::OpenFileLogs() fw = fwi->second; } FileLogStream* fls = new FileLogStream(loglevel, fw); - fls->OnLog(SPARSE, "HEADER", InspIRCd::LogHeader); + fls->OnLog(LOG_SPARSE, "HEADER", LogStream::LogHeader); AddLogTypes(type, fls, true); } } @@ -135,13 +136,16 @@ void LogManager::CloseLogs() { if (ServerInstance->Config && ServerInstance->Config->cmdline.forcedebug) return; - std::map<std::string, std::vector<LogStream*> >().swap(LogStreams); /* Clear it */ - std::map<LogStream*, std::vector<std::string> >().swap(GlobalLogStreams); /* Clear it */ + + LogStreams.clear(); + GlobalLogStreams.clear(); + for (std::map<LogStream*, int>::iterator i = AllLogStreams.begin(); i != AllLogStreams.end(); ++i) { delete i->first; } - std::map<LogStream*, int>().swap(AllLogStreams); /* And clear it */ + + AllLogStreams.clear(); } void LogManager::AddLogTypes(const std::string &types, LogStream* l, bool autoclose) @@ -187,36 +191,13 @@ void LogManager::AddLogTypes(const std::string &types, LogStream* l, bool autocl bool LogManager::AddLogType(const std::string &type, LogStream *l, bool autoclose) { - std::map<std::string, std::vector<LogStream *> >::iterator i = LogStreams.find(type); - - if (i != LogStreams.end()) - { - i->second.push_back(l); - } - else - { - std::vector<LogStream *> v; - v.push_back(l); - LogStreams[type] = v; - } + LogStreams[type].push_back(l); if (type == "*") - { GlobalLogStreams.insert(std::make_pair(l, std::vector<std::string>())); - } if (autoclose) - { - std::map<LogStream*, int>::iterator ai = AllLogStreams.find(l); - if (ai == AllLogStreams.end()) - { - AllLogStreams.insert(std::make_pair(l, 1)); - } - else - { - ++ai->second; - } - } + AllLogStreams[l]++; return true; } @@ -225,24 +206,20 @@ void LogManager::DelLogStream(LogStream* l) { for (std::map<std::string, std::vector<LogStream*> >::iterator i = LogStreams.begin(); i != LogStreams.end(); ++i) { - std::vector<LogStream*>::iterator it; - while ((it = std::find(i->second.begin(), i->second.end(), l)) != i->second.end()) + while (stdalgo::erase(i->second, l)) { - if (it == i->second.end()) - continue; - i->second.erase(it); + // Keep erasing while it exists } } - std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.find(l); - if (gi != GlobalLogStreams.end()) - { - GlobalLogStreams.erase(gi); - } + + GlobalLogStreams.erase(l); + std::map<LogStream*, int>::iterator ai = AllLogStreams.begin(); if (ai == AllLogStreams.end()) { return; /* Done. */ } + delete ai->first; AllLogStreams.erase(ai); } @@ -252,17 +229,13 @@ bool LogManager::DelLogType(const std::string &type, LogStream *l) std::map<std::string, std::vector<LogStream *> >::iterator i = LogStreams.find(type); if (type == "*") { - std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.find(l); - if (gi != GlobalLogStreams.end()) GlobalLogStreams.erase(gi); + GlobalLogStreams.erase(l); } if (i != LogStreams.end()) { - std::vector<LogStream *>::iterator it = std::find(i->second.begin(), i->second.end(), l); - - if (it != i->second.end()) + if (stdalgo::erase(i->second, l)) { - i->second.erase(it); if (i->second.size() == 0) { LogStreams.erase(i); @@ -293,24 +266,17 @@ bool LogManager::DelLogType(const std::string &type, LogStream *l) return true; } -void LogManager::Log(const std::string &type, int loglevel, const char *fmt, ...) +void LogManager::Log(const std::string &type, LogLevel loglevel, const char *fmt, ...) { if (Logging) - { return; - } - - va_list a; - static char buf[65536]; - - va_start(a, fmt); - vsnprintf(buf, 65536, fmt, a); - va_end(a); - this->Log(type, loglevel, std::string(buf)); + std::string buf; + VAFORMAT(buf, fmt, fmt); + this->Log(type, loglevel, buf); } -void LogManager::Log(const std::string &type, int loglevel, const std::string &msg) +void LogManager::Log(const std::string &type, LogLevel loglevel, const std::string &msg) { if (Logging) { @@ -321,7 +287,7 @@ void LogManager::Log(const std::string &type, int loglevel, const std::string &m for (std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.begin(); gi != GlobalLogStreams.end(); ++gi) { - if (std::find(gi->second.begin(), gi->second.end(), type) != gi->second.end()) + if (stdalgo::isin(gi->second, type)) { continue; } @@ -342,8 +308,10 @@ void LogManager::Log(const std::string &type, int loglevel, const std::string &m } -FileWriter::FileWriter(FILE* logfile) -: log(logfile), writeops(0) +FileWriter::FileWriter(FILE* logfile, unsigned int flushcount) + : log(logfile) + , flush(flushcount) + , writeops(0) { } @@ -354,8 +322,8 @@ void FileWriter::WriteLogLine(const std::string &line) // XXX: For now, just return. Don't throw an exception. It'd be nice to find out if this is happening, but I'm terrified of breaking so close to final release. -- w00t // throw CoreException("FileWriter::WriteLogLine called with a closed logfile"); - fprintf(log,"%s",line.c_str()); - if (writeops++ % 20) + fputs(line.c_str(), log); + if (++writeops % flush == 0) { fflush(log); } diff --git a/src/mode.cpp b/src/mode.cpp index 0f5d45783..0bb97cc9e 100644 --- a/src/mode.cpp +++ b/src/mode.cpp @@ -25,40 +25,23 @@ #include "inspircd.h" -/* +s (secret) */ -/* +p (private) */ -/* +m (moderated) */ -/* +t (only (half) ops can change topic) */ -/* +n (no external messages) */ -/* +i (invite only) */ -/* +w (see wallops) */ -/* +i (invisible) */ -#include "modes/simplemodes.h" -/* +b (bans) */ -#include "modes/cmode_b.h" -/* +k (keyed channel) */ -#include "modes/cmode_k.h" -/* +l (channel user limit) */ -#include "modes/cmode_l.h" -/* +o (channel op) */ -#include "modes/cmode_o.h" -/* +v (channel voice) */ -#include "modes/cmode_v.h" -/* +o (operator) */ -#include "modes/umode_o.h" -/* +s (server notice masks) */ -#include "modes/umode_s.h" - -ModeHandler::ModeHandler(Module* Creator, const std::string& Name, char modeletter, ParamSpec Params, ModeType type) - : ServiceProvider(Creator, Name, SERVICE_MODE), m_paramtype(TR_TEXT), - parameters_taken(Params), mode(modeletter), prefix(0), oper(false), - list(false), m_type(type), levelrequired(HALFOP_VALUE) +ModeHandler::ModeHandler(Module* Creator, const std::string& Name, char modeletter, ParamSpec Params, ModeType type, Class mclass) + : ServiceProvider(Creator, Name, SERVICE_MODE) + , modeid(ModeParser::MODEID_MAX) + , parameters_taken(Params) + , mode(modeletter) + , oper(false) + , list(false) + , m_type(type) + , type_id(mclass) + , ranktoset(HALFOP_VALUE) + , ranktounset(HALFOP_VALUE) { } CullResult ModeHandler::cull() { - if (ServerInstance->Modes) + if (ServerInstance) ServerInstance->Modes->DelMode(this); return classbase::cull(); } @@ -67,31 +50,21 @@ ModeHandler::~ModeHandler() { } -bool ModeHandler::IsListMode() -{ - return list; -} - -unsigned int ModeHandler::GetPrefixRank() -{ - return 0; -} - -int ModeHandler::GetNumParams(bool adding) +bool ModeHandler::NeedsParam(bool adding) const { switch (parameters_taken) { case PARAM_ALWAYS: - return 1; + return true; case PARAM_SETONLY: - return adding ? 1 : 0; + return adding; case PARAM_NONE: break; } - return 0; + return false; } -std::string ModeHandler::GetUserParameter(User* user) +std::string ModeHandler::GetUserParameter(const User* user) const { return ""; } @@ -116,6 +89,11 @@ void ModeHandler::DisplayEmptyList(User*, Channel*) void ModeHandler::OnParameterMissing(User* user, User* dest, Channel* channel) { + const std::string message = InspIRCd::Format("You must specify a parameter for the %s mode", name.c_str()); + if (channel) + user->WriteNumeric(Numerics::InvalidModeParameter(channel, this, "*", message)); + else + user->WriteNumeric(Numerics::InvalidModeParameter(dest, this, "*", message)); } bool ModeHandler::ResolveModeConflict(std::string& theirs, const std::string& ours, Channel*) @@ -123,11 +101,17 @@ bool ModeHandler::ResolveModeConflict(std::string& theirs, const std::string& ou return (theirs < ours); } +void ModeHandler::RegisterService() +{ + ServerInstance->Modes.AddMode(this); + ServerInstance->Modules.AddReferent((GetModeType() == MODETYPE_CHANNEL ? "mode/" : "umode/") + name, this); +} + ModeAction SimpleUserModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { /* We're either trying to add a mode we already have or remove a mode we don't have, deny. */ - if (dest->IsModeSet(this->GetModeChar()) == adding) + if (dest->IsModeSet(this) == adding) return MODEACTION_DENY; /* adding will be either true or false, depending on if we @@ -136,7 +120,7 @@ ModeAction SimpleUserModeHandler::OnModeChange(User* source, User* dest, Channel aren't removing a mode we don't have, we don't have to do any other checks here to see if it's true or false, just add or remove the mode */ - dest->SetMode(this->GetModeChar(), adding); + dest->SetMode(this, adding); return MODEACTION_ALLOW; } @@ -146,7 +130,7 @@ ModeAction SimpleChannelModeHandler::OnModeChange(User* source, User* dest, Chan { /* We're either trying to add a mode we already have or remove a mode we don't have, deny. */ - if (channel->IsModeSet(this->GetModeChar()) == adding) + if (channel->IsModeSet(this) == adding) return MODEACTION_DENY; /* adding will be either true or false, depending on if we @@ -155,120 +139,124 @@ ModeAction SimpleChannelModeHandler::OnModeChange(User* source, User* dest, Chan aren't removing a mode we don't have, we don't have to do any other checks here to see if it's true or false, just add or remove the mode */ - channel->SetMode(this->GetModeChar(), adding); + channel->SetMode(this, adding); return MODEACTION_ALLOW; } -ModeAction ParamChannelModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) +ModeWatcher::ModeWatcher(Module* Creator, const std::string& modename, ModeType type) + : mode(modename), m_type(type), creator(Creator) { - if (adding && !ParamValidate(parameter)) - return MODEACTION_DENY; - std::string now = channel->GetModeParameter(this); - if (parameter == now) - return MODEACTION_DENY; - if (adding) - channel->SetModeParam(this, parameter); - else - channel->SetModeParam(this, ""); - return MODEACTION_ALLOW; + ServerInstance->Modes->AddModeWatcher(this); } -bool ParamChannelModeHandler::ParamValidate(std::string& parameter) +ModeWatcher::~ModeWatcher() { - return true; + ServerInstance->Modes->DelModeWatcher(this); } -ModeWatcher::ModeWatcher(Module* Creator, char modeletter, ModeType type) - : mode(modeletter), m_type(type), creator(Creator) +bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool) { + return true; } -ModeWatcher::~ModeWatcher() +void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool) { } -char ModeWatcher::GetModeChar() +PrefixMode::PrefixMode(Module* Creator, const std::string& Name, char ModeLetter, unsigned int Rank, char PrefixChar) + : ModeHandler(Creator, Name, ModeLetter, PARAM_ALWAYS, MODETYPE_CHANNEL, MC_PREFIX) + , prefix(PrefixChar) + , prefixrank(Rank) + , selfremove(true) { - return mode; + list = true; } -ModeType ModeWatcher::GetModeType() +ModResult PrefixMode::AccessCheck(User* src, Channel*, std::string& value, bool adding) { - return m_type; + if (!adding && src->nick == value && selfremove) + return MOD_RES_ALLOW; + return MOD_RES_PASSTHRU; } -bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool, ModeType) +ModeAction PrefixMode::OnModeChange(User* source, User*, Channel* chan, std::string& parameter, bool adding) { - return true; -} + User* target; + if (IS_LOCAL(source)) + target = ServerInstance->FindNickOnly(parameter); + else + target = ServerInstance->FindNick(parameter); -void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool, ModeType) -{ + if (!target) + { + source->WriteNumeric(Numerics::NoSuchNick(parameter)); + return MODEACTION_DENY; + } + + Membership* memb = chan->GetUser(target); + if (!memb) + return MODEACTION_DENY; + + parameter = target->nick; + return (memb->SetPrefix(this, adding) ? MODEACTION_ALLOW : MODEACTION_DENY); } -User* ModeParser::SanityChecks(User *user, const char *dest, Channel *chan, int) +void PrefixMode::Update(unsigned int rank, unsigned int setrank, unsigned int unsetrank, bool selfrm) { - User *d; - if ((!user) || (!dest) || (!chan) || (!*dest)) - { - return NULL; - } - d = ServerInstance->FindNick(dest); - if (!d) - { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(), dest); - return NULL; - } - return d; + prefixrank = rank; + ranktoset = setrank; + ranktounset = unsetrank; + selfremove = selfrm; } -void ModeParser::DisplayCurrentModes(User *user, User* targetuser, Channel* targetchannel, const char* text) +ModeAction ParamModeBase::OnModeChange(User* source, User*, Channel* chan, std::string& parameter, bool adding) { - if (targetchannel) + if (adding) { - /* Display channel's current mode string */ - user->WriteNumeric(RPL_CHANNELMODEIS, "%s %s +%s",user->nick.c_str(), targetchannel->name.c_str(), targetchannel->ChanModes(targetchannel->HasUser(user))); - user->WriteNumeric(RPL_CHANNELCREATED, "%s %s %lu", user->nick.c_str(), targetchannel->name.c_str(), (unsigned long)targetchannel->age); - return; + if (chan->GetModeParameter(this) == parameter) + return MODEACTION_DENY; + + if (OnSet(source, chan, parameter) != MODEACTION_ALLOW) + return MODEACTION_DENY; + + chan->SetMode(this, true); + + // Handler might have changed the parameter internally + parameter.clear(); + this->GetParameter(chan, parameter); } else { - if (targetuser == user || user->HasPrivPermission("users/auspex")) - { - /* Display user's current mode string */ - user->WriteNumeric(RPL_UMODEIS, "%s :+%s",targetuser->nick.c_str(),targetuser->FormatModes()); - if (IS_OPER(targetuser)) - user->WriteNumeric(RPL_SNOMASKIS, "%s +%s :Server notice mask", targetuser->nick.c_str(), targetuser->FormatNoticeMasks()); - return; - } - else - { - user->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't view modes for other users", user->nick.c_str()); - return; - } + if (!chan->IsModeSet(this)) + return MODEACTION_DENY; + this->OnUnsetInternal(source, chan); + chan->SetMode(this, false); } + return MODEACTION_ALLOW; } -ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, bool adding, const unsigned char modechar, - std::string ¶meter, bool SkipACL) +ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, Modes::Change& mcitem, bool SkipACL) { ModeType type = chan ? MODETYPE_CHANNEL : MODETYPE_USER; - unsigned char mask = chan ? MASK_CHANNEL : MASK_USER; - ModeHandler *mh = FindMode(modechar, type); - int pcnt = mh->GetNumParams(adding); + ModeHandler* mh = mcitem.mh; + bool adding = mcitem.adding; + const bool needs_param = mh->NeedsParam(adding); + std::string& parameter = mcitem.param; // crop mode parameter size to 250 characters if (parameter.length() > 250 && adding) - parameter = parameter.substr(0, 250); + parameter.erase(250); ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, modechar, parameter, adding, pcnt)); + FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mh, parameter, adding)); if (IS_LOCAL(user) && (MOD_RESULT == MOD_RES_DENY)) return MODEACTION_DENY; + const char modechar = mh->GetModeChar(); + if (chan && !SkipACL && (MOD_RESULT != MOD_RES_ALLOW)) { MOD_RESULT = mh->AccessCheck(user, chan, parameter, adding); @@ -277,7 +265,7 @@ ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, bool return MODEACTION_DENY; if (MOD_RESULT == MOD_RES_PASSTHRU) { - unsigned int neededrank = mh->GetLevelRequired(); + unsigned int neededrank = mh->GetLevelRequired(adding); /* Compare our rank on the channel against the rank of the required prefix, * allow if >= ours. Because mIRC and xchat throw a tizz if the modes shown * in NAMES(X) are not in rank order, we know the most powerful mode is listed @@ -286,11 +274,12 @@ ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, bool unsigned int ourrank = chan->GetPrefixValue(user); if (ourrank < neededrank) { - ModeHandler* neededmh = NULL; - for(char c='A'; c <= 'z'; c++) + const PrefixMode* neededmh = NULL; + const PrefixModeList& prefixmodes = GetPrefixModes(); + for (PrefixModeList::const_iterator i = prefixmodes.begin(); i != prefixmodes.end(); ++i) { - ModeHandler *privmh = FindMode(c, MODETYPE_CHANNEL); - if (privmh && privmh->GetPrefixRank() >= neededrank) + const PrefixMode* const privmh = *i; + if (privmh->GetPrefixRank() >= neededrank) { // this mode is sufficient to allow this action if (!neededmh || privmh->GetPrefixRank() < neededmh->GetPrefixRank()) @@ -298,148 +287,87 @@ ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, bool } } if (neededmh) - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You must have channel %s access or above to %sset channel mode %c", - user->nick.c_str(), chan->name.c_str(), neededmh->name.c_str(), adding ? "" : "un", modechar); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, InspIRCd::Format("You must have channel %s access or above to %sset channel mode %c", + neededmh->name.c_str(), adding ? "" : "un", modechar)); else - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You cannot %sset channel mode %c", - user->nick.c_str(), chan->name.c_str(), adding ? "" : "un", modechar); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, InspIRCd::Format("You cannot %sset channel mode %c", (adding ? "" : "un"), modechar)); return MODEACTION_DENY; } } } - unsigned char handler_id = (modechar - 'A') | mask; - - for (ModeWatchIter watchers = modewatchers[handler_id].begin(); watchers != modewatchers[handler_id].end(); watchers++) + // Ask mode watchers whether this mode change is OK + std::pair<ModeWatcherMap::iterator, ModeWatcherMap::iterator> itpair = modewatchermap.equal_range(mh->name); + for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i) { - if ((*watchers)->BeforeMode(user, targetuser, chan, parameter, adding, type) == false) - return MODEACTION_DENY; - /* A module whacked the parameter completely, and there was one. abort. */ - if (pcnt && parameter.empty()) - return MODEACTION_DENY; + ModeWatcher* mw = i->second; + if (mw->GetModeType() == type) + { + if (!mw->BeforeMode(user, targetuser, chan, parameter, adding)) + return MODEACTION_DENY; + + // A module whacked the parameter completely, and there was one. Abort. + if ((needs_param) && (parameter.empty())) + return MODEACTION_DENY; + } } - if (IS_LOCAL(user) && !IS_OPER(user)) + if (IS_LOCAL(user) && !user->IsOper()) { - char* disabled = (type == MODETYPE_CHANNEL) ? ServerInstance->Config->DisabledCModes : ServerInstance->Config->DisabledUModes; - if (disabled[modechar - 'A']) + const std::bitset<64>& disabled = (type == MODETYPE_CHANNEL) ? ServerInstance->Config->DisabledCModes : ServerInstance->Config->DisabledUModes; + if (disabled.test(modechar - 'A')) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - %s mode %c has been locked by the administrator", - user->nick.c_str(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar); + user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - %s mode %c has been locked by the administrator", + type == MODETYPE_CHANNEL ? "channel" : "user", modechar)); return MODEACTION_DENY; } } - if (adding && IS_LOCAL(user) && mh->NeedsOper() && !user->HasModePermission(modechar, type)) + if ((adding) && (IS_LOCAL(user)) && (mh->NeedsOper()) && (!user->HasModePermission(mh))) { /* It's an oper only mode, and they don't have access to it. */ - if (IS_OPER(user)) + if (user->IsOper()) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to set %s mode %c", - user->nick.c_str(), user->oper->NameStr(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar); + user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Oper type %s does not have access to set %s mode %c", + user->oper->name.c_str(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar)); } else { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Only operators may set %s mode %c", - user->nick.c_str(), type == MODETYPE_CHANNEL ? "channel" : "user", modechar); + user->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Only operators may set %s mode %c", + type == MODETYPE_CHANNEL ? "channel" : "user", modechar)); } return MODEACTION_DENY; } - if (mh->GetTranslateType() == TR_NICK) - { - User* prefixtarget; - if (IS_LOCAL(user)) - prefixtarget = ServerInstance->FindNickOnly(parameter); - else - prefixtarget = ServerInstance->FindNick(parameter); - - if (!prefixtarget) - { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameter.c_str()); - return MODEACTION_DENY; - } - } - - if (mh->GetPrefixRank() && chan) - { - User* user_to_prefix = ServerInstance->FindNick(parameter); - if (!user_to_prefix) - return MODEACTION_DENY; - if (!chan->SetPrefix(user_to_prefix, modechar, adding)) - return MODEACTION_DENY; - } - /* Call the handler for the mode */ ModeAction ma = mh->OnModeChange(user, targetuser, chan, parameter, adding); - if (pcnt && parameter.empty()) + if ((needs_param) && (parameter.empty())) return MODEACTION_DENY; if (ma != MODEACTION_ALLOW) return ma; - for (ModeWatchIter watchers = modewatchers[handler_id].begin(); watchers != modewatchers[handler_id].end(); watchers++) - (*watchers)->AfterMode(user, targetuser, chan, parameter, adding, type); + itpair = modewatchermap.equal_range(mh->name); + for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i) + { + ModeWatcher* mw = i->second; + if (mw->GetModeType() == type) + mw->AfterMode(user, targetuser, chan, parameter, adding); + } return MODEACTION_ALLOW; } -void ModeParser::Process(const std::vector<std::string>& parameters, User *user, bool merge) +void ModeParser::ModeParamsToChangeList(User* user, ModeType type, const std::vector<std::string>& parameters, Modes::ChangeList& changelist, unsigned int beginindex, unsigned int endindex) { - const std::string& target = parameters[0]; - Channel* targetchannel = ServerInstance->FindChan(target); - User* targetuser = NULL; - if (!targetchannel) - { - if (IS_LOCAL(user)) - targetuser = ServerInstance->FindNickOnly(target); - else - targetuser = ServerInstance->FindNick(target); - } - ModeType type = targetchannel ? MODETYPE_CHANNEL : MODETYPE_USER; - - LastParse.clear(); - LastParseParams.clear(); - LastParseTranslate.clear(); - - if ((!targetchannel) && ((!targetuser) || (IS_SERVER(targetuser)))) - { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(),target.c_str()); - return; - } - if (parameters.size() == 1) - { - this->DisplayCurrentModes(user, targetuser, targetchannel, target.c_str()); - return; - } + if (endindex > parameters.size()) + endindex = parameters.size(); - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (user, targetuser, targetchannel, parameters)); - - bool SkipAccessChecks = false; - - if (!IS_LOCAL(user) || ServerInstance->ULine(user->server) || MOD_RESULT == MOD_RES_ALLOW) - SkipAccessChecks = true; - else if (MOD_RESULT == MOD_RES_DENY) - return; - - if (targetuser && !SkipAccessChecks && user != targetuser) - { - user->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't change mode for other users", user->nick.c_str()); - return; - } - - std::string mode_sequence = parameters[1]; - - std::string output_mode; - std::ostringstream output_parameters; - LastParseParams.push_back(output_mode); - LastParseTranslate.push_back(TR_TEXT); + const std::string& mode_sequence = parameters[beginindex]; bool adding = true; - char output_pm = '\0'; // current output state, '+' or '-' - unsigned int param_at = 2; + unsigned int param_at = beginindex+1; for (std::string::const_iterator letter = mode_sequence.begin(); letter != mode_sequence.end(); letter++) { @@ -454,143 +382,154 @@ void ModeParser::Process(const std::vector<std::string>& parameters, User *user, if (!mh) { /* No mode handler? Unknown mode character then. */ - user->WriteServ("%d %s %c :is unknown mode char to me", type == MODETYPE_CHANNEL ? 472 : 501, user->nick.c_str(), modechar); + user->WriteNumeric(type == MODETYPE_CHANNEL ? ERR_UNKNOWNMODE : ERR_UNKNOWNSNOMASK, modechar, "is unknown mode char to me"); continue; } std::string parameter; - int pcnt = mh->GetNumParams(adding); - if (pcnt && param_at == parameters.size()) - { - /* No parameter, continue to the next mode */ - mh->OnParameterMissing(user, targetuser, targetchannel); + if ((mh->NeedsParam(adding)) && (param_at < endindex)) + parameter = parameters[param_at++]; + + changelist.push(mh, adding, parameter); + } +} + +static bool IsModeParamValid(User* user, Channel* targetchannel, User* targetuser, const Modes::Change& item) +{ + // An empty parameter is never acceptable + if (item.param.empty()) + { + item.mh->OnParameterMissing(user, targetuser, targetchannel); + return false; + } + + // The parameter cannot begin with a ':' character or contain a space + if ((item.param[0] == ':') || (item.param.find(' ') != std::string::npos)) + return false; + + return true; +} + +// Returns true if we should apply a merged mode, false if we should skip it +static bool ShouldApplyMergedMode(Channel* chan, Modes::Change& item) +{ + ModeHandler* mh = item.mh; + if ((!chan) || (!chan->IsModeSet(mh)) || (mh->IsListMode())) + // Mode not set here or merge is not applicable, apply the incoming mode + return true; + + // Mode handler decides + std::string ours = chan->GetModeParameter(mh); + return mh->ResolveModeConflict(item.param, ours, chan); +} + +void ModeParser::Process(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags) +{ + // Call ProcessSingle until the entire list is processed, but at least once to ensure + // LastParse and LastChangeList are cleared + unsigned int processed = 0; + do + { + unsigned int n = ProcessSingle(user, targetchannel, targetuser, changelist, flags, processed); + processed += n; + } + while (processed < changelist.size()); +} + +unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags, unsigned int beginindex) +{ + LastChangeList.clear(); + + unsigned int modes_processed = 0; + Modes::ChangeList::List& list = changelist.getlist(); + for (Modes::ChangeList::List::iterator i = list.begin()+beginindex; i != list.end(); ++i) + { + modes_processed++; + + Modes::Change& item = *i; + ModeHandler* mh = item.mh; + + // If a mode change has been given for a mode that does not exist then reject + // it. This can happen when core_reloadmodule attempts to restore a mode that + // no longer exists. + if (!mh) continue; - } - else if (pcnt) + + // If the mode is supposed to have a parameter then we first take a look at item.param + // and, if we were asked to, also handle mode merges now + if (mh->NeedsParam(item.adding)) { - parameter = parameters[param_at++]; - /* Make sure the user isn't trying to slip in an invalid parameter */ - if ((parameter.empty()) || (parameter.find(':') == 0) || (parameter.rfind(' ') != std::string::npos)) + // Skip the mode if the parameter does not pass basic validation + if (!IsModeParamValid(user, targetchannel, targetuser, item)) + continue; + + // If this is a merge and we won we don't apply this mode + if ((flags & MODE_MERGE) && (!ShouldApplyMergedMode(targetchannel, item))) continue; - if (merge && targetchannel && targetchannel->IsModeSet(modechar) && !mh->IsListMode()) - { - std::string ours = targetchannel->GetModeParameter(modechar); - if (!mh->ResolveModeConflict(parameter, ours, targetchannel)) - /* we won the mode merge, don't apply this mode */ - continue; - } } - ModeAction ma = TryMode(user, targetuser, targetchannel, adding, modechar, parameter, SkipAccessChecks); + ModeAction ma = TryMode(user, targetuser, targetchannel, item, (!(flags & MODE_CHECKACCESS))); if (ma != MODEACTION_ALLOW) continue; - char needed_pm = adding ? '+' : '-'; - if (needed_pm != output_pm) - { - output_pm = needed_pm; - output_mode.append(1, output_pm); - } - output_mode.append(1, modechar); + LastChangeList.push(mh, item.adding, item.param); - if (pcnt) - { - TranslateType tt = mh->GetTranslateType(); - if (tt == TR_NICK) - { - User* u = ServerInstance->FindNick(parameter); - if (u) - parameter = u->nick; - } - output_parameters << " " << parameter; - LastParseParams.push_back(parameter); - LastParseTranslate.push_back(tt); - } - - if ( (output_mode.length() + output_parameters.str().length() > 450) - || (output_mode.length() > 100) - || (LastParseParams.size() > ServerInstance->Config->Limits.MaxModes)) + if (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes) { /* mode sequence is getting too long */ break; } } - LastParseParams[0] = output_mode; - - if (!output_mode.empty()) + if (!LastChangeList.empty()) { - LastParse = targetchannel ? targetchannel->name : targetuser->nick; - LastParse.append(" "); - LastParse.append(output_mode); - LastParse.append(output_parameters.str()); - + ClientProtocol::Events::Mode modeevent(user, targetchannel, targetuser, LastChangeList); if (targetchannel) { - targetchannel->WriteChannel(user, "MODE %s", LastParse.c_str()); - FOREACH_MOD(I_OnMode,OnMode(user, targetchannel, TYPE_CHANNEL, LastParseParams, LastParseTranslate)); + targetchannel->Write(modeevent); } else { - targetuser->WriteFrom(user, "MODE %s", LastParse.c_str()); - FOREACH_MOD(I_OnMode,OnMode(user, targetuser, TYPE_USER, LastParseParams, LastParseTranslate)); + LocalUser* localtarget = IS_LOCAL(targetuser); + if (localtarget) + localtarget->Send(modeevent); } + + FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags)); } - else if (targetchannel && parameters.size() == 2) - { - /* Special case for displaying the list for listmodes, - * e.g. MODE #chan b, or MODE #chan +b without a parameter - */ - this->DisplayListModes(user, targetchannel, mode_sequence); - } + + return modes_processed; } -void ModeParser::DisplayListModes(User* user, Channel* chan, std::string &mode_sequence) +void ModeParser::ShowListModeList(User* user, Channel* chan, ModeHandler* mh) { - seq++; - - for (std::string::const_iterator letter = mode_sequence.begin(); letter != mode_sequence.end(); letter++) { - unsigned char mletter = *letter; - if (mletter == '+') - continue; - - /* Ensure the user doesnt request the same mode twice, - * so they cant flood themselves off out of idiocy. - */ - if (sent[mletter] == seq) - continue; - - sent[mletter] = seq; - - ModeHandler *mh = this->FindMode(mletter, MODETYPE_CHANNEL); - - if (!mh || !mh->IsListMode()) - return; - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mletter, "", true, 0)); + FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mh, "", true)); if (MOD_RESULT == MOD_RES_DENY) - continue; + return; bool display = true; - if (!user->HasPrivPermission("channels/auspex") && ServerInstance->Config->HideModeLists[mletter] && (chan->GetPrefixValue(user) < HALFOP_VALUE)) - { - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You do not have access to view the +%c list", - user->nick.c_str(), chan->name.c_str(), mletter); - display = false; - } - - unsigned char handler_id = (mletter - 'A') | MASK_CHANNEL; - for(ModeWatchIter watchers = modewatchers[handler_id].begin(); watchers != modewatchers[handler_id].end(); watchers++) + // Ask mode watchers whether it's OK to show the list + std::pair<ModeWatcherMap::iterator, ModeWatcherMap::iterator> itpair = modewatchermap.equal_range(mh->name); + for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i) { - std::string dummyparam; + ModeWatcher* mw = i->second; + if (mw->GetModeType() == MODETYPE_CHANNEL) + { + std::string dummyparam; - if (!((*watchers)->BeforeMode(user, NULL, chan, dummyparam, true, MODETYPE_CHANNEL))) - display = false; + if (!mw->BeforeMode(user, NULL, chan, dummyparam, true)) + { + // A mode watcher doesn't want us to show the list + display = false; + break; + } + } } + if (display) mh->DisplayList(user, chan); else @@ -598,11 +537,6 @@ void ModeParser::DisplayListModes(User* user, Channel* chan, std::string &mode_s } } -const std::string& ModeParser::GetLastParse() -{ - return LastParse; -} - void ModeParser::CleanMask(std::string &mask) { std::string::size_type pos_of_pling = mask.find_first_of('!'); @@ -639,50 +573,77 @@ void ModeParser::CleanMask(std::string &mask) } } -bool ModeParser::AddMode(ModeHandler* mh) +ModeHandler::Id ModeParser::AllocateModeId(ModeType mt) { - unsigned char mask = 0; - unsigned char pos = 0; + for (ModeHandler::Id i = 0; i != MODEID_MAX; ++i) + { + if (!modehandlersbyid[mt][i]) + return i; + } - /* Yes, i know, this might let people declare modes like '_' or '^'. - * If they do that, thats their problem, and if i ever EVER see an - * official InspIRCd developer do that, i'll beat them with a paddle! - */ - if ((mh->GetModeChar() < 'A') || (mh->GetModeChar() > 'z') || (mh->GetPrefix() > 126)) - return false; + throw ModuleException("Out of ModeIds"); +} + +void ModeParser::AddMode(ModeHandler* mh) +{ + if (!ModeParser::IsModeChar(mh->GetModeChar())) + throw ModuleException("Invalid letter for mode " + mh->name); /* A mode prefix of ',' is not acceptable, it would fuck up server to server. * A mode prefix of ':' will fuck up both server to server, and client to server. * A mode prefix of '#' will mess up /whois and /privmsg */ - if ((mh->GetPrefix() == ',') || (mh->GetPrefix() == ':') || (mh->GetPrefix() == '#')) - return false; + PrefixMode* pm = mh->IsPrefixMode(); + if (pm) + { + if ((pm->GetPrefix() > 126) || (pm->GetPrefix() == ',') || (pm->GetPrefix() == ':') || (pm->GetPrefix() == '#')) + throw ModuleException("Invalid prefix for mode " + mh->name); - if (mh->GetPrefix() && FindPrefix(mh->GetPrefix())) - return false; + if (FindPrefix(pm->GetPrefix())) + throw ModuleException("Prefix already exists for mode " + mh->name); + } - mh->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL; - pos = (mh->GetModeChar()-65) | mask; + ModeHandler*& slot = modehandlers[mh->GetModeType()][mh->GetModeChar()-65]; + if (slot) + throw ModuleException("Letter is already in use for mode " + mh->name); - if (modehandlers[pos]) - return false; + // The mode needs an id if it is either a user mode, a simple mode (flag) or a parameter mode. + // Otherwise (for listmodes and prefix modes) the id remains MODEID_MAX, which is invalid. + ModeHandler::Id modeid = MODEID_MAX; + if ((mh->GetModeType() == MODETYPE_USER) || (mh->IsParameterMode()) || (!mh->IsListMode())) + modeid = AllocateModeId(mh->GetModeType()); - modehandlers[pos] = mh; - return true; + if (!modehandlersbyname[mh->GetModeType()].insert(std::make_pair(mh->name, mh)).second) + throw ModuleException("Mode name already in use: " + mh->name); + + // Everything is fine, add the mode + + // If we allocated an id for this mode then save it and put the mode handler into the slot + if (modeid != MODEID_MAX) + { + mh->modeid = modeid; + modehandlersbyid[mh->GetModeType()][modeid] = mh; + } + + slot = mh; + if (pm) + mhlist.prefix.push_back(pm); + else if (mh->IsListModeBase()) + mhlist.list.push_back(mh->IsListModeBase()); } bool ModeParser::DelMode(ModeHandler* mh) { - unsigned char mask = 0; - unsigned char pos = 0; - - if ((mh->GetModeChar() < 'A') || (mh->GetModeChar() > 'z')) + if (!ModeParser::IsModeChar(mh->GetModeChar())) return false; - mh->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL; - pos = (mh->GetModeChar()-65) | mask; + ModeHandlerMap& mhmap = modehandlersbyname[mh->GetModeType()]; + ModeHandlerMap::iterator mhmapit = mhmap.find(mh->name); + if ((mhmapit == mhmap.end()) || (mhmapit->second != mh)) + return false; - if (modehandlers[pos] != mh) + ModeHandler*& slot = modehandlers[mh->GetModeType()][mh->GetModeChar()-65]; + if (slot != mh) return false; /* Note: We can't stack here, as we have modes potentially being removed across many different channels. @@ -691,106 +652,83 @@ bool ModeParser::DelMode(ModeHandler* mh) switch (mh->GetModeType()) { case MODETYPE_USER: - for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end(); ) + { + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ) { User* user = i->second; ++i; mh->RemoveMode(user); } + } break; case MODETYPE_CHANNEL: - for (chan_hash::iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); ) + { + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ) { // The channel may not be in the hash after RemoveMode(), see m_permchannels Channel* chan = i->second; ++i; - mh->RemoveMode(chan); + + Modes::ChangeList changelist; + mh->RemoveMode(chan, changelist); + this->Process(ServerInstance->FakeClient, chan, NULL, changelist, MODE_LOCALONLY); } + } break; } - modehandlers[pos] = NULL; - + mhmap.erase(mhmapit); + if (mh->GetId() != MODEID_MAX) + modehandlersbyid[mh->GetModeType()][mh->GetId()] = NULL; + slot = NULL; + if (mh->IsPrefixMode()) + mhlist.prefix.erase(std::find(mhlist.prefix.begin(), mhlist.prefix.end(), mh->IsPrefixMode())); + else if (mh->IsListModeBase()) + mhlist.list.erase(std::find(mhlist.list.begin(), mhlist.list.end(), mh->IsListModeBase())); return true; } -ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt) -{ - unsigned char mask = 0; - unsigned char pos = 0; - - if ((modeletter < 'A') || (modeletter > 'z')) - return NULL; - - mt == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL; - pos = (modeletter-65) | mask; - - return modehandlers[pos]; -} - -std::string ModeParser::UserModeList() +ModeHandler* ModeParser::FindMode(const std::string& modename, ModeType mt) { - char modestr[256]; - int pointer = 0; + ModeHandlerMap& mhmap = modehandlersbyname[mt]; + ModeHandlerMap::const_iterator it = mhmap.find(modename); + if (it != mhmap.end()) + return it->second; - for (unsigned char mode = 'A'; mode <= 'z'; mode++) - { - unsigned char pos = (mode-65) | MASK_USER; - - if (modehandlers[pos]) - modestr[pointer++] = mode; - } - modestr[pointer++] = 0; - return modestr; + return NULL; } -std::string ModeParser::ChannelModeList() +ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt) { - char modestr[256]; - int pointer = 0; - - for (unsigned char mode = 'A'; mode <= 'z'; mode++) - { - unsigned char pos = (mode-65) | MASK_CHANNEL; + if (!ModeParser::IsModeChar(modeletter)) + return NULL; - if (modehandlers[pos]) - modestr[pointer++] = mode; - } - modestr[pointer++] = 0; - return modestr; + return modehandlers[mt][modeletter-65]; } -std::string ModeParser::ParaModeList() +PrefixMode* ModeParser::FindPrefixMode(unsigned char modeletter) { - char modestr[256]; - int pointer = 0; - - for (unsigned char mode = 'A'; mode <= 'z'; mode++) - { - unsigned char pos = (mode-65) | MASK_CHANNEL; - - if ((modehandlers[pos]) && (modehandlers[pos]->GetNumParams(true))) - modestr[pointer++] = mode; - } - modestr[pointer++] = 0; - return modestr; + ModeHandler* mh = FindMode(modeletter, MODETYPE_CHANNEL); + if (!mh) + return NULL; + return mh->IsPrefixMode(); } -ModeHandler* ModeParser::FindPrefix(unsigned const char pfxletter) +PrefixMode* ModeParser::FindPrefix(unsigned const char pfxletter) { - for (unsigned char mode = 'A'; mode <= 'z'; mode++) + const PrefixModeList& list = GetPrefixModes(); + for (PrefixModeList::const_iterator i = list.begin(); i != list.end(); ++i) { - unsigned char pos = (mode-65) | MASK_CHANNEL; - - if ((modehandlers[pos]) && (modehandlers[pos]->GetPrefix() == pfxletter)) - { - return modehandlers[pos]; - } + PrefixMode* pm = *i; + if (pm->GetPrefix() == pfxletter) + return pm; } return NULL; } -std::string ModeParser::GiveModeList(ModeMasks m) +std::string ModeParser::GiveModeList(ModeType mt) { std::string type1; /* Listmodes EXCEPT those with a prefix */ std::string type2; /* Modes that take a param when adding or removing */ @@ -799,37 +737,38 @@ std::string ModeParser::GiveModeList(ModeMasks m) for (unsigned char mode = 'A'; mode <= 'z'; mode++) { - unsigned char pos = (mode-65) | m; + ModeHandler* mh = modehandlers[mt][mode-65]; /* One parameter when adding */ - if (modehandlers[pos]) + if (mh) { - if (modehandlers[pos]->GetNumParams(true)) + if (mh->NeedsParam(true)) { - if ((modehandlers[pos]->IsListMode()) && (!modehandlers[pos]->GetPrefix())) + PrefixMode* pm = mh->IsPrefixMode(); + if ((mh->IsListMode()) && ((!pm) || (pm->GetPrefix() == 0))) { - type1 += modehandlers[pos]->GetModeChar(); + type1 += mh->GetModeChar(); } else { /* ... and one parameter when removing */ - if (modehandlers[pos]->GetNumParams(false)) + if (mh->NeedsParam(false)) { /* But not a list mode */ - if (!modehandlers[pos]->GetPrefix()) + if (!pm) { - type2 += modehandlers[pos]->GetModeChar(); + type2 += mh->GetModeChar(); } } else { /* No parameters when removing */ - type3 += modehandlers[pos]->GetModeChar(); + type3 += mh->GetModeChar(); } } } else { - type4 += modehandlers[pos]->GetModeChar(); + type4 += mh->GetModeChar(); } } } @@ -839,7 +778,7 @@ std::string ModeParser::GiveModeList(ModeMasks m) struct PrefixModeSorter { - bool operator()(ModeHandler* lhs, ModeHandler* rhs) + bool operator()(PrefixMode* lhs, PrefixMode* rhs) { return lhs->GetPrefixRank() < rhs->GetPrefixRank(); } @@ -849,20 +788,18 @@ std::string ModeParser::BuildPrefixes(bool lettersAndModes) { std::string mletters; std::string mprefixes; - std::vector<ModeHandler*> prefixes; + std::vector<PrefixMode*> prefixes; - for (unsigned char mode = 'A'; mode <= 'z'; mode++) + const PrefixModeList& list = GetPrefixModes(); + for (PrefixModeList::const_iterator i = list.begin(); i != list.end(); ++i) { - unsigned char pos = (mode-65) | MASK_CHANNEL; - - if ((modehandlers[pos]) && (modehandlers[pos]->GetPrefix())) - { - prefixes.push_back(modehandlers[pos]); - } + PrefixMode* pm = *i; + if (pm->GetPrefix()) + prefixes.push_back(pm); } std::sort(prefixes.begin(), prefixes.end(), PrefixModeSorter()); - for (std::vector<ModeHandler*>::const_reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); ++n) + for (std::vector<PrefixMode*>::const_reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); ++n) { mletters += (*n)->GetPrefix(); mprefixes += (*n)->GetModeChar(); @@ -871,149 +808,69 @@ std::string ModeParser::BuildPrefixes(bool lettersAndModes) return lettersAndModes ? "(" + mprefixes + ")" + mletters : mletters; } -bool ModeParser::AddModeWatcher(ModeWatcher* mw) +void ModeParser::AddModeWatcher(ModeWatcher* mw) { - unsigned char mask = 0; - unsigned char pos = 0; - - if (!mw) - return false; - - if ((mw->GetModeChar() < 'A') || (mw->GetModeChar() > 'z')) - return false; - - mw->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL; - pos = (mw->GetModeChar()-65) | mask; - - modewatchers[pos].push_back(mw); - - return true; + modewatchermap.insert(std::make_pair(mw->GetModeName(), mw)); } bool ModeParser::DelModeWatcher(ModeWatcher* mw) { - unsigned char mask = 0; - unsigned char pos = 0; - - if (!mw) - return false; - - if ((mw->GetModeChar() < 'A') || (mw->GetModeChar() > 'z')) - return false; - - mw->GetModeType() == MODETYPE_USER ? mask = MASK_USER : mask = MASK_CHANNEL; - pos = (mw->GetModeChar()-65) | mask; - - ModeWatchIter a = std::find(modewatchers[pos].begin(),modewatchers[pos].end(),mw); - - if (a == modewatchers[pos].end()) + std::pair<ModeWatcherMap::iterator, ModeWatcherMap::iterator> itpair = modewatchermap.equal_range(mw->GetModeName()); + for (ModeWatcherMap::iterator i = itpair.first; i != itpair.second; ++i) { - return false; + if (i->second == mw) + { + modewatchermap.erase(i); + return true; + } } - modewatchers[pos].erase(a); - - return true; + return false; } -/** This default implementation can remove simple user modes - */ -void ModeHandler::RemoveMode(User* user, irc::modestacker* stack) +void ModeHandler::RemoveMode(User* user) { + // Remove the mode if it's set on the user if (user->IsModeSet(this->GetModeChar())) { - if (stack) - { - stack->Push(this->GetModeChar()); - } - else - { - std::vector<std::string> parameters; - parameters.push_back(user->nick); - parameters.push_back("-"); - parameters[1].push_back(this->GetModeChar()); - ServerInstance->Modes->Process(parameters, ServerInstance->FakeClient); - } + Modes::ChangeList changelist; + changelist.push_remove(this); + ServerInstance->Modes->Process(ServerInstance->FakeClient, NULL, user, changelist, ModeParser::MODE_LOCALONLY); } } -/** This default implementation can remove simple channel modes - * (no parameters) - */ -void ModeHandler::RemoveMode(Channel* channel, irc::modestacker* stack) +void ModeHandler::RemoveMode(Channel* channel, Modes::ChangeList& changelist) { - if (channel->IsModeSet(this->GetModeChar())) + if (channel->IsModeSet(this)) { - if (stack) - { - stack->Push(this->GetModeChar()); - } + if (this->NeedsParam(false)) + // Removing this mode requires a parameter + changelist.push_remove(this, channel->GetModeParameter(this)); else - { - std::vector<std::string> parameters; - parameters.push_back(channel->name); - parameters.push_back("-"); - parameters[1].push_back(this->GetModeChar()); - ServerInstance->SendMode(parameters, ServerInstance->FakeClient); - } + changelist.push_remove(this); } } -struct builtin_modes +void PrefixMode::RemoveMode(Channel* chan, Modes::ChangeList& changelist) { - ModeChannelSecret s; - ModeChannelPrivate p; - ModeChannelModerated m; - ModeChannelTopicOps t; - - ModeChannelNoExternal n; - ModeChannelInviteOnly i; - ModeChannelKey k; - ModeChannelLimit l; - - ModeChannelBan b; - ModeChannelOp o; - ModeChannelVoice v; - - ModeUserWallops uw; - ModeUserInvisible ui; - ModeUserOperator uo; - ModeUserServerNoticeMask us; - - void init(ModeParser* modes) + const Channel::MemberMap& userlist = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = userlist.begin(); i != userlist.end(); ++i) { - modes->AddMode(&s); - modes->AddMode(&p); - modes->AddMode(&m); - modes->AddMode(&t); - modes->AddMode(&n); - modes->AddMode(&i); - modes->AddMode(&k); - modes->AddMode(&l); - modes->AddMode(&b); - modes->AddMode(&o); - modes->AddMode(&v); - modes->AddMode(&uw); - modes->AddMode(&ui); - modes->AddMode(&uo); - modes->AddMode(&us); + if (i->second->HasMode(this)) + changelist.push_remove(this, i->first->nick); } -}; +} -static builtin_modes static_modes; +bool ModeParser::IsModeChar(char chr) +{ + return ((chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z')); +} ModeParser::ModeParser() { /* Clear mode handler list */ memset(modehandlers, 0, sizeof(modehandlers)); - - /* Last parse string */ - LastParse.clear(); - - seq = 0; - memset(&sent, 0, sizeof(sent)); - - static_modes.init(this); + memset(modehandlersbyid, 0, sizeof(modehandlersbyid)); } ModeParser::~ModeParser() diff --git a/src/modes/cmode_b.cpp b/src/modes/cmode_b.cpp deleted file mode 100644 index e45f191f7..000000000 --- a/src/modes/cmode_b.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include <string> -#include <vector> -#include "inspircd_config.h" -#include "configreader.h" -#include "hash_map.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modules.h" -#include "inspstring.h" -#include "hashcomp.h" -#include "modes/cmode_b.h" - -ModeChannelBan::ModeChannelBan() : ModeHandler(NULL, "ban", 'b', PARAM_ALWAYS, MODETYPE_CHANNEL) -{ - list = true; -} - -ModeAction ModeChannelBan::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) -{ - int status = channel->GetPrefixValue(source); - /* Call the correct method depending on wether we're adding or removing the mode */ - if (adding) - { - this->AddBan(source, parameter, channel, status); - } - else - { - this->DelBan(source, parameter, channel, status); - } - /* If the method above 'ate' the parameter by reducing it to an empty string, then - * it won't matter wether we return ALLOW or DENY here, as an empty string overrides - * the return value and is always MODEACTION_DENY if the mode is supposed to have - * a parameter. - */ - return MODEACTION_ALLOW; -} - -void ModeChannelBan::RemoveMode(Channel* channel, irc::modestacker* stack) -{ - BanList copy; - - for (BanList::iterator i = channel->bans.begin(); i != channel->bans.end(); i++) - { - copy.push_back(*i); - } - - for (BanList::iterator i = copy.begin(); i != copy.end(); i++) - { - if (stack) - { - stack->Push(this->GetModeChar(), i->data); - } - else - { - std::vector<std::string> parameters; parameters.push_back(channel->name); parameters.push_back("-b"); parameters.push_back(i->data); - ServerInstance->SendMode(parameters, ServerInstance->FakeClient); - } - } -} - -void ModeChannelBan::RemoveMode(User*, irc::modestacker* stack) -{ -} - -void ModeChannelBan::DisplayList(User* user, Channel* channel) -{ - /* Display the channel banlist */ - for (BanList::reverse_iterator i = channel->bans.rbegin(); i != channel->bans.rend(); ++i) - { - user->WriteServ("367 %s %s %s %s %lu",user->nick.c_str(), channel->name.c_str(), i->data.c_str(), i->set_by.c_str(), (unsigned long)i->set_time); - } - user->WriteServ("368 %s %s :End of channel ban list",user->nick.c_str(), channel->name.c_str()); - return; -} - -void ModeChannelBan::DisplayEmptyList(User* user, Channel* channel) -{ - user->WriteServ("368 %s %s :End of channel ban list",user->nick.c_str(), channel->name.c_str()); -} - -std::string& ModeChannelBan::AddBan(User *user, std::string &dest, Channel *chan, int) -{ - if ((!user) || (!chan)) - { - ServerInstance->Logs->Log("MODE",DEFAULT,"*** BUG *** AddBan was given an invalid parameter"); - dest.clear(); - return dest; - } - - /* Attempt to tidy the mask */ - ModeParser::CleanMask(dest); - /* If the mask was invalid, we exit */ - if (dest.empty() || dest.length() > 250) - return dest; - - long maxbans = chan->GetMaxBans(); - if (IS_LOCAL(user) && ((unsigned)chan->bans.size() >= (unsigned)maxbans)) - { - user->WriteServ("478 %s %s :Channel ban list for %s is full (maximum entries for this channel is %ld)",user->nick.c_str(), chan->name.c_str(), chan->name.c_str(), maxbans); - dest.clear(); - return dest; - } - - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnAddBan, MOD_RESULT, (user,chan,dest)); - if (MOD_RESULT == MOD_RES_DENY) - { - dest.clear(); - return dest; - } - - for (BanList::iterator i = chan->bans.begin(); i != chan->bans.end(); i++) - { - if (i->data == dest) - { - /* dont allow a user to set the same ban twice */ - dest.clear(); - return dest; - } - } - - b.set_time = ServerInstance->Time(); - b.data.assign(dest, 0, MAXBUF); - b.set_by.assign(user->nick, 0, 64); - chan->bans.push_back(b); - return dest; -} - -std::string& ModeChannelBan::DelBan(User *user, std::string& dest, Channel *chan, int) -{ - if ((!user) || (!chan)) - { - ServerInstance->Logs->Log("MODE",DEFAULT,"*** BUG *** TakeBan was given an invalid parameter"); - dest.clear(); - return dest; - } - - for (BanList::iterator i = chan->bans.begin(); i != chan->bans.end(); i++) - { - if (!strcasecmp(i->data.c_str(), dest.c_str())) - { - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnDelBan, MOD_RESULT, (user, chan, dest)); - if (MOD_RESULT == MOD_RES_DENY) - { - dest.clear(); - return dest; - } - dest = i->data; - chan->bans.erase(i); - return dest; - } - } - dest.clear(); - return dest; -} - diff --git a/src/modes/cmode_o.cpp b/src/modes/cmode_o.cpp deleted file mode 100644 index 0a13b39ce..000000000 --- a/src/modes/cmode_o.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "configreader.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modules.h" -#include "modes/cmode_o.h" - -ModeChannelOp::ModeChannelOp() : ModeHandler(NULL, "op", 'o', PARAM_ALWAYS, MODETYPE_CHANNEL) -{ - list = true; - prefix = '@'; - levelrequired = OP_VALUE; - m_paramtype = TR_NICK; -} - -unsigned int ModeChannelOp::GetPrefixRank() -{ - return OP_VALUE; -} - -void ModeChannelOp::RemoveMode(Channel* channel, irc::modestacker* stack) -{ - const UserMembList* clist = channel->GetUsers(); - - for (UserMembCIter i = clist->begin(); i != clist->end(); i++) - { - if (stack) - stack->Push(this->GetModeChar(), i->first->nick); - else - { - std::vector<std::string> parameters; - parameters.push_back(channel->name); - parameters.push_back("-o"); - parameters.push_back(i->first->nick); - ServerInstance->SendMode(parameters, ServerInstance->FakeClient); - } - } -} - -void ModeChannelOp::RemoveMode(User*, irc::modestacker* stack) -{ -} - -ModeAction ModeChannelOp::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) -{ - return MODEACTION_ALLOW; -} diff --git a/src/modes/cmode_v.cpp b/src/modes/cmode_v.cpp deleted file mode 100644 index 4a00f60f1..000000000 --- a/src/modes/cmode_v.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "configreader.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modules.h" -#include "modes/cmode_v.h" - -ModeChannelVoice::ModeChannelVoice() : ModeHandler(NULL, "voice", 'v', PARAM_ALWAYS, MODETYPE_CHANNEL) -{ - list = true; - prefix = '+'; - levelrequired = HALFOP_VALUE; - m_paramtype = TR_NICK; -} - -unsigned int ModeChannelVoice::GetPrefixRank() -{ - return VOICE_VALUE; -} - -void ModeChannelVoice::RemoveMode(Channel* channel, irc::modestacker* stack) -{ - const UserMembList* clist = channel->GetUsers(); - - for (UserMembCIter i = clist->begin(); i != clist->end(); i++) - { - if (stack) - stack->Push(this->GetModeChar(), i->first->nick); - else - { - std::vector<std::string> parameters; - parameters.push_back(channel->name); - parameters.push_back("-v"); - parameters.push_back(i->first->nick); - ServerInstance->SendMode(parameters, ServerInstance->FakeClient); - } - } -} - -void ModeChannelVoice::RemoveMode(User*, irc::modestacker* stack) -{ -} - -ModeAction ModeChannelVoice::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) -{ - return MODEACTION_ALLOW; -} diff --git a/src/modes/umode_s.cpp b/src/modes/umode_s.cpp deleted file mode 100644 index 1b782ae85..000000000 --- a/src/modes/umode_s.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/umode_s.h" - -ModeUserServerNoticeMask::ModeUserServerNoticeMask() : ModeHandler(NULL, "snomask", 's', PARAM_SETONLY, MODETYPE_USER) -{ - oper = true; -} - -ModeAction ModeUserServerNoticeMask::OnModeChange(User* source, User* dest, Channel*, std::string ¶meter, bool adding) -{ - /* Set the array fields */ - if (adding) - { - /* Fix for bug #310 reported by Smartys */ - if (!dest->modes[UM_SNOMASK]) - dest->snomasks.reset(); - - dest->modes[UM_SNOMASK] = true; - parameter = dest->ProcessNoticeMasks(parameter.c_str()); - return MODEACTION_ALLOW; - } - else - { - if (dest->modes[UM_SNOMASK] != false) - { - dest->modes[UM_SNOMASK] = false; - return MODEACTION_ALLOW; - } - } - - /* Allow the change */ - return MODEACTION_DENY; -} - -std::string ModeUserServerNoticeMask::GetUserParameter(User* user) -{ - std::string masks = user->FormatNoticeMasks(); - if (masks.length()) - masks = "+" + masks; - return masks; -} - -void ModeUserServerNoticeMask::OnParameterMissing(User* user, User* dest, Channel* channel) -{ - user->WriteServ("NOTICE %s :*** The user mode +s requires a parameter (server notice mask). Please provide a parameter, e.g. '+s +*'.", - user->nick.c_str()); -} - diff --git a/src/modmanager_dynamic.cpp b/src/modmanager_dynamic.cpp deleted file mode 100644 index 050f41c75..000000000 --- a/src/modmanager_dynamic.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "xline.h" -#include "socket.h" -#include "socketengine.h" -#include "command_parse.h" -#include "dns.h" -#include "exitcodes.h" -#include <iostream> - -#ifndef _WIN32 -#include <dirent.h> -#endif - -#ifndef PURE_STATIC - -bool ModuleManager::Load(const std::string& filename, bool defer) -{ - /* Don't allow people to specify paths for modules, it doesn't work as expected */ - if (filename.find('/') != std::string::npos) - { - LastModuleError = "You can't load modules with a path: " + filename; - return false; - } - - char modfile[MAXBUF]; - snprintf(modfile,MAXBUF,"%s/%s",ServerInstance->Config->ModPath.c_str(),filename.c_str()); - - if (!ServerConfig::FileExists(modfile)) - { - LastModuleError = "Module file could not be found: " + filename; - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - return false; - } - - if (Modules.find(filename) != Modules.end()) - { - LastModuleError = "Module " + filename + " is already loaded, cannot load a module twice!"; - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - return false; - } - - Module* newmod = NULL; - DLLManager* newhandle = new DLLManager(modfile); - - try - { - newmod = newhandle->CallInit(); - - if (newmod) - { - newmod->ModuleSourceFile = filename; - newmod->ModuleDLLManager = newhandle; - newmod->dying = false; - Modules[filename] = newmod; - std::string version = newhandle->GetVersion(); - if (defer) - { - ServerInstance->Logs->Log("MODULE", DEFAULT,"New module introduced: %s (Module version %s)", - filename.c_str(), version.c_str()); - } - else - { - newmod->init(); - - Version v = newmod->GetVersion(); - ServerInstance->Logs->Log("MODULE", DEFAULT,"New module introduced: %s (Module version %s)%s", - filename.c_str(), version.c_str(), (!(v.Flags & VF_VENDOR) ? " [3rd Party]" : " [Vendor]")); - } - } - else - { - LastModuleError = "Unable to load " + filename + ": " + newhandle->LastError(); - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - delete newhandle; - return false; - } - } - catch (CoreException& modexcept) - { - // failure in module constructor - if (newmod) - { - DoSafeUnload(newmod); - ServerInstance->GlobalCulls.AddItem(newhandle); - } - else - delete newhandle; - LastModuleError = "Unable to load " + filename + ": " + modexcept.GetReason(); - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - return false; - } - - this->ModCount++; - if (defer) - return true; - - FOREACH_MOD(I_OnLoadModule,OnLoadModule(newmod)); - /* We give every module a chance to re-prioritize when we introduce a new one, - * not just the one thats loading, as the new module could affect the preference - * of others - */ - for(int tries = 0; tries < 20; tries++) - { - prioritizationState = tries > 0 ? PRIO_STATE_LAST : PRIO_STATE_FIRST; - for (std::map<std::string, Module*>::iterator n = Modules.begin(); n != Modules.end(); ++n) - n->second->Prioritize(); - - if (prioritizationState == PRIO_STATE_LAST) - break; - if (tries == 19) - ServerInstance->Logs->Log("MODULE", DEFAULT, "Hook priority dependency loop detected while loading " + filename); - } - - ServerInstance->BuildISupport(); - return true; -} - -namespace { - struct UnloadAction : public HandlerBase0<void> - { - Module* const mod; - UnloadAction(Module* m) : mod(m) {} - void Call() - { - DLLManager* dll = mod->ModuleDLLManager; - ServerInstance->Modules->DoSafeUnload(mod); - ServerInstance->GlobalCulls.Apply(); - delete dll; - ServerInstance->GlobalCulls.AddItem(this); - } - }; - - struct ReloadAction : public HandlerBase0<void> - { - Module* const mod; - HandlerBase1<void, bool>* const callback; - ReloadAction(Module* m, HandlerBase1<void, bool>* c) - : mod(m), callback(c) {} - void Call() - { - DLLManager* dll = mod->ModuleDLLManager; - std::string name = mod->ModuleSourceFile; - ServerInstance->Modules->DoSafeUnload(mod); - ServerInstance->GlobalCulls.Apply(); - delete dll; - bool rv = ServerInstance->Modules->Load(name.c_str()); - if (callback) - callback->Call(rv); - ServerInstance->GlobalCulls.AddItem(this); - } - }; -} - -bool ModuleManager::Unload(Module* mod) -{ - if (!CanUnload(mod)) - return false; - ServerInstance->AtomicActions.AddAction(new UnloadAction(mod)); - return true; -} - -void ModuleManager::Reload(Module* mod, HandlerBase1<void, bool>* callback) -{ - if (CanUnload(mod)) - ServerInstance->AtomicActions.AddAction(new ReloadAction(mod, callback)); - else if (callback) - callback->Call(false); -} - -/* We must load the modules AFTER initializing the socket engine, now */ -void ModuleManager::LoadAll() -{ - ModCount = 0; - - std::cout << std::endl << "Loading core commands"; - fflush(stdout); - - DIR* library = opendir(ServerInstance->Config->ModPath.c_str()); - if (library) - { - dirent* entry = NULL; - while (0 != (entry = readdir(library))) - { - if (InspIRCd::Match(entry->d_name, "cmd_*.so", ascii_case_insensitive_map)) - { - std::cout << "."; - fflush(stdout); - - if (!Load(entry->d_name, true)) - { - ServerInstance->Logs->Log("MODULE", DEFAULT, this->LastError()); - std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << this->LastError() << std::endl << std::endl; - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } - } - closedir(library); - std::cout << std::endl; - } - - ConfigTagList tags = ServerInstance->Config->ConfTags("module"); - for(ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - std::string name = tag->getString("name"); - std::cout << "[" << con_green << "*" << con_reset << "] Loading module:\t" << con_green << name << con_reset << std::endl; - - if (!this->Load(name, true)) - { - ServerInstance->Logs->Log("MODULE", DEFAULT, this->LastError()); - std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << this->LastError() << std::endl << std::endl; - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } - - for(std::map<std::string, Module*>::iterator i = Modules.begin(); i != Modules.end(); i++) - { - Module* mod = i->second; - try - { - ServerInstance->Logs->Log("MODULE", DEBUG, "Initializing %s", i->first.c_str()); - mod->init(); - } - catch (CoreException& modexcept) - { - LastModuleError = "Unable to initialize " + mod->ModuleSourceFile + ": " + modexcept.GetReason(); - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << LastModuleError << std::endl << std::endl; - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } - - /* We give every module a chance to re-prioritize when we introduce a new one, - * not just the one thats loading, as the new module could affect the preference - * of others - */ - for(int tries = 0; tries < 20; tries++) - { - prioritizationState = tries > 0 ? PRIO_STATE_LAST : PRIO_STATE_FIRST; - for (std::map<std::string, Module*>::iterator n = Modules.begin(); n != Modules.end(); ++n) - n->second->Prioritize(); - - if (prioritizationState == PRIO_STATE_LAST) - break; - if (tries == 19) - { - ServerInstance->Logs->Log("MODULE", DEFAULT, "Hook priority dependency loop detected"); - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } -} - -#endif diff --git a/src/modmanager_static.cpp b/src/modmanager_static.cpp deleted file mode 100644 index cea40c7a3..000000000 --- a/src/modmanager_static.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#define MODNAME cmd_all - -#include "inspircd.h" -#include "exitcodes.h" -#include <iostream> - -#ifdef PURE_STATIC - -typedef std::map<std::string, AllModuleList*> modmap; -static std::vector<AllCommandList::fn>* cmdlist = NULL; -static modmap* modlist = NULL; - -AllCommandList::AllCommandList(fn cmd) -{ - if (!cmdlist) - cmdlist = new std::vector<AllCommandList::fn>(); - cmdlist->push_back(cmd); -} - -AllModuleList::AllModuleList(AllModuleList::fn mod, const std::string& Name) : init(mod), name(Name) -{ - if (!modlist) - modlist = new modmap(); - modlist->insert(std::make_pair(Name, this)); -} - -class AllModule : public Module -{ - std::vector<Command*> cmds; - public: - AllModule() - { - if (!cmdlist) - return; - try - { - cmds.reserve(cmdlist->size()); - for(std::vector<AllCommandList::fn>::iterator i = cmdlist->begin(); i != cmdlist->end(); ++i) - { - Command* c = (*i)(this); - cmds.push_back(c); - ServerInstance->AddCommand(c); - } - } - catch (...) - { - this->AllModule::~AllModule(); - throw; - } - } - - ~AllModule() - { - for(std::vector<Command*>::iterator i = cmds.begin(); i != cmds.end(); ++i) - delete *i; - } - - Version GetVersion() - { - return Version("All commands", VF_VENDOR|VF_CORE); - } -}; - -MODULE_INIT(AllModule) - -bool ModuleManager::Load(const std::string& name, bool defer) -{ - modmap::iterator it = modlist->find(name); - if (it == modlist->end()) - return false; - Module* mod = NULL; - try - { - mod = (*it->second->init)(); - mod->ModuleSourceFile = name; - mod->ModuleDLLManager = NULL; - mod->dying = false; - Modules[name] = mod; - if (defer) - { - ServerInstance->Logs->Log("MODULE", DEFAULT,"New module introduced: %s", name.c_str()); - return true; - } - else - { - mod->init(); - } - } - catch (CoreException& modexcept) - { - if (mod) - DoSafeUnload(mod); - ServerInstance->Logs->Log("MODULE", DEFAULT, "Unable to load " + name + ": " + modexcept.GetReason()); - return false; - } - FOREACH_MOD(I_OnLoadModule,OnLoadModule(mod)); - /* We give every module a chance to re-prioritize when we introduce a new one, - * not just the one thats loading, as the new module could affect the preference - * of others - */ - for(int tries = 0; tries < 20; tries++) - { - prioritizationState = tries > 0 ? PRIO_STATE_LAST : PRIO_STATE_FIRST; - for (std::map<std::string, Module*>::iterator n = Modules.begin(); n != Modules.end(); ++n) - n->second->Prioritize(); - - if (prioritizationState == PRIO_STATE_LAST) - break; - if (tries == 19) - ServerInstance->Logs->Log("MODULE", DEFAULT, "Hook priority dependency loop detected while loading " + name); - } - - ServerInstance->BuildISupport(); - return true; -} - -namespace { - struct UnloadAction : public HandlerBase0<void> - { - Module* const mod; - UnloadAction(Module* m) : mod(m) {} - void Call() - { - ServerInstance->Modules->DoSafeUnload(mod); - ServerInstance->GlobalCulls.Apply(); - ServerInstance->GlobalCulls.AddItem(this); - } - }; - - struct ReloadAction : public HandlerBase0<void> - { - Module* const mod; - HandlerBase1<void, bool>* const callback; - ReloadAction(Module* m, HandlerBase1<void, bool>* c) - : mod(m), callback(c) {} - void Call() - { - std::string name = mod->ModuleSourceFile; - ServerInstance->Modules->DoSafeUnload(mod); - ServerInstance->GlobalCulls.Apply(); - bool rv = ServerInstance->Modules->Load(name.c_str()); - if (callback) - callback->Call(rv); - ServerInstance->GlobalCulls.AddItem(this); - } - }; -} - -bool ModuleManager::Unload(Module* mod) -{ - if (!CanUnload(mod)) - return false; - ServerInstance->AtomicActions.AddAction(new UnloadAction(mod)); - return true; -} - -void ModuleManager::Reload(Module* mod, HandlerBase1<void, bool>* callback) -{ - if (CanUnload(mod)) - ServerInstance->AtomicActions.AddAction(new ReloadAction(mod, callback)); - else if (callback) - callback->Call(false); -} - -void ModuleManager::LoadAll() -{ - Load("cmd_all", true); - Load("cmd_whowas.so", true); - Load("cmd_lusers.so", true); - - ConfigTagList tags = ServerInstance->Config->ConfTags("module"); - for(ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - std::string name = tag->getString("name"); - std::cout << "[" << con_green << "*" << con_reset << "] Loading module:\t" << con_green << name << con_reset << std::endl; - - if (!this->Load(name, true)) - { - ServerInstance->Logs->Log("MODULE", DEFAULT, this->LastError()); - std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << this->LastError() << std::endl << std::endl; - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } - - for(std::map<std::string, Module*>::iterator i = Modules.begin(); i != Modules.end(); i++) - { - Module* mod = i->second; - try - { - mod->init(); - } - catch (CoreException& modexcept) - { - LastModuleError = "Unable to initialize " + mod->ModuleSourceFile + ": " + modexcept.GetReason(); - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << LastModuleError << std::endl << std::endl; - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } - - /* We give every module a chance to re-prioritize when we introduce a new one, - * not just the one thats loading, as the new module could affect the preference - * of others - */ - for(int tries = 0; tries < 20; tries++) - { - prioritizationState = tries > 0 ? PRIO_STATE_LAST : PRIO_STATE_FIRST; - for (std::map<std::string, Module*>::iterator n = Modules.begin(); n != Modules.end(); ++n) - n->second->Prioritize(); - - if (prioritizationState == PRIO_STATE_LAST) - break; - if (tries == 19) - { - ServerInstance->Logs->Log("MODULE", DEFAULT, "Hook priority dependency loop detected"); - ServerInstance->Exit(EXIT_STATUS_MODULE); - } - } -} - -#endif diff --git a/src/modulemanager.cpp b/src/modulemanager.cpp new file mode 100644 index 000000000..eb40c5971 --- /dev/null +++ b/src/modulemanager.cpp @@ -0,0 +1,157 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "exitcodes.h" +#include <iostream> + +#ifndef _WIN32 +#include <dirent.h> +#endif + +bool ModuleManager::Load(const std::string& modname, bool defer) +{ + /* Don't allow people to specify paths for modules, it doesn't work as expected */ + if (modname.find('/') != std::string::npos) + { + LastModuleError = "You can't load modules with a path: " + modname; + return false; + } + + const std::string filename = ExpandModName(modname); + const std::string moduleFile = ServerInstance->Config->Paths.PrependModule(filename); + + if (!FileSystem::FileExists(moduleFile)) + { + LastModuleError = "Module file could not be found: " + filename; + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); + return false; + } + + if (Modules.find(filename) != Modules.end()) + { + LastModuleError = "Module " + filename + " is already loaded, cannot load a module twice!"; + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); + return false; + } + + Module* newmod = NULL; + DLLManager* newhandle = new DLLManager(moduleFile.c_str()); + ServiceList newservices; + if (!defer) + this->NewServices = &newservices; + + try + { + newmod = newhandle->CallInit(); + this->NewServices = NULL; + + if (newmod) + { + newmod->ModuleSourceFile = filename; + newmod->ModuleDLLManager = newhandle; + newmod->dying = false; + Modules[filename] = newmod; + std::string version = newhandle->GetVersion(); + if (version.empty()) + version.assign("unknown"); + if (defer) + { + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "New module introduced: %s (Module version %s)", + filename.c_str(), version.c_str()); + } + else + { + ConfigStatus confstatus; + + AttachAll(newmod); + AddServices(newservices); + newmod->init(); + newmod->ReadConfig(confstatus); + + Version v = newmod->GetVersion(); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "New module introduced: %s (Module version %s)%s", + filename.c_str(), version.c_str(), (!(v.Flags & VF_VENDOR) ? " [3rd Party]" : " [Vendor]")); + } + } + else + { + LastModuleError = "Unable to load " + filename + ": " + newhandle->LastError(); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); + delete newhandle; + return false; + } + } + catch (CoreException& modexcept) + { + this->NewServices = NULL; + + // failure in module constructor + if (newmod) + { + DoSafeUnload(newmod); + ServerInstance->GlobalCulls.AddItem(newhandle); + } + else + delete newhandle; + LastModuleError = "Unable to load " + filename + ": " + modexcept.GetReason(); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); + return false; + } + + if (defer) + return true; + + FOREACH_MOD(OnLoadModule, (newmod)); + PrioritizeHooks(); + ServerInstance->ISupport.Build(); + return true; +} + +/* We must load the modules AFTER initializing the socket engine, now */ +void ModuleManager::LoadCoreModules(std::map<std::string, ServiceList>& servicemap) +{ + std::cout << std::endl << "Loading core commands"; + fflush(stdout); + + DIR* library = opendir(ServerInstance->Config->Paths.Module.c_str()); + if (library) + { + dirent* entry = NULL; + while (0 != (entry = readdir(library))) + { + if (InspIRCd::Match(entry->d_name, "core_*.so", ascii_case_insensitive_map)) + { + std::cout << "."; + fflush(stdout); + + this->NewServices = &servicemap[entry->d_name]; + + if (!Load(entry->d_name, true)) + { + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, this->LastError()); + std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << this->LastError() << std::endl << std::endl; + ServerInstance->Exit(EXIT_STATUS_MODULE); + } + } + } + closedir(library); + std::cout << std::endl; + } +} diff --git a/src/modules.cpp b/src/modules.cpp index 7df7579bf..3062aad43 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -24,26 +24,22 @@ */ +#include <iostream> #include "inspircd.h" -#include "xline.h" -#include "socket.h" -#include "socketengine.h" -#include "command_parse.h" -#include "dns.h" #include "exitcodes.h" #ifndef _WIN32 #include <dirent.h> #endif -static std::vector<dynamic_reference_base*>* dynrefs = NULL; +static insp::intrusive_list<dynamic_reference_base>* dynrefs = NULL; void dynamic_reference_base::reset_all() { if (!dynrefs) return; - for(unsigned int i = 0; i < dynrefs->size(); i++) - (*dynrefs)[i]->ClearCache(); + for (insp::intrusive_list<dynamic_reference_base>::iterator i = dynrefs->begin(); i != dynrefs->end(); ++i) + (*i)->resolve(); } // Version is a simple class for holding a modules version number @@ -56,24 +52,6 @@ Version::Version(const std::string &desc, int flags, const std::string& linkdata { } -Request::Request(Module* src, Module* dst, const char* idstr) -: id(idstr), source(src), dest(dst) -{ -} - -void Request::Send() -{ - if (dest) - dest->OnRequest(*this); -} - -Event::Event(Module* src, const std::string &eventid) : source(src), id(eventid) { } - -void Event::Send() -{ - FOREACH_MOD(I_OnEvent,OnEvent(*this)); -} - // These declarations define the behavours of the base class Module (which does nothing at all) Module::Module() { } @@ -85,101 +63,100 @@ Module::~Module() { } -ModResult Module::OnSendSnotice(char &snomask, std::string &type, const std::string &message) { return MOD_RES_PASSTHRU; } -void Module::OnUserConnect(LocalUser*) { } -void Module::OnUserQuit(User*, const std::string&, const std::string&) { } -void Module::OnUserDisconnect(LocalUser*) { } -void Module::OnUserJoin(Membership*, bool, bool, CUList&) { } -void Module::OnPostJoin(Membership*) { } -void Module::OnUserPart(Membership*, std::string&, CUList&) { } -void Module::OnPreRehash(User*, const std::string&) { } -void Module::OnModuleRehash(User*, const std::string&) { } -void Module::OnRehash(User*) { } -ModResult Module::OnUserPreJoin(User*, Channel*, const char*, std::string&, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnMode(User*, void*, int, const std::vector<std::string>&, const std::vector<TranslateType>&) { } -void Module::OnOper(User*, const std::string&) { } -void Module::OnPostOper(User*, const std::string&, const std::string &) { } -void Module::OnPostDeoper(User*) { } -void Module::OnInfo(User*) { } -void Module::OnWhois(User*, User*) { } -ModResult Module::OnUserPreInvite(User*, User*, Channel*, time_t) { return MOD_RES_PASSTHRU; } -ModResult Module::OnUserPreMessage(User*, void*, int, std::string&, char, CUList&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnUserPreNotice(User*, void*, int, std::string&, char, CUList&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnUserPreNick(User*, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnUserPostNick(User*, const std::string&) { } -ModResult Module::OnPreMode(User*, User*, Channel*, const std::vector<std::string>&) { return MOD_RES_PASSTHRU; } -void Module::On005Numeric(std::string&) { } -ModResult Module::OnKill(User*, User*, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnLoadModule(Module*) { } -void Module::OnUnloadModule(Module*) { } -void Module::OnBackgroundTimer(time_t) { } -ModResult Module::OnPreCommand(std::string&, std::vector<std::string>&, LocalUser*, bool, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnPostCommand(const std::string&, const std::vector<std::string>&, LocalUser*, CmdResult, const std::string&) { } -void Module::OnUserInit(LocalUser*) { } -ModResult Module::OnCheckReady(LocalUser*) { return MOD_RES_PASSTHRU; } -ModResult Module::OnUserRegister(LocalUser*) { return MOD_RES_PASSTHRU; } -ModResult Module::OnUserPreKick(User*, Membership*, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnUserKick(User*, Membership*, const std::string&, CUList&) { } -ModResult Module::OnRawMode(User*, Channel*, const char, const std::string &, bool, int) { return MOD_RES_PASSTHRU; } -ModResult Module::OnCheckInvite(User*, Channel*) { return MOD_RES_PASSTHRU; } -ModResult Module::OnCheckKey(User*, Channel*, const std::string&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnCheckLimit(User*, Channel*) { return MOD_RES_PASSTHRU; } -ModResult Module::OnCheckChannelBan(User*, Channel*) { return MOD_RES_PASSTHRU; } -ModResult Module::OnCheckBan(User*, Channel*, const std::string&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnExtBanCheck(User*, Channel*, char) { return MOD_RES_PASSTHRU; } -ModResult Module::OnStats(char, User*, string_list&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnChangeLocalUserHost(LocalUser*, const std::string&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnChangeLocalUserGECOS(LocalUser*, const std::string&) { return MOD_RES_PASSTHRU; } -ModResult Module::OnPreTopicChange(User*, Channel*, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnEvent(Event&) { } -void Module::OnRequest(Request&) { } -ModResult Module::OnPassCompare(Extensible* ex, const std::string &password, const std::string &input, const std::string& hashtype) { return MOD_RES_PASSTHRU; } -void Module::OnGlobalOper(User*) { } -void Module::OnPostConnect(User*) { } -ModResult Module::OnAddBan(User*, Channel*, const std::string &) { return MOD_RES_PASSTHRU; } -ModResult Module::OnDelBan(User*, Channel*, const std::string &) { return MOD_RES_PASSTHRU; } -void Module::OnStreamSocketAccept(StreamSocket*, irc::sockets::sockaddrs*, irc::sockets::sockaddrs*) { } -int Module::OnStreamSocketWrite(StreamSocket*, std::string&) { return -1; } -void Module::OnStreamSocketClose(StreamSocket*) { } -void Module::OnStreamSocketConnect(StreamSocket*) { } -int Module::OnStreamSocketRead(StreamSocket*, std::string&) { return -1; } -void Module::OnUserMessage(User*, void*, int, const std::string&, char, const CUList&) { } -void Module::OnUserNotice(User*, void*, int, const std::string&, char, const CUList&) { } -void Module::OnRemoteKill(User*, User*, const std::string&, const std::string&) { } -void Module::OnUserInvite(User*, User*, Channel*, time_t) { } -void Module::OnPostTopicChange(User*, Channel*, const std::string&) { } -void Module::OnGetServerDescription(const std::string&, std::string&) { } -void Module::OnSyncUser(User*, Module*, void*) { } -void Module::OnSyncChannel(Channel*, Module*, void*) { } -void Module::OnSyncNetwork(Module*, void*) { } -void Module::ProtoSendMode(void*, TargetTypeFlags, void*, const std::vector<std::string>&, const std::vector<TranslateType>&) { } -void Module::OnDecodeMetaData(Extensible*, const std::string&, const std::string&) { } -void Module::ProtoSendMetaData(void*, Extensible*, const std::string&, const std::string&) { } -void Module::OnWallops(User*, const std::string&) { } -void Module::OnChangeHost(User*, const std::string&) { } -void Module::OnChangeName(User*, const std::string&) { } -void Module::OnChangeIdent(User*, const std::string&) { } -void Module::OnAddLine(User*, XLine*) { } -void Module::OnDelLine(User*, XLine*) { } -void Module::OnExpireLine(XLine*) { } -void Module::OnCleanup(int, void*) { } -ModResult Module::OnChannelPreDelete(Channel*) { return MOD_RES_PASSTHRU; } -void Module::OnChannelDelete(Channel*) { } -ModResult Module::OnSetAway(User*, const std::string &) { return MOD_RES_PASSTHRU; } -ModResult Module::OnWhoisLine(User*, User*, int&, std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnBuildNeighborList(User*, UserChanList&, std::map<User*,bool>&) { } -void Module::OnGarbageCollect() { } -ModResult Module::OnSetConnectClass(LocalUser* user, ConnectClass* myclass) { return MOD_RES_PASSTHRU; } -void Module::OnText(User*, void*, int, const std::string&, char, CUList&) { } +void Module::DetachEvent(Implementation i) +{ + ServerInstance->Modules->Detach(i, this); +} + +void Module::ReadConfig(ConfigStatus& status) { } +ModResult Module::OnSendSnotice(char &snomask, std::string &type, const std::string &message) { DetachEvent(I_OnSendSnotice); return MOD_RES_PASSTHRU; } +void Module::OnUserConnect(LocalUser*) { DetachEvent(I_OnUserConnect); } +void Module::OnUserQuit(User*, const std::string&, const std::string&) { DetachEvent(I_OnUserQuit); } +void Module::OnUserDisconnect(LocalUser*) { DetachEvent(I_OnUserDisconnect); } +void Module::OnUserJoin(Membership*, bool, bool, CUList&) { DetachEvent(I_OnUserJoin); } +void Module::OnPostJoin(Membership*) { DetachEvent(I_OnPostJoin); } +void Module::OnUserPart(Membership*, std::string&, CUList&) { DetachEvent(I_OnUserPart); } +void Module::OnPreRehash(User*, const std::string&) { DetachEvent(I_OnPreRehash); } +void Module::OnModuleRehash(User*, const std::string&) { DetachEvent(I_OnModuleRehash); } +ModResult Module::OnUserPreJoin(LocalUser*, Channel*, const std::string&, std::string&, const std::string&) { DetachEvent(I_OnUserPreJoin); return MOD_RES_PASSTHRU; } +void Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag) { DetachEvent(I_OnMode); } +void Module::OnOper(User*, const std::string&) { DetachEvent(I_OnOper); } +void Module::OnPostOper(User*, const std::string&, const std::string &) { DetachEvent(I_OnPostOper); } +void Module::OnPostDeoper(User*) { DetachEvent(I_OnPostDeoper); } +ModResult Module::OnUserPreInvite(User*, User*, Channel*, time_t) { DetachEvent(I_OnUserPreInvite); return MOD_RES_PASSTHRU; } +ModResult Module::OnUserPreMessage(User*, const MessageTarget&, MessageDetails&) { DetachEvent(I_OnUserPreMessage); return MOD_RES_PASSTHRU; } +ModResult Module::OnUserPreNick(LocalUser*, const std::string&) { DetachEvent(I_OnUserPreNick); return MOD_RES_PASSTHRU; } +void Module::OnUserPostNick(User*, const std::string&) { DetachEvent(I_OnUserPostNick); } +ModResult Module::OnPreMode(User*, User*, Channel*, Modes::ChangeList&) { DetachEvent(I_OnPreMode); return MOD_RES_PASSTHRU; } +void Module::On005Numeric(std::map<std::string, std::string>&) { DetachEvent(I_On005Numeric); } +ModResult Module::OnKill(User*, User*, const std::string&) { DetachEvent(I_OnKill); return MOD_RES_PASSTHRU; } +void Module::OnLoadModule(Module*) { DetachEvent(I_OnLoadModule); } +void Module::OnUnloadModule(Module*) { DetachEvent(I_OnUnloadModule); } +void Module::OnBackgroundTimer(time_t) { DetachEvent(I_OnBackgroundTimer); } +ModResult Module::OnPreCommand(std::string&, CommandBase::Params&, LocalUser*, bool) { DetachEvent(I_OnPreCommand); return MOD_RES_PASSTHRU; } +void Module::OnPostCommand(Command*, const CommandBase::Params&, LocalUser*, CmdResult, bool) { DetachEvent(I_OnPostCommand); } +void Module::OnUserInit(LocalUser*) { DetachEvent(I_OnUserInit); } +ModResult Module::OnCheckReady(LocalUser*) { DetachEvent(I_OnCheckReady); return MOD_RES_PASSTHRU; } +ModResult Module::OnUserRegister(LocalUser*) { DetachEvent(I_OnUserRegister); return MOD_RES_PASSTHRU; } +ModResult Module::OnUserPreKick(User*, Membership*, const std::string&) { DetachEvent(I_OnUserPreKick); return MOD_RES_PASSTHRU; } +void Module::OnUserKick(User*, Membership*, const std::string&, CUList&) { DetachEvent(I_OnUserKick); } +ModResult Module::OnRawMode(User*, Channel*, ModeHandler*, const std::string&, bool) { DetachEvent(I_OnRawMode); return MOD_RES_PASSTHRU; } +ModResult Module::OnCheckInvite(User*, Channel*) { DetachEvent(I_OnCheckInvite); return MOD_RES_PASSTHRU; } +ModResult Module::OnCheckKey(User*, Channel*, const std::string&) { DetachEvent(I_OnCheckKey); return MOD_RES_PASSTHRU; } +ModResult Module::OnCheckLimit(User*, Channel*) { DetachEvent(I_OnCheckLimit); return MOD_RES_PASSTHRU; } +ModResult Module::OnCheckChannelBan(User*, Channel*) { DetachEvent(I_OnCheckChannelBan); return MOD_RES_PASSTHRU; } +ModResult Module::OnCheckBan(User*, Channel*, const std::string&) { DetachEvent(I_OnCheckBan); return MOD_RES_PASSTHRU; } +ModResult Module::OnExtBanCheck(User*, Channel*, char) { DetachEvent(I_OnExtBanCheck); return MOD_RES_PASSTHRU; } +ModResult Module::OnPreChangeHost(LocalUser*, const std::string&) { DetachEvent(I_OnPreChangeHost); return MOD_RES_PASSTHRU; } +ModResult Module::OnPreChangeRealName(LocalUser*, const std::string&) { DetachEvent(I_OnPreChangeRealName); return MOD_RES_PASSTHRU; } +ModResult Module::OnPreTopicChange(User*, Channel*, const std::string&) { DetachEvent(I_OnPreTopicChange); return MOD_RES_PASSTHRU; } +ModResult Module::OnPassCompare(Extensible* ex, const std::string &password, const std::string &input, const std::string& hashtype) { DetachEvent(I_OnPassCompare); return MOD_RES_PASSTHRU; } +void Module::OnPostConnect(User*) { DetachEvent(I_OnPostConnect); } +void Module::OnUserPostMessage(User*, const MessageTarget&, const MessageDetails&) { DetachEvent(I_OnUserPostMessage); } +void Module::OnUserMessageBlocked(User*, const MessageTarget&, const MessageDetails&) { DetachEvent(I_OnUserMessageBlocked); } +void Module::OnUserInvite(User*, User*, Channel*, time_t, unsigned int, CUList&) { DetachEvent(I_OnUserInvite); } +void Module::OnPostTopicChange(User*, Channel*, const std::string&) { DetachEvent(I_OnPostTopicChange); } +void Module::OnDecodeMetaData(Extensible*, const std::string&, const std::string&) { DetachEvent(I_OnDecodeMetaData); } +void Module::OnChangeHost(User*, const std::string&) { DetachEvent(I_OnChangeHost); } +void Module::OnChangeRealName(User*, const std::string&) { DetachEvent(I_OnChangeRealName); } +void Module::OnChangeIdent(User*, const std::string&) { DetachEvent(I_OnChangeIdent); } +void Module::OnAddLine(User*, XLine*) { DetachEvent(I_OnAddLine); } +void Module::OnDelLine(User*, XLine*) { DetachEvent(I_OnDelLine); } +void Module::OnExpireLine(XLine*) { DetachEvent(I_OnExpireLine); } +void Module::OnCleanup(ExtensionItem::ExtensibleType, Extensible*) { } +ModResult Module::OnChannelPreDelete(Channel*) { DetachEvent(I_OnChannelPreDelete); return MOD_RES_PASSTHRU; } +void Module::OnChannelDelete(Channel*) { DetachEvent(I_OnChannelDelete); } +void Module::OnBuildNeighborList(User*, IncludeChanList&, std::map<User*,bool>&) { DetachEvent(I_OnBuildNeighborList); } +void Module::OnGarbageCollect() { DetachEvent(I_OnGarbageCollect); } +ModResult Module::OnSetConnectClass(LocalUser* user, ConnectClass* myclass) { DetachEvent(I_OnSetConnectClass); return MOD_RES_PASSTHRU; } +void Module::OnUserMessage(User*, const MessageTarget&, const MessageDetails&) { DetachEvent(I_OnUserMessage); } +ModResult Module::OnNamesListItem(User*, Membership*, std::string&, std::string&) { DetachEvent(I_OnNamesListItem); return MOD_RES_PASSTHRU; } +ModResult Module::OnNumeric(User*, const Numeric::Numeric&) { DetachEvent(I_OnNumeric); return MOD_RES_PASSTHRU; } +ModResult Module::OnAcceptConnection(int, ListenSocket*, irc::sockets::sockaddrs*, irc::sockets::sockaddrs*) { DetachEvent(I_OnAcceptConnection); return MOD_RES_PASSTHRU; } +ModResult Module::OnSendWhoLine(User*, const std::vector<std::string>&, User*, Membership*, Numeric::Numeric&) { DetachEvent(I_OnSendWhoLine); return MOD_RES_PASSTHRU; } +void Module::OnSetUserIP(LocalUser*) { DetachEvent(I_OnSetUserIP); } +void Module::OnServiceAdd(ServiceProvider&) { DetachEvent(I_OnServiceAdd); } +void Module::OnServiceDel(ServiceProvider&) { DetachEvent(I_OnServiceDel); } +ModResult Module::OnUserWrite(LocalUser*, ClientProtocol::Message&) { DetachEvent(I_OnUserWrite); return MOD_RES_PASSTHRU; } + +#ifdef INSPIRCD_ENABLE_TESTSUITE void Module::OnRunTestSuite() { } -void Module::OnNamesListItem(User*, Membership*, std::string&, std::string&) { } -ModResult Module::OnNumeric(User*, unsigned int, const std::string&) { return MOD_RES_PASSTHRU; } -void Module::OnHookIO(StreamSocket*, ListenSocket*) { } -ModResult Module::OnAcceptConnection(int, ListenSocket*, irc::sockets::sockaddrs*, irc::sockets::sockaddrs*) { return MOD_RES_PASSTHRU; } -void Module::OnSendWhoLine(User*, const std::vector<std::string>&, User*, std::string&) { } -void Module::OnSetUserIP(LocalUser*) { } +#endif + +ServiceProvider::ServiceProvider(Module* Creator, const std::string& Name, ServiceType Type) + : creator(Creator), name(Name), service(Type) +{ + if ((ServerInstance) && (ServerInstance->Modules->NewServices)) + ServerInstance->Modules->NewServices->push_back(this); +} -ModuleManager::ModuleManager() : ModCount(0) +void ServiceProvider::DisableAutoRegister() +{ + if ((ServerInstance) && (ServerInstance->Modules->NewServices)) + stdalgo::erase(*ServerInstance->Modules->NewServices, this); +} + +ModuleManager::ModuleManager() { } @@ -189,7 +166,7 @@ ModuleManager::~ModuleManager() bool ModuleManager::Attach(Implementation i, Module* mod) { - if (std::find(EventHandlers[i].begin(), EventHandlers[i].end(), mod) != EventHandlers[i].end()) + if (stdalgo::isin(EventHandlers[i], mod)) return false; EventHandlers[i].push_back(mod); @@ -198,13 +175,7 @@ bool ModuleManager::Attach(Implementation i, Module* mod) bool ModuleManager::Detach(Implementation i, Module* mod) { - EventHandlerIter x = std::find(EventHandlers[i].begin(), EventHandlers[i].end(), mod); - - if (x == EventHandlers[i].end()) - return false; - - EventHandlers[i].erase(x); - return true; + return stdalgo::erase(EventHandlers[i], mod); } void ModuleManager::Attach(Implementation* i, Module* mod, size_t sz) @@ -213,18 +184,22 @@ void ModuleManager::Attach(Implementation* i, Module* mod, size_t sz) Attach(i[n], mod); } +void ModuleManager::AttachAll(Module* mod) +{ + for (size_t i = 0; i != I_END; ++i) + Attach((Implementation)i, mod); +} + void ModuleManager::DetachAll(Module* mod) { - for (size_t n = I_BEGIN + 1; n != I_END; ++n) + for (size_t n = 0; n != I_END; ++n) Detach((Implementation)n, mod); } -bool ModuleManager::SetPriority(Module* mod, Priority s) +void ModuleManager::SetPriority(Module* mod, Priority s) { - for (size_t n = I_BEGIN + 1; n != I_END; ++n) + for (size_t n = 0; n != I_END; ++n) SetPriority(mod, (Implementation)n, s); - - return true; } bool ModuleManager::SetPriority(Module* mod, Implementation i, Priority s, Module* which) @@ -255,22 +230,25 @@ bool ModuleManager::SetPriority(Module* mod, Implementation i, Priority s, Modul return false; found_src: + // The modules registered for a hook are called in reverse order (to allow for easier removal + // of list entries while looping), meaning that the Priority given to us has the exact opposite effect + // on the list, e.g.: PRIORITY_BEFORE will actually put 'mod' after 'which', etc. size_t swap_pos = my_pos; switch (s) { - case PRIORITY_FIRST: + case PRIORITY_LAST: if (prioritizationState != PRIO_STATE_FIRST) return true; else swap_pos = 0; break; - case PRIORITY_LAST: + case PRIORITY_FIRST: if (prioritizationState != PRIO_STATE_FIRST) return true; else swap_pos = EventHandlers[i].size() - 1; break; - case PRIORITY_AFTER: + case PRIORITY_BEFORE: { /* Find the latest possible position, only searching AFTER our position */ for (size_t x = EventHandlers[i].size() - 1; x > my_pos; --x) @@ -285,7 +263,7 @@ found_src: return true; } /* Place this module before a set of other modules */ - case PRIORITY_BEFORE: + case PRIORITY_AFTER: { for (size_t x = 0; x < my_pos; ++x) { @@ -325,6 +303,29 @@ swap_now: return true; } +bool ModuleManager::PrioritizeHooks() +{ + /* We give every module a chance to re-prioritize when we introduce a new one, + * not just the one thats loading, as the new module could affect the preference + * of others + */ + for (int tries = 0; tries < 20; tries++) + { + prioritizationState = tries > 0 ? PRIO_STATE_LAST : PRIO_STATE_FIRST; + for (std::map<std::string, Module*>::iterator n = Modules.begin(); n != Modules.end(); ++n) + n->second->Prioritize(); + + if (prioritizationState == PRIO_STATE_LAST) + break; + if (tries == 19) + { + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "Hook priority dependency loop detected"); + return false; + } + } + return true; +} + bool ModuleManager::CanUnload(Module* mod) { std::map<std::string, Module*>::iterator modfind = Modules.find(mod->ModuleSourceFile); @@ -332,13 +333,7 @@ bool ModuleManager::CanUnload(Module* mod) if ((modfind == Modules.end()) || (modfind->second != mod) || (mod->dying)) { LastModuleError = "Module " + mod->ModuleSourceFile + " is not loaded, cannot unload it!"; - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); - return false; - } - if (mod->GetVersion().Flags & VF_STATIC) - { - LastModuleError = "Module " + mod->ModuleSourceFile + " not unloadable (marked static)"; - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); return false; } @@ -346,41 +341,59 @@ bool ModuleManager::CanUnload(Module* mod) return true; } +void ModuleManager::UnregisterModes(Module* mod, ModeType modetype) +{ + const ModeParser::ModeHandlerMap& modes = ServerInstance->Modes.GetModes(modetype); + for (ModeParser::ModeHandlerMap::const_iterator i = modes.begin(); i != modes.end(); ) + { + ModeHandler* const mh = i->second; + ++i; + if (mh->creator == mod) + this->DelService(*mh); + } +} + void ModuleManager::DoSafeUnload(Module* mod) { + // First, notify all modules that a module is about to be unloaded, so in case + // they pass execution to the soon to be unloaded module, it will happen now, + // i.e. before we unregister the services of the module being unloaded + FOREACH_MOD(OnUnloadModule, (mod)); + std::map<std::string, Module*>::iterator modfind = Modules.find(mod->ModuleSourceFile); + // Unregister modes before extensions because modes may require their extension to show the mode being unset + UnregisterModes(mod, MODETYPE_USER); + UnregisterModes(mod, MODETYPE_CHANNEL); + std::vector<reference<ExtensionItem> > items; ServerInstance->Extensions.BeginUnregister(modfind->second, items); /* Give the module a chance to tidy out all its metadata */ - for (chan_hash::iterator c = ServerInstance->chanlist->begin(); c != ServerInstance->chanlist->end(); ) + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator c = chans.begin(); c != chans.end(); ) { Channel* chan = c->second; ++c; - mod->OnCleanup(TYPE_CHANNEL, chan); + mod->OnCleanup(ExtensionItem::EXT_CHANNEL, chan); chan->doUnhookExtensions(items); - const UserMembList* users = chan->GetUsers(); - for(UserMembCIter mi = users->begin(); mi != users->end(); mi++) + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator mi = users.begin(); mi != users.end(); ++mi) + { + mod->OnCleanup(ExtensionItem::EXT_MEMBERSHIP, mi->second); mi->second->doUnhookExtensions(items); + } } - for (user_hash::iterator u = ServerInstance->Users->clientlist->begin(); u != ServerInstance->Users->clientlist->end(); ) + + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator u = users.begin(); u != users.end(); ) { User* user = u->second; // The module may quit the user (e.g. SSL mod unloading) and that will remove it from the container ++u; - mod->OnCleanup(TYPE_USER, user); + mod->OnCleanup(ExtensionItem::EXT_USER, user); user->doUnhookExtensions(items); } - for(char m='A'; m <= 'z'; m++) - { - ModeHandler* mh; - mh = ServerInstance->Modes->FindMode(m, MODETYPE_USER); - if (mh && mh->creator == mod) - ServerInstance->Modes->DelMode(mh); - mh = ServerInstance->Modes->FindMode(m, MODETYPE_CHANNEL); - if (mh && mh->creator == mod) - ServerInstance->Modes->DelMode(mh); - } + for(std::multimap<std::string, ServiceProvider*>::iterator i = DataProviders.begin(); i != DataProviders.end(); ) { std::multimap<std::string, ServiceProvider*>::iterator curr = i++; @@ -390,19 +403,13 @@ void ModuleManager::DoSafeUnload(Module* mod) dynamic_reference_base::reset_all(); - /* Tidy up any dangling resolvers */ - ServerInstance->Res->CleanResolvers(mod); - - FOREACH_MOD(I_OnUnloadModule,OnUnloadModule(mod)); - DetachAll(mod); Modules.erase(modfind); ServerInstance->GlobalCulls.AddItem(mod); - ServerInstance->Logs->Log("MODULE", DEFAULT,"Module %s unloaded",mod->ModuleSourceFile.c_str()); - this->ModCount--; - ServerInstance->BuildISupport(); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "Module %s unloaded",mod->ModuleSourceFile.c_str()); + ServerInstance->ISupport.Build(); } void ModuleManager::UnloadAll() @@ -428,40 +435,109 @@ void ModuleManager::UnloadAll() } } -std::string& ModuleManager::LastError() +namespace { - return LastModuleError; + struct UnloadAction : public ActionBase + { + Module* const mod; + UnloadAction(Module* m) : mod(m) {} + void Call() CXX11_OVERRIDE + { + DLLManager* dll = mod->ModuleDLLManager; + ServerInstance->Modules->DoSafeUnload(mod); + ServerInstance->GlobalCulls.Apply(); + // In pure static mode this is always NULL + delete dll; + ServerInstance->GlobalCulls.AddItem(this); + } + }; } -CmdResult InspIRCd::CallCommandHandler(const std::string &commandname, const std::vector<std::string>& parameters, User* user) +bool ModuleManager::Unload(Module* mod) { - return this->Parser->CallHandler(commandname, parameters, user); + if (!CanUnload(mod)) + return false; + ServerInstance->AtomicActions.AddAction(new UnloadAction(mod)); + return true; } -bool InspIRCd::IsValidModuleCommand(const std::string &commandname, int pcnt, User* user) +void ModuleManager::LoadAll() { - return this->Parser->IsValidCommand(commandname, pcnt, user); + std::map<std::string, ServiceList> servicemap; + LoadCoreModules(servicemap); + + ConfigTagList tags = ServerInstance->Config->ConfTags("module"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + std::string name = ExpandModName(tag->getString("name")); + this->NewServices = &servicemap[name]; + + // Skip modules which are already loaded. + if (Modules.find(name) != Modules.end()) + continue; + + std::cout << "[" << con_green << "*" << con_reset << "] Loading module:\t" << con_green << name << con_reset << std::endl; + if (!this->Load(name, true)) + { + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, this->LastError()); + std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << this->LastError() << std::endl << std::endl; + ServerInstance->Exit(EXIT_STATUS_MODULE); + } + } + + ConfigStatus confstatus; + + for (ModuleMap::const_iterator i = Modules.begin(); i != Modules.end(); ++i) + { + Module* mod = i->second; + try + { + ServerInstance->Logs->Log("MODULE", LOG_DEBUG, "Initializing %s", i->first.c_str()); + AttachAll(mod); + AddServices(servicemap[i->first]); + mod->init(); + mod->ReadConfig(confstatus); + } + catch (CoreException& modexcept) + { + LastModuleError = "Unable to initialize " + mod->ModuleSourceFile + ": " + modexcept.GetReason(); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); + std::cout << std::endl << "[" << con_red << "*" << con_reset << "] " << LastModuleError << std::endl << std::endl; + ServerInstance->Exit(EXIT_STATUS_MODULE); + } + } + + this->NewServices = NULL; + + if (!PrioritizeHooks()) + ServerInstance->Exit(EXIT_STATUS_MODULE); +} + +std::string& ModuleManager::LastError() +{ + return LastModuleError; +} + +void ModuleManager::AddServices(const ServiceList& list) +{ + for (ServiceList::const_iterator i = list.begin(); i != list.end(); ++i) + { + ServiceProvider& s = **i; + AddService(s); + } } void ModuleManager::AddService(ServiceProvider& item) { switch (item.service) { - case SERVICE_COMMAND: - if (!ServerInstance->Parser->AddCommand(static_cast<Command*>(&item))) - throw ModuleException("Command "+std::string(item.name)+" already exists."); - return; - case SERVICE_MODE: - if (!ServerInstance->Modes->AddMode(static_cast<ModeHandler*>(&item))) - throw ModuleException("Mode "+std::string(item.name)+" already exists."); - return; - case SERVICE_METADATA: - if (!ServerInstance->Extensions.Register(static_cast<ExtensionItem*>(&item))) - throw ModuleException("Extension " + std::string(item.name) + " already exists."); - return; case SERVICE_DATA: case SERVICE_IOHOOK: { + if ((!item.name.compare(0, 5, "mode/", 5)) || (!item.name.compare(0, 6, "umode/", 6))) + throw ModuleException("The \"mode/\" and the \"umode\" service name prefixes are reserved."); + DataProviders.insert(std::make_pair(item.name, &item)); std::string::size_type slash = item.name.find('/'); if (slash != std::string::npos) @@ -469,11 +545,14 @@ void ModuleManager::AddService(ServiceProvider& item) DataProviders.insert(std::make_pair(item.name.substr(0, slash), &item)); DataProviders.insert(std::make_pair(item.name.substr(slash + 1), &item)); } - return; + dynamic_reference_base::reset_all(); + break; } default: - throw ModuleException("Cannot add unknown service type"); + item.RegisterService(); } + + FOREACH_MOD(OnServiceAdd, (item)); } void ModuleManager::DelService(ServiceProvider& item) @@ -483,22 +562,18 @@ void ModuleManager::DelService(ServiceProvider& item) case SERVICE_MODE: if (!ServerInstance->Modes->DelMode(static_cast<ModeHandler*>(&item))) throw ModuleException("Mode "+std::string(item.name)+" does not exist."); - return; + // Fall through case SERVICE_DATA: case SERVICE_IOHOOK: { - for(std::multimap<std::string, ServiceProvider*>::iterator i = DataProviders.begin(); i != DataProviders.end(); ) - { - std::multimap<std::string, ServiceProvider*>::iterator curr = i++; - if (curr->second == &item) - DataProviders.erase(curr); - } - dynamic_reference_base::reset_all(); + DelReferent(&item); return; } default: throw ModuleException("Cannot delete unknown service type"); } + + FOREACH_MOD(OnServiceDel, (item)); } ServiceProvider* ModuleManager::FindService(ServiceType type, const std::string& name) @@ -519,85 +594,66 @@ ServiceProvider* ModuleManager::FindService(ServiceType type, const std::string& } } +std::string ModuleManager::ExpandModName(const std::string& modname) +{ + // Transform "callerid" -> "m_callerid.so" unless it already has a ".so" extension, + // so coremods in the "core_*.so" form aren't changed + std::string ret = modname; + if ((modname.length() < 3) || (modname.compare(modname.size() - 3, 3, ".so"))) + ret.insert(0, "m_").append(".so"); + return ret; +} + dynamic_reference_base::dynamic_reference_base(Module* Creator, const std::string& Name) - : name(Name), value(NULL), creator(Creator) + : name(Name), hook(NULL), value(NULL), creator(Creator) { if (!dynrefs) - dynrefs = new std::vector<dynamic_reference_base*>; - dynrefs->push_back(this); + dynrefs = new insp::intrusive_list<dynamic_reference_base>; + dynrefs->push_front(this); + + // Resolve unless there is no ModuleManager (part of class InspIRCd) + if (ServerInstance) + resolve(); } dynamic_reference_base::~dynamic_reference_base() { - for(unsigned int i = 0; i < dynrefs->size(); i++) + dynrefs->erase(this); + if (dynrefs->empty()) { - if (dynrefs->at(i) == this) - { - unsigned int last = dynrefs->size() - 1; - if (i != last) - dynrefs->at(i) = dynrefs->at(last); - dynrefs->erase(dynrefs->begin() + last); - if (dynrefs->empty()) - { - delete dynrefs; - dynrefs = NULL; - } - return; - } + delete dynrefs; + dynrefs = NULL; } } void dynamic_reference_base::SetProvider(const std::string& newname) { name = newname; - ClearCache(); -} - -void dynamic_reference_base::lookup() -{ - if (!*this) - throw ModuleException("Dynamic reference to '" + name + "' failed to resolve"); + resolve(); } -dynamic_reference_base::operator bool() +void dynamic_reference_base::resolve() { - if (!value) + // Because find() may return any element with a matching key in case count(key) > 1 use lower_bound() + // to ensure a dynref with the same name as another one resolves to the same object + std::multimap<std::string, ServiceProvider*>::iterator i = ServerInstance->Modules.DataProviders.lower_bound(name); + if ((i != ServerInstance->Modules.DataProviders.end()) && (i->first == this->name)) { - std::multimap<std::string, ServiceProvider*>::iterator i = ServerInstance->Modules->DataProviders.find(name); - if (i != ServerInstance->Modules->DataProviders.end()) - value = static_cast<DataProvider*>(i->second); + ServiceProvider* newvalue = i->second; + if (value != newvalue) + { + value = newvalue; + if (hook) + hook->OnCapture(); + } } - return (value != NULL); -} - -void InspIRCd::SendMode(const std::vector<std::string>& parameters, User *user) -{ - this->Modes->Process(parameters, user); -} - - -void InspIRCd::SendGlobalMode(const std::vector<std::string>& parameters, User *user) -{ - Modes->Process(parameters, user); - if (!Modes->GetLastParse().empty()) - this->PI->SendMode(parameters[0], Modes->GetLastParseParams(), Modes->GetLastParseTranslate()); -} - -bool InspIRCd::AddResolver(Resolver* r, bool cached) -{ - if (!cached) - return this->Res->AddResolverClass(r); else - { - r->TriggerCachedResult(); - delete r; - return true; - } + value = NULL; } Module* ModuleManager::Find(const std::string &name) { - std::map<std::string, Module*>::iterator modfind = Modules.find(name); + std::map<std::string, Module*>::const_iterator modfind = Modules.find(ExpandModName(name)); if (modfind == Modules.end()) return NULL; @@ -605,181 +661,21 @@ Module* ModuleManager::Find(const std::string &name) return modfind->second; } -const std::vector<std::string> ModuleManager::GetAllModuleNames(int filter) +void ModuleManager::AddReferent(const std::string& name, ServiceProvider* service) { - std::vector<std::string> retval; - for (std::map<std::string, Module*>::iterator x = Modules.begin(); x != Modules.end(); ++x) - if (!filter || (x->second->GetVersion().Flags & filter)) - retval.push_back(x->first); - return retval; -} - -ConfigReader::ConfigReader() -{ - this->error = 0; - ServerInstance->Logs->Log("MODULE", DEBUG, "ConfigReader is deprecated in 2.0; " - "use ServerInstance->Config->ConfValue(\"key\") or ->ConfTags(\"key\") instead"); -} - - -ConfigReader::~ConfigReader() -{ -} - -static ConfigTag* SlowGetTag(const std::string &tag, int index) -{ - ConfigTagList tags = ServerInstance->Config->ConfTags(tag); - while (tags.first != tags.second) - { - if (!index) - return tags.first->second; - tags.first++; - index--; - } - return NULL; -} - -std::string ConfigReader::ReadValue(const std::string &tag, const std::string &name, const std::string &default_value, int index, bool allow_linefeeds) -{ - std::string result = default_value; - ConfigTag* conftag = SlowGetTag(tag, index); - if (!conftag || !conftag->readString(name, result, allow_linefeeds)) - { - this->error = CONF_VALUE_NOT_FOUND; - } - return result; -} - -std::string ConfigReader::ReadValue(const std::string &tag, const std::string &name, int index, bool allow_linefeeds) -{ - return ReadValue(tag, name, "", index, allow_linefeeds); -} - -bool ConfigReader::ReadFlag(const std::string &tag, const std::string &name, const std::string &default_value, int index) -{ - bool def = (default_value == "yes"); - ConfigTag* conftag = SlowGetTag(tag, index); - return conftag ? conftag->getBool(name, def) : def; -} - -bool ConfigReader::ReadFlag(const std::string &tag, const std::string &name, int index) -{ - return ReadFlag(tag, name, "", index); -} - - -int ConfigReader::ReadInteger(const std::string &tag, const std::string &name, const std::string &default_value, int index, bool need_positive) -{ - int v = atoi(default_value.c_str()); - ConfigTag* conftag = SlowGetTag(tag, index); - int result = conftag ? conftag->getInt(name, v) : v; - - if ((need_positive) && (result < 0)) - { - this->error = CONF_INT_NEGATIVE; - return 0; - } - - return result; -} - -int ConfigReader::ReadInteger(const std::string &tag, const std::string &name, int index, bool need_positive) -{ - return ReadInteger(tag, name, "", index, need_positive); -} - -long ConfigReader::GetError() -{ - long olderr = this->error; - this->error = 0; - return olderr; -} - -int ConfigReader::Enumerate(const std::string &tag) -{ - ServerInstance->Logs->Log("MODULE", DEBUG, "Module is using ConfigReader::Enumerate on %s; this is slow!", - tag.c_str()); - int i=0; - while (SlowGetTag(tag, i)) i++; - return i; -} - -FileReader::FileReader(const std::string &filename) -{ - LoadFile(filename); -} - -FileReader::FileReader() -{ -} - -std::string FileReader::Contents() -{ - std::string x; - for (file_cache::iterator a = this->fc.begin(); a != this->fc.end(); a++) - { - x.append(*a); - x.append("\r\n"); - } - return x; -} - -unsigned long FileReader::ContentSize() -{ - return this->contentsize; -} - -void FileReader::CalcSize() -{ - unsigned long n = 0; - for (file_cache::iterator a = this->fc.begin(); a != this->fc.end(); a++) - n += (a->length() + 2); - this->contentsize = n; + DataProviders.insert(std::make_pair(name, service)); + dynamic_reference_base::reset_all(); } -void FileReader::LoadFile(const std::string &filename) +void ModuleManager::DelReferent(ServiceProvider* service) { - std::map<std::string, file_cache>::iterator file = ServerInstance->Config->Files.find(filename); - if (file != ServerInstance->Config->Files.end()) + for (std::multimap<std::string, ServiceProvider*>::iterator i = DataProviders.begin(); i != DataProviders.end(); ) { - this->fc = file->second; + ServiceProvider* curr = i->second; + if (curr == service) + DataProviders.erase(i++); + else + ++i; } - else - { - fc.clear(); - FILE* f = fopen(filename.c_str(), "r"); - if (!f) - return; - char linebuf[MAXBUF*10]; - while (fgets(linebuf, sizeof(linebuf), f)) - { - int len = strlen(linebuf); - if (len) - fc.push_back(std::string(linebuf, len - 1)); - } - fclose(f); - } - CalcSize(); -} - - -FileReader::~FileReader() -{ -} - -bool FileReader::Exists() -{ - return (!(fc.size() == 0)); -} - -std::string FileReader::GetLine(int x) -{ - if ((x<0) || ((unsigned)x>=fc.size())) - return ""; - return fc[x]; -} - -int FileReader::FileSize() -{ - return fc.size(); + dynamic_reference_base::reset_all(); } diff --git a/src/modules/extra/m_geoip.cpp b/src/modules/extra/m_geoip.cpp index 03b7a55f7..0d7c2eb70 100644 --- a/src/modules/extra/m_geoip.cpp +++ b/src/modules/extra/m_geoip.cpp @@ -17,9 +17,25 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: find_compiler_flags("geoip" "") +/// $LinkerFlags: find_linker_flags("geoip" "-lGeoIP") + +/// $PackageInfo: require_system("centos" "7.0") GeoIP-devel pkgconfig +/// $PackageInfo: require_system("darwin") geoip pkg-config +/// $PackageInfo: require_system("debian") libgeoip-dev pkg-config +/// $PackageInfo: require_system("ubuntu") libgeoip-dev pkg-config #include "inspircd.h" #include "xline.h" +#include "modules/stats.h" +#include "modules/whois.h" + +// Fix warnings about the use of commas at end of enumerator lists on C++03. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-extensions" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-pedantic" +#endif #include <GeoIP.h> @@ -27,41 +43,46 @@ # pragma comment(lib, "GeoIP.lib") #endif -/* $ModDesc: Provides a way to restrict users by country using GeoIP lookup */ -/* $LinkerFlags: -lGeoIP */ +enum +{ + // InspIRCd-specific. + RPL_WHOISCOUNTRY = 344 +}; -class ModuleGeoIP : public Module +class ModuleGeoIP : public Module, public Stats::EventListener, public Whois::EventListener { - LocalStringExt ext; + StringExtItem ext; + bool extban; GeoIP* gi; - std::string* SetExt(LocalUser* user) + std::string* SetExt(User* user) { - const char* c = GeoIP_country_code_by_addr(gi, user->GetIPString()); + const char* c = GeoIP_country_code_by_addr(gi, user->GetIPString().c_str()); if (!c) c = "UNK"; - std::string* cc = new std::string(c); - ext.set(user, cc); - return cc; + ext.set(user, c); + return ext.get(user); } public: - ModuleGeoIP() : ext("geoip_cc", this), gi(NULL) + ModuleGeoIP() + : Stats::EventListener(this) + , Whois::EventListener(this) + , ext("geoip_cc", ExtensionItem::EXT_USER, this) + , extban(true) + , gi(NULL) { } - void init() + void init() CXX11_OVERRIDE { gi = GeoIP_new(GEOIP_STANDARD); if (gi == NULL) throw ModuleException("Unable to initialize geoip, are you missing GeoIP.dat?"); - ServerInstance->Modules->AddService(ext); - Implementation eventlist[] = { I_OnSetConnectClass, I_OnSetUserIP, I_OnStats }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); ++i) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { LocalUser* user = *i; if ((user->registered == REG_ALL) && (!ext.get(user))) @@ -77,12 +98,38 @@ class ModuleGeoIP : public Module GeoIP_delete(gi); } - Version GetVersion() + void ReadConfig(ConfigStatus&) CXX11_OVERRIDE { - return Version("Provides a way to assign users to connect classes by country using GeoIP lookup", VF_VENDOR); + ConfigTag* tag = ServerInstance->Config->ConfValue("geoip"); + extban = tag->getBool("extban"); } - ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides a way to assign users to connect classes by country using GeoIP lookup", VF_OPTCOMMON|VF_VENDOR); + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + if (extban) + tokens["EXTBAN"].push_back('G'); + } + + ModResult OnCheckBan(User* user, Channel*, const std::string& mask) CXX11_OVERRIDE + { + if (extban && (mask.length() > 2) && (mask[0] == 'G') && (mask[1] == ':')) + { + std::string* cc = ext.get(user); + if (!cc) + cc = SetExt(user); + + if (InspIRCd::Match(*cc, mask.substr(2))) + return MOD_RES_DENY; + } + return MOD_RES_PASSTHRU; + } + + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { std::string* cc = ext.get(user); if (!cc) @@ -99,21 +146,36 @@ class ModuleGeoIP : public Module return MOD_RES_DENY; } - void OnSetUserIP(LocalUser* user) + void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE { // If user has sent NICK/USER, re-set the ExtItem as this is likely CGI:IRC changing the IP if (user->registered == REG_NICKUSER) SetExt(user); } - ModResult OnStats(char symbol, User* user, string_list &out) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE + { + // If the extban is disabled we don't expose users location. + if (!extban) + return; + + std::string* cc = ext.get(whois.GetTarget()); + if (!cc) + cc = SetExt(whois.GetTarget()); + + whois.SendLine(RPL_WHOISCOUNTRY, *cc, "is located in this country"); + } + + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if (symbol != 'G') + if (stats.GetSymbol() != 'G') return MOD_RES_PASSTHRU; unsigned int unknown = 0; std::map<std::string, unsigned int> results; - for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); ++i) + + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { std::string* cc = ext.get(*i); if (cc) @@ -122,18 +184,16 @@ class ModuleGeoIP : public Module unknown++; } - std::string p = ServerInstance->Config->ServerName + " 801 " + user->nick + " :GeoIPSTATS "; for (std::map<std::string, unsigned int>::const_iterator i = results.begin(); i != results.end(); ++i) { - out.push_back(p + i->first + " " + ConvToStr(i->second)); + stats.AddRow(801, "GeoIPSTATS " + i->first + " " + ConvToStr(i->second)); } if (unknown) - out.push_back(p + "Unknown " + ConvToStr(unknown)); + stats.AddRow(801, "GeoIPSTATS Unknown " + ConvToStr(unknown)); return MOD_RES_DENY; } }; MODULE_INIT(ModuleGeoIP) - diff --git a/src/modules/extra/m_ldap.cpp b/src/modules/extra/m_ldap.cpp new file mode 100644 index 000000000..8c2752dbf --- /dev/null +++ b/src/modules/extra/m_ldap.cpp @@ -0,0 +1,677 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013-2015 Adam <Adam@anope.org> + * Copyright (C) 2003-2015 Anope Team <team@anope.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/// $LinkerFlags: -llber -lldap_r + +/// $PackageInfo: require_system("centos") openldap-devel +/// $PackageInfo: require_system("debian") libldap2-dev +/// $PackageInfo: require_system("ubuntu") libldap2-dev + +#include "inspircd.h" +#include "modules/ldap.h" + +// Ignore OpenLDAP deprecation warnings on OS X Yosemite and newer. +#if defined __APPLE__ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include <ldap.h> + +#ifdef _WIN32 +# pragma comment(lib, "libldap_r.lib") +# pragma comment(lib, "liblber.lib") +#endif + +class LDAPService; + +class LDAPRequest +{ + public: + LDAPService* service; + LDAPInterface* inter; + LDAPMessage* message; /* message returned by ldap_ */ + LDAPResult* result; /* final result */ + struct timeval tv; + QueryType type; + + LDAPRequest(LDAPService* s, LDAPInterface* i) + : service(s) + , inter(i) + , message(NULL) + , result(NULL) + { + type = QUERY_UNKNOWN; + tv.tv_sec = 0; + tv.tv_usec = 100000; + } + + virtual ~LDAPRequest() + { + delete result; + if (message != NULL) + ldap_msgfree(message); + } + + virtual int run() = 0; +}; + +class LDAPBind : public LDAPRequest +{ + std::string who, pass; + + public: + LDAPBind(LDAPService* s, LDAPInterface* i, const std::string& w, const std::string& p) + : LDAPRequest(s, i) + , who(w) + , pass(p) + { + type = QUERY_BIND; + } + + int run() CXX11_OVERRIDE; +}; + +class LDAPSearch : public LDAPRequest +{ + std::string base; + int searchscope; + std::string filter; + + public: + LDAPSearch(LDAPService* s, LDAPInterface* i, const std::string& b, int se, const std::string& f) + : LDAPRequest(s, i) + , base(b) + , searchscope(se) + , filter(f) + { + type = QUERY_SEARCH; + } + + int run() CXX11_OVERRIDE; +}; + +class LDAPAdd : public LDAPRequest +{ + std::string dn; + LDAPMods attributes; + + public: + LDAPAdd(LDAPService* s, LDAPInterface* i, const std::string& d, const LDAPMods& attr) + : LDAPRequest(s, i) + , dn(d) + , attributes(attr) + { + type = QUERY_ADD; + } + + int run() CXX11_OVERRIDE; +}; + +class LDAPDel : public LDAPRequest +{ + std::string dn; + + public: + LDAPDel(LDAPService* s, LDAPInterface* i, const std::string& d) + : LDAPRequest(s, i) + , dn(d) + { + type = QUERY_DELETE; + } + + int run() CXX11_OVERRIDE; +}; + +class LDAPModify : public LDAPRequest +{ + std::string base; + LDAPMods attributes; + + public: + LDAPModify(LDAPService* s, LDAPInterface* i, const std::string& b, const LDAPMods& attr) + : LDAPRequest(s, i) + , base(b) + , attributes(attr) + { + type = QUERY_MODIFY; + } + + int run() CXX11_OVERRIDE; +}; + +class LDAPCompare : public LDAPRequest +{ + std::string dn, attr, val; + + public: + LDAPCompare(LDAPService* s, LDAPInterface* i, const std::string& d, const std::string& a, const std::string& v) + : LDAPRequest(s, i) + , dn(d) + , attr(a) + , val(v) + { + type = QUERY_COMPARE; + } + + int run() CXX11_OVERRIDE; +}; + +class LDAPService : public LDAPProvider, public SocketThread +{ + LDAP* con; + reference<ConfigTag> config; + time_t last_connect; + int searchscope; + time_t timeout; + + public: + static LDAPMod** BuildMods(const LDAPMods& attributes) + { + LDAPMod** mods = new LDAPMod*[attributes.size() + 1]; + memset(mods, 0, sizeof(LDAPMod*) * (attributes.size() + 1)); + for (unsigned int x = 0; x < attributes.size(); ++x) + { + const LDAPModification& l = attributes[x]; + LDAPMod* mod = new LDAPMod; + mods[x] = mod; + + if (l.op == LDAPModification::LDAP_ADD) + mod->mod_op = LDAP_MOD_ADD; + else if (l.op == LDAPModification::LDAP_DEL) + mod->mod_op = LDAP_MOD_DELETE; + else if (l.op == LDAPModification::LDAP_REPLACE) + mod->mod_op = LDAP_MOD_REPLACE; + else if (l.op != 0) + { + FreeMods(mods); + throw LDAPException("Unknown LDAP operation"); + } + mod->mod_type = strdup(l.name.c_str()); + mod->mod_values = new char*[l.values.size() + 1]; + memset(mod->mod_values, 0, sizeof(char*) * (l.values.size() + 1)); + for (unsigned int j = 0, c = 0; j < l.values.size(); ++j) + if (!l.values[j].empty()) + mod->mod_values[c++] = strdup(l.values[j].c_str()); + } + return mods; + } + + static void FreeMods(LDAPMod** mods) + { + for (unsigned int i = 0; mods[i] != NULL; ++i) + { + LDAPMod* mod = mods[i]; + if (mod->mod_type != NULL) + free(mod->mod_type); + if (mod->mod_values != NULL) + { + for (unsigned int j = 0; mod->mod_values[j] != NULL; ++j) + free(mod->mod_values[j]); + delete[] mod->mod_values; + } + } + delete[] mods; + } + + private: + void Reconnect() + { + // Only try one connect a minute. It is an expensive blocking operation + if (last_connect > ServerInstance->Time() - 60) + throw LDAPException("Unable to connect to LDAP service " + this->name + ": reconnecting too fast"); + last_connect = ServerInstance->Time(); + + ldap_unbind_ext(this->con, NULL, NULL); + Connect(); + } + + void QueueRequest(LDAPRequest* r) + { + this->LockQueue(); + this->queries.push_back(r); + this->UnlockQueueWakeup(); + } + + public: + typedef std::vector<LDAPRequest*> query_queue; + query_queue queries, results; + Mutex process_mutex; /* held when processing requests not in either queue */ + + LDAPService(Module* c, ConfigTag* tag) + : LDAPProvider(c, "LDAP/" + tag->getString("id")) + , con(NULL), config(tag), last_connect(0) + { + std::string scope = config->getString("searchscope"); + if (stdalgo::string::equalsci(scope, "base")) + searchscope = LDAP_SCOPE_BASE; + else if (stdalgo::string::equalsci(scope, "onelevel")) + searchscope = LDAP_SCOPE_ONELEVEL; + else + searchscope = LDAP_SCOPE_SUBTREE; + timeout = config->getDuration("timeout", 5); + + Connect(); + } + + ~LDAPService() + { + this->LockQueue(); + + for (unsigned int i = 0; i < this->queries.size(); ++i) + { + LDAPRequest* req = this->queries[i]; + + /* queries have no results yet */ + req->result = new LDAPResult(); + req->result->type = req->type; + req->result->error = "LDAP Interface is going away"; + req->inter->OnError(*req->result); + + delete req; + } + this->queries.clear(); + + for (unsigned int i = 0; i < this->results.size(); ++i) + { + LDAPRequest* req = this->results[i]; + + /* even though this may have already finished successfully we return that it didn't */ + req->result->error = "LDAP Interface is going away"; + req->inter->OnError(*req->result); + + delete req; + } + this->results.clear(); + + this->UnlockQueue(); + + ldap_unbind_ext(this->con, NULL, NULL); + } + + void Connect() + { + std::string server = config->getString("server"); + int i = ldap_initialize(&this->con, server.c_str()); + if (i != LDAP_SUCCESS) + throw LDAPException("Unable to connect to LDAP service " + this->name + ": " + ldap_err2string(i)); + + const int version = LDAP_VERSION3; + i = ldap_set_option(this->con, LDAP_OPT_PROTOCOL_VERSION, &version); + if (i != LDAP_OPT_SUCCESS) + { + ldap_unbind_ext(this->con, NULL, NULL); + this->con = NULL; + throw LDAPException("Unable to set protocol version for " + this->name + ": " + ldap_err2string(i)); + } + + const struct timeval tv = { 0, 0 }; + i = ldap_set_option(this->con, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if (i != LDAP_OPT_SUCCESS) + { + ldap_unbind_ext(this->con, NULL, NULL); + this->con = NULL; + throw LDAPException("Unable to set timeout for " + this->name + ": " + ldap_err2string(i)); + } + } + + void BindAsManager(LDAPInterface* i) CXX11_OVERRIDE + { + std::string binddn = config->getString("binddn"); + std::string bindauth = config->getString("bindauth"); + this->Bind(i, binddn, bindauth); + } + + void Bind(LDAPInterface* i, const std::string& who, const std::string& pass) CXX11_OVERRIDE + { + LDAPBind* b = new LDAPBind(this, i, who, pass); + QueueRequest(b); + } + + void Search(LDAPInterface* i, const std::string& base, const std::string& filter) CXX11_OVERRIDE + { + if (i == NULL) + throw LDAPException("No interface"); + + LDAPSearch* s = new LDAPSearch(this, i, base, searchscope, filter); + QueueRequest(s); + } + + void Add(LDAPInterface* i, const std::string& dn, LDAPMods& attributes) CXX11_OVERRIDE + { + LDAPAdd* add = new LDAPAdd(this, i, dn, attributes); + QueueRequest(add); + } + + void Del(LDAPInterface* i, const std::string& dn) CXX11_OVERRIDE + { + LDAPDel* del = new LDAPDel(this, i, dn); + QueueRequest(del); + } + + void Modify(LDAPInterface* i, const std::string& base, LDAPMods& attributes) CXX11_OVERRIDE + { + LDAPModify* mod = new LDAPModify(this, i, base, attributes); + QueueRequest(mod); + } + + void Compare(LDAPInterface* i, const std::string& dn, const std::string& attr, const std::string& val) CXX11_OVERRIDE + { + LDAPCompare* comp = new LDAPCompare(this, i, dn, attr, val); + QueueRequest(comp); + } + + private: + void BuildReply(int res, LDAPRequest* req) + { + LDAPResult* ldap_result = req->result = new LDAPResult(); + req->result->type = req->type; + + if (res != LDAP_SUCCESS) + { + ldap_result->error = ldap_err2string(res); + return; + } + + if (req->message == NULL) + { + return; + } + + /* a search result */ + + for (LDAPMessage* cur = ldap_first_message(this->con, req->message); cur; cur = ldap_next_message(this->con, cur)) + { + LDAPAttributes attributes; + + char* dn = ldap_get_dn(this->con, cur); + if (dn != NULL) + { + attributes["dn"].push_back(dn); + ldap_memfree(dn); + dn = NULL; + } + + BerElement* ber = NULL; + + for (char* attr = ldap_first_attribute(this->con, cur, &ber); attr; attr = ldap_next_attribute(this->con, cur, ber)) + { + berval** vals = ldap_get_values_len(this->con, cur, attr); + int count = ldap_count_values_len(vals); + + std::vector<std::string> attrs; + for (int j = 0; j < count; ++j) + attrs.push_back(vals[j]->bv_val); + attributes[attr] = attrs; + + ldap_value_free_len(vals); + ldap_memfree(attr); + } + if (ber != NULL) + ber_free(ber, 0); + + ldap_result->messages.push_back(attributes); + } + } + + void SendRequests() + { + process_mutex.Lock(); + + query_queue q; + this->LockQueue(); + queries.swap(q); + this->UnlockQueue(); + + if (q.empty()) + { + process_mutex.Unlock(); + return; + } + + for (unsigned int i = 0; i < q.size(); ++i) + { + LDAPRequest* req = q[i]; + int ret = req->run(); + + if (ret == LDAP_SERVER_DOWN || ret == LDAP_TIMEOUT) + { + /* try again */ + try + { + Reconnect(); + } + catch (const LDAPException &) + { + } + + ret = req->run(); + } + + BuildReply(ret, req); + + this->LockQueue(); + this->results.push_back(req); + this->UnlockQueue(); + } + + this->NotifyParent(); + + process_mutex.Unlock(); + } + + public: + void Run() CXX11_OVERRIDE + { + while (!this->GetExitFlag()) + { + this->LockQueue(); + if (this->queries.empty()) + this->WaitForQueue(); + this->UnlockQueue(); + + SendRequests(); + } + } + + void OnNotify() CXX11_OVERRIDE + { + query_queue r; + + this->LockQueue(); + this->results.swap(r); + this->UnlockQueue(); + + for (unsigned int i = 0; i < r.size(); ++i) + { + LDAPRequest* req = r[i]; + LDAPInterface* li = req->inter; + LDAPResult* res = req->result; + + if (!res->error.empty()) + li->OnError(*res); + else + li->OnResult(*res); + + delete req; + } + } + + LDAP* GetConnection() + { + return con; + } +}; + +class ModuleLDAP : public Module +{ + typedef insp::flat_map<std::string, LDAPService*> ServiceMap; + ServiceMap LDAPServices; + + public: + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ServiceMap conns; + + ConfigTagList tags = ServerInstance->Config->ConfTags("database"); + for (ConfigIter i = tags.first; i != tags.second; i++) + { + const reference<ConfigTag>& tag = i->second; + + if (!stdalgo::string::equalsci(tag->getString("module"), "ldap")) + continue; + + std::string id = tag->getString("id"); + + ServiceMap::iterator curr = LDAPServices.find(id); + if (curr == LDAPServices.end()) + { + LDAPService* conn = new LDAPService(this, tag); + conns[id] = conn; + + ServerInstance->Modules->AddService(*conn); + ServerInstance->Threads.Start(conn); + } + else + { + conns.insert(*curr); + LDAPServices.erase(curr); + } + } + + for (ServiceMap::iterator i = LDAPServices.begin(); i != LDAPServices.end(); ++i) + { + LDAPService* conn = i->second; + ServerInstance->Modules->DelService(*conn); + conn->join(); + conn->OnNotify(); + delete conn; + } + + LDAPServices.swap(conns); + } + + void OnUnloadModule(Module* m) CXX11_OVERRIDE + { + for (ServiceMap::iterator it = this->LDAPServices.begin(); it != this->LDAPServices.end(); ++it) + { + LDAPService* s = it->second; + + s->process_mutex.Lock(); + s->LockQueue(); + + for (unsigned int i = s->queries.size(); i > 0; --i) + { + LDAPRequest* req = s->queries[i - 1]; + LDAPInterface* li = req->inter; + + if (li->creator == m) + { + s->queries.erase(s->queries.begin() + i - 1); + delete req; + } + } + + for (unsigned int i = s->results.size(); i > 0; --i) + { + LDAPRequest* req = s->results[i - 1]; + LDAPInterface* li = req->inter; + + if (li->creator == m) + { + s->results.erase(s->results.begin() + i - 1); + delete req; + } + } + + s->UnlockQueue(); + s->process_mutex.Unlock(); + } + } + + ~ModuleLDAP() + { + for (ServiceMap::iterator i = LDAPServices.begin(); i != LDAPServices.end(); ++i) + { + LDAPService* conn = i->second; + conn->join(); + conn->OnNotify(); + delete conn; + } + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("LDAP support", VF_VENDOR); + } +}; + +int LDAPBind::run() +{ + berval cred; + cred.bv_val = strdup(pass.c_str()); + cred.bv_len = pass.length(); + + int i = ldap_sasl_bind_s(service->GetConnection(), who.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); + + free(cred.bv_val); + + return i; +} + +int LDAPSearch::run() +{ + return ldap_search_ext_s(service->GetConnection(), base.c_str(), searchscope, filter.c_str(), NULL, 0, NULL, NULL, &tv, 0, &message); +} + +int LDAPAdd::run() +{ + LDAPMod** mods = LDAPService::BuildMods(attributes); + int i = ldap_add_ext_s(service->GetConnection(), dn.c_str(), mods, NULL, NULL); + LDAPService::FreeMods(mods); + return i; +} + +int LDAPDel::run() +{ + return ldap_delete_ext_s(service->GetConnection(), dn.c_str(), NULL, NULL); +} + +int LDAPModify::run() +{ + LDAPMod** mods = LDAPService::BuildMods(attributes); + int i = ldap_modify_ext_s(service->GetConnection(), base.c_str(), mods, NULL, NULL); + LDAPService::FreeMods(mods); + return i; +} + +int LDAPCompare::run() +{ + berval cred; + cred.bv_val = strdup(val.c_str()); + cred.bv_len = val.length(); + + int ret = ldap_compare_ext_s(service->GetConnection(), dn.c_str(), attr.c_str(), &cred, NULL, NULL); + + free(cred.bv_val); + + return ret; + +} + +MODULE_INIT(ModuleLDAP) diff --git a/src/modules/extra/m_ldapauth.cpp b/src/modules/extra/m_ldapauth.cpp deleted file mode 100644 index 405bab082..000000000 --- a/src/modules/extra/m_ldapauth.cpp +++ /dev/null @@ -1,425 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2011 Pierre Carrier <pierre@spotify.com> - * Copyright (C) 2009-2010 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2008 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2007 Carsten Valdemar Munk <carsten.munk+inspircd@gmail.com> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "users.h" -#include "channels.h" -#include "modules.h" - -#include <ldap.h> - -#ifdef _WIN32 -# pragma comment(lib, "libldap.lib") -# pragma comment(lib, "liblber.lib") -#endif - -/* $ModDesc: Allow/Deny connections based upon answer from LDAP server */ -/* $LinkerFlags: -lldap */ - -struct RAIILDAPString -{ - char *str; - - RAIILDAPString(char *Str) - : str(Str) - { - } - - ~RAIILDAPString() - { - ldap_memfree(str); - } - - operator char*() - { - return str; - } - - operator std::string() - { - return str; - } -}; - -struct RAIILDAPMessage -{ - RAIILDAPMessage() - { - } - - ~RAIILDAPMessage() - { - dealloc(); - } - - void dealloc() - { - ldap_msgfree(msg); - } - - operator LDAPMessage*() - { - return msg; - } - - LDAPMessage **operator &() - { - return &msg; - } - - LDAPMessage *msg; -}; - -class ModuleLDAPAuth : public Module -{ - LocalIntExt ldapAuthed; - LocalStringExt ldapVhost; - std::string base; - std::string attribute; - std::string ldapserver; - std::string allowpattern; - std::string killreason; - std::string username; - std::string password; - std::string vhost; - std::vector<std::string> whitelistedcidrs; - std::vector<std::pair<std::string, std::string> > requiredattributes; - int searchscope; - bool verbose; - bool useusername; - LDAP *conn; - -public: - ModuleLDAPAuth() - : ldapAuthed("ldapauth", this) - , ldapVhost("ldapauth_vhost", this) - { - conn = NULL; - } - - void init() - { - ServerInstance->Modules->AddService(ldapAuthed); - ServerInstance->Modules->AddService(ldapVhost); - Implementation eventlist[] = { I_OnCheckReady, I_OnRehash,I_OnUserRegister, I_OnUserConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } - - ~ModuleLDAPAuth() - { - if (conn) - ldap_unbind_ext(conn, NULL, NULL); - } - - void OnRehash(User* user) - { - ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth"); - whitelistedcidrs.clear(); - requiredattributes.clear(); - - base = tag->getString("baserdn"); - attribute = tag->getString("attribute"); - ldapserver = tag->getString("server"); - allowpattern = tag->getString("allowpattern"); - killreason = tag->getString("killreason"); - std::string scope = tag->getString("searchscope"); - username = tag->getString("binddn"); - password = tag->getString("bindauth"); - vhost = tag->getString("host"); - verbose = tag->getBool("verbose"); /* Set to true if failed connects should be reported to operators */ - useusername = tag->getBool("userfield"); - - ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist"); - - for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i) - { - std::string cidr = i->second->getString("cidr"); - if (!cidr.empty()) { - whitelistedcidrs.push_back(cidr); - } - } - - ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire"); - - for (ConfigIter i = attributetags.first; i != attributetags.second; ++i) - { - const std::string attr = i->second->getString("attribute"); - const std::string val = i->second->getString("value"); - - if (!attr.empty() && !val.empty()) - requiredattributes.push_back(make_pair(attr, val)); - } - - if (scope == "base") - searchscope = LDAP_SCOPE_BASE; - else if (scope == "onelevel") - searchscope = LDAP_SCOPE_ONELEVEL; - else searchscope = LDAP_SCOPE_SUBTREE; - - Connect(); - } - - bool Connect() - { - if (conn != NULL) - ldap_unbind_ext(conn, NULL, NULL); - int res, v = LDAP_VERSION3; - res = ldap_initialize(&conn, ldapserver.c_str()); - if (res != LDAP_SUCCESS) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "LDAP connection failed: %s", ldap_err2string(res)); - conn = NULL; - return false; - } - - res = ldap_set_option(conn, LDAP_OPT_PROTOCOL_VERSION, (void *)&v); - if (res != LDAP_SUCCESS) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "LDAP set protocol to v3 failed: %s", ldap_err2string(res)); - ldap_unbind_ext(conn, NULL, NULL); - conn = NULL; - return false; - } - return true; - } - - std::string SafeReplace(const std::string &text, std::map<std::string, - std::string> &replacements) - { - std::string result; - result.reserve(MAXBUF); - - for (unsigned int i = 0; i < text.length(); ++i) { - char c = text[i]; - if (c == '$') { - // find the first nonalpha - i++; - unsigned int start = i; - - while (i < text.length() - 1 && isalpha(text[i + 1])) - ++i; - - std::string key = text.substr(start, (i - start) + 1); - result.append(replacements[key]); - } else { - result.push_back(c); - } - } - - return result; - } - - virtual void OnUserConnect(LocalUser *user) - { - std::string* cc = ldapVhost.get(user); - if (cc) - { - user->ChangeDisplayedHost(cc->c_str()); - ldapVhost.unset(user); - } - } - - ModResult OnUserRegister(LocalUser* user) - { - if ((!allowpattern.empty()) && (InspIRCd::Match(user->nick,allowpattern))) - { - ldapAuthed.set(user,1); - return MOD_RES_PASSTHRU; - } - - for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++) - { - if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map)) - { - ldapAuthed.set(user,1); - return MOD_RES_PASSTHRU; - } - } - - if (!CheckCredentials(user)) - { - ServerInstance->Users->QuitUser(user, killreason); - return MOD_RES_DENY; - } - return MOD_RES_PASSTHRU; - } - - bool CheckCredentials(LocalUser* user) - { - if (conn == NULL) - if (!Connect()) - return false; - - if (user->password.empty()) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (No password provided)", user->GetFullRealHost().c_str()); - return false; - } - - int res; - // bind anonymously if no bind DN and authentication are given in the config - struct berval cred; - cred.bv_val = const_cast<char*>(password.c_str()); - cred.bv_len = password.length(); - - if ((res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS) - { - if (res == LDAP_SERVER_DOWN) - { - // Attempt to reconnect if the connection dropped - if (verbose) - ServerInstance->SNO->WriteToSnoMask('a', "LDAP server has gone away - reconnecting..."); - Connect(); - res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); - } - - if (res != LDAP_SUCCESS) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (LDAP bind failed: %s)", user->GetFullRealHost().c_str(), ldap_err2string(res)); - ldap_unbind_ext(conn, NULL, NULL); - conn = NULL; - return false; - } - } - - RAIILDAPMessage msg; - std::string what; - std::string::size_type pos = user->password.find(':'); - // If a username is provided in PASS, use it, othewrise user their nick or ident - if (pos != std::string::npos) - { - what = (attribute + "=" + user->password.substr(0, pos)); - - // Trim the user: prefix, leaving just 'pass' for later password check - user->password = user->password.substr(pos + 1); - } - else - { - what = (attribute + "=" + (useusername ? user->ident : user->nick)); - } - if ((res = ldap_search_ext_s(conn, base.c_str(), searchscope, what.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg)) != LDAP_SUCCESS) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (LDAP search failed: %s)", user->GetFullRealHost().c_str(), ldap_err2string(res)); - return false; - } - if (ldap_count_entries(conn, msg) > 1) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (LDAP search returned more than one result: %s)", user->GetFullRealHost().c_str(), ldap_err2string(res)); - return false; - } - - LDAPMessage *entry; - if ((entry = ldap_first_entry(conn, msg)) == NULL) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (LDAP search returned no results: %s)", user->GetFullRealHost().c_str(), ldap_err2string(res)); - return false; - } - cred.bv_val = (char*)user->password.data(); - cred.bv_len = user->password.length(); - RAIILDAPString DN(ldap_get_dn(conn, entry)); - if ((res = ldap_sasl_bind_s(conn, DN, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), ldap_err2string(res)); - return false; - } - - if (!requiredattributes.empty()) - { - bool authed = false; - - for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it) - { - const std::string &attr = it->first; - const std::string &val = it->second; - - struct berval attr_value; - attr_value.bv_val = const_cast<char*>(val.c_str()); - attr_value.bv_len = val.length(); - - ServerInstance->Logs->Log("m_ldapauth", DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str()); - - authed = (ldap_compare_ext_s(conn, DN, attr.c_str(), &attr_value, NULL, NULL) == LDAP_COMPARE_TRUE); - - if (authed) - break; - } - - if (!authed) - { - if (verbose) - ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (Lacks required LDAP attributes)", user->GetFullRealHost().c_str()); - return false; - } - } - - if (!vhost.empty()) - { - irc::commasepstream stream(DN); - - // mashed map of key:value parts of the DN - std::map<std::string, std::string> dnParts; - - std::string dnPart; - while (stream.GetToken(dnPart)) - { - pos = dnPart.find('='); - if (pos == std::string::npos) // malformed - continue; - - std::string key = dnPart.substr(0, pos); - std::string value = dnPart.substr(pos + 1, dnPart.length() - pos + 1); // +1s to skip the = itself - dnParts[key] = value; - } - - // change host according to config key - ldapVhost.set(user, SafeReplace(vhost, dnParts)); - } - - ldapAuthed.set(user,1); - return true; - } - - ModResult OnCheckReady(LocalUser* user) - { - return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY; - } - - Version GetVersion() - { - return Version("Allow/Deny connections based upon answer from LDAP server", VF_VENDOR); - } - -}; - -MODULE_INIT(ModuleLDAPAuth) diff --git a/src/modules/extra/m_ldapoper.cpp b/src/modules/extra/m_ldapoper.cpp deleted file mode 100644 index 1f46361d4..000000000 --- a/src/modules/extra/m_ldapoper.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Carsten Valdemar Munk <carsten.munk+inspircd@gmail.com> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "users.h" -#include "channels.h" -#include "modules.h" - -#include <ldap.h> - -#ifdef _WIN32 -# pragma comment(lib, "libldap.lib") -# pragma comment(lib, "liblber.lib") -#endif - -/* $ModDesc: Adds the ability to authenticate opers via LDAP */ -/* $LinkerFlags: -lldap */ - -// Duplicated code, also found in cmd_oper and m_sqloper -static bool OneOfMatches(const char* host, const char* ip, const std::string& hostlist) -{ - std::stringstream hl(hostlist); - std::string xhost; - while (hl >> xhost) - { - if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) - { - return true; - } - } - return false; -} - -struct RAIILDAPString -{ - char *str; - - RAIILDAPString(char *Str) - : str(Str) - { - } - - ~RAIILDAPString() - { - ldap_memfree(str); - } - - operator char*() - { - return str; - } - - operator std::string() - { - return str; - } -}; - -class ModuleLDAPAuth : public Module -{ - std::string base; - std::string ldapserver; - std::string username; - std::string password; - std::string attribute; - int searchscope; - LDAP *conn; - - bool HandleOper(LocalUser* user, const std::string& opername, const std::string& inputpass) - { - OperIndex::iterator it = ServerInstance->Config->oper_blocks.find(opername); - if (it == ServerInstance->Config->oper_blocks.end()) - return false; - - ConfigTag* tag = it->second->oper_block; - if (!tag) - return false; - - std::string acceptedhosts = tag->getString("host"); - std::string hostname = user->ident + "@" + user->host; - if (!OneOfMatches(hostname.c_str(), user->GetIPString(), acceptedhosts)) - return false; - - if (!LookupOper(opername, inputpass)) - return false; - - user->Oper(it->second); - return true; - } - -public: - ModuleLDAPAuth() - : conn(NULL) - { - } - - void init() - { - Implementation eventlist[] = { I_OnRehash, I_OnPreCommand }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } - - virtual ~ModuleLDAPAuth() - { - if (conn) - ldap_unbind_ext(conn, NULL, NULL); - } - - virtual void OnRehash(User* user) - { - ConfigTag* tag = ServerInstance->Config->ConfValue("ldapoper"); - - base = tag->getString("baserdn"); - ldapserver = tag->getString("server"); - std::string scope = tag->getString("searchscope"); - username = tag->getString("binddn"); - password = tag->getString("bindauth"); - attribute = tag->getString("attribute"); - - if (scope == "base") - searchscope = LDAP_SCOPE_BASE; - else if (scope == "onelevel") - searchscope = LDAP_SCOPE_ONELEVEL; - else searchscope = LDAP_SCOPE_SUBTREE; - - Connect(); - } - - bool Connect() - { - if (conn != NULL) - ldap_unbind_ext(conn, NULL, NULL); - int res, v = LDAP_VERSION3; - res = ldap_initialize(&conn, ldapserver.c_str()); - if (res != LDAP_SUCCESS) - { - conn = NULL; - return false; - } - - res = ldap_set_option(conn, LDAP_OPT_PROTOCOL_VERSION, (void *)&v); - if (res != LDAP_SUCCESS) - { - ldap_unbind_ext(conn, NULL, NULL); - conn = NULL; - return false; - } - return true; - } - - ModResult OnPreCommand(std::string& command, std::vector<std::string>& parameters, LocalUser* user, bool validated, const std::string& original_line) - { - if (validated && command == "OPER" && parameters.size() >= 2) - { - if (HandleOper(user, parameters[0], parameters[1])) - return MOD_RES_DENY; - } - return MOD_RES_PASSTHRU; - } - - bool LookupOper(const std::string& opername, const std::string& opassword) - { - if (conn == NULL) - if (!Connect()) - return false; - - int res; - char* authpass = strdup(password.c_str()); - // bind anonymously if no bind DN and authentication are given in the config - struct berval cred; - cred.bv_val = authpass; - cred.bv_len = password.length(); - - if ((res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) != LDAP_SUCCESS) - { - if (res == LDAP_SERVER_DOWN) - { - // Attempt to reconnect if the connection dropped - ServerInstance->SNO->WriteToSnoMask('a', "LDAP server has gone away - reconnecting..."); - Connect(); - res = ldap_sasl_bind_s(conn, username.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); - } - - if (res != LDAP_SUCCESS) - { - free(authpass); - ldap_unbind_ext(conn, NULL, NULL); - conn = NULL; - return false; - } - } - free(authpass); - - LDAPMessage *msg, *entry; - std::string what = attribute + "=" + opername; - if ((res = ldap_search_ext_s(conn, base.c_str(), searchscope, what.c_str(), NULL, 0, NULL, NULL, NULL, 0, &msg)) != LDAP_SUCCESS) - { - return false; - } - if (ldap_count_entries(conn, msg) > 1) - { - ldap_msgfree(msg); - return false; - } - if ((entry = ldap_first_entry(conn, msg)) == NULL) - { - ldap_msgfree(msg); - return false; - } - authpass = strdup(opassword.c_str()); - cred.bv_val = authpass; - cred.bv_len = opassword.length(); - RAIILDAPString DN(ldap_get_dn(conn, entry)); - if ((res = ldap_sasl_bind_s(conn, DN, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL)) == LDAP_SUCCESS) - { - free(authpass); - ldap_msgfree(msg); - return true; - } - else - { - free(authpass); - ldap_msgfree(msg); - return false; - } - } - - virtual Version GetVersion() - { - return Version("Adds the ability to authenticate opers via LDAP", VF_VENDOR); - } - -}; - -MODULE_INIT(ModuleLDAPAuth) diff --git a/src/modules/extra/m_mssql.cpp b/src/modules/extra/m_mssql.cpp deleted file mode 100644 index 598f9aac9..000000000 --- a/src/modules/extra/m_mssql.cpp +++ /dev/null @@ -1,870 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2008-2009 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008-2009 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include <tds.h> -#include <tdsconvert.h> -#include "users.h" -#include "channels.h" -#include "modules.h" - -#include "m_sqlv2.h" - -/* $ModDesc: MsSQL provider */ -/* $CompileFlags: exec("grep VERSION_NO /usr/include/tdsver.h 2>/dev/null | perl -e 'print "-D_TDSVER=".((<> =~ /freetds v(\d+\.\d+)/i) ? $1*100 : 0);'") */ -/* $LinkerFlags: -ltds */ -/* $ModDep: m_sqlv2.h */ - -class SQLConn; -class MsSQLResult; -class ModuleMsSQL; - -typedef std::map<std::string, SQLConn*> ConnMap; -typedef std::deque<MsSQLResult*> ResultQueue; - -unsigned long count(const char * const str, char a) -{ - unsigned long n = 0; - for (const char *p = str; *p; ++p) - { - if (*p == '?') - ++n; - } - return n; -} - -ConnMap connections; -Mutex* ResultsMutex; -Mutex* LoggingMutex; - -class QueryThread : public SocketThread -{ - private: - ModuleMsSQL* const Parent; - public: - QueryThread(ModuleMsSQL* mod) : Parent(mod) { } - ~QueryThread() { } - virtual void Run(); - virtual void OnNotify(); -}; - -class MsSQLResult : public SQLresult -{ - private: - int currentrow; - int rows; - int cols; - - std::vector<std::string> colnames; - std::vector<SQLfieldList> fieldlists; - SQLfieldList emptyfieldlist; - - SQLfieldList* fieldlist; - SQLfieldMap* fieldmap; - - public: - MsSQLResult(Module* self, Module* to, unsigned int rid) - : SQLresult(self, to, rid), currentrow(0), rows(0), cols(0), fieldlist(NULL), fieldmap(NULL) - { - } - - ~MsSQLResult() - { - } - - void AddRow(int colsnum, char **dat, char **colname) - { - colnames.clear(); - cols = colsnum; - for (int i = 0; i < colsnum; i++) - { - fieldlists.resize(fieldlists.size()+1); - colnames.push_back(colname[i]); - SQLfield sf(dat[i] ? dat[i] : "", dat[i] ? false : true); - fieldlists[rows].push_back(sf); - } - rows++; - } - - void UpdateAffectedCount() - { - rows++; - } - - virtual int Rows() - { - return rows; - } - - virtual int Cols() - { - return cols; - } - - virtual std::string ColName(int column) - { - if (column < (int)colnames.size()) - { - return colnames[column]; - } - else - { - throw SQLbadColName(); - } - return ""; - } - - virtual int ColNum(const std::string &column) - { - for (unsigned int i = 0; i < colnames.size(); i++) - { - if (column == colnames[i]) - return i; - } - throw SQLbadColName(); - return 0; - } - - virtual SQLfield GetValue(int row, int column) - { - if ((row >= 0) && (row < rows) && (column >= 0) && (column < Cols())) - { - return fieldlists[row][column]; - } - - throw SQLbadColName(); - - /* XXX: We never actually get here because of the throw */ - return SQLfield("",true); - } - - virtual SQLfieldList& GetRow() - { - if (currentrow < rows) - return fieldlists[currentrow]; - else - return emptyfieldlist; - } - - virtual SQLfieldMap& GetRowMap() - { - /* In an effort to reduce overhead we don't actually allocate the map - * until the first time it's needed...so... - */ - if(fieldmap) - { - fieldmap->clear(); - } - else - { - fieldmap = new SQLfieldMap; - } - - if (currentrow < rows) - { - for (int i = 0; i < Cols(); i++) - { - fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i))); - } - currentrow++; - } - - return *fieldmap; - } - - virtual SQLfieldList* GetRowPtr() - { - fieldlist = new SQLfieldList(); - - if (currentrow < rows) - { - for (int i = 0; i < Rows(); i++) - { - fieldlist->push_back(fieldlists[currentrow][i]); - } - currentrow++; - } - return fieldlist; - } - - virtual SQLfieldMap* GetRowMapPtr() - { - fieldmap = new SQLfieldMap(); - - if (currentrow < rows) - { - for (int i = 0; i < Cols(); i++) - { - fieldmap->insert(std::make_pair(colnames[i],GetValue(currentrow, i))); - } - currentrow++; - } - - return fieldmap; - } - - virtual void Free(SQLfieldMap* fm) - { - delete fm; - } - - virtual void Free(SQLfieldList* fl) - { - delete fl; - } -}; - -class SQLConn : public classbase -{ - private: - ResultQueue results; - Module* mod; - SQLhost host; - TDSLOGIN* login; - TDSSOCKET* sock; - TDSCONTEXT* context; - - public: - QueryQueue queue; - - SQLConn(Module* m, const SQLhost& hi) - : mod(m), host(hi), login(NULL), sock(NULL), context(NULL) - { - if (OpenDB()) - { - std::string query("USE " + host.name); - if (tds_submit_query(sock, query.c_str()) == TDS_SUCCEED) - { - if (tds_process_simple_query(sock) != TDS_SUCCEED) - { - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: Could not select database " + host.name + " for DB with id: " + host.id); - LoggingMutex->Unlock(); - CloseDB(); - } - } - else - { - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: Could not select database " + host.name + " for DB with id: " + host.id); - LoggingMutex->Unlock(); - CloseDB(); - } - } - else - { - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: Could not connect to DB with id: " + host.id); - LoggingMutex->Unlock(); - CloseDB(); - } - } - - ~SQLConn() - { - CloseDB(); - } - - SQLerror Query(SQLrequest* req) - { - if (!sock) - return SQLerror(SQL_BAD_CONN, "Socket was NULL, check if SQL server is running."); - - /* Pointer to the buffer we screw around with substitution in */ - char* query; - - /* Pointer to the current end of query, where we append new stuff */ - char* queryend; - - /* Total length of the unescaped parameters */ - unsigned long maxparamlen, paramcount; - - /* The length of the longest parameter */ - maxparamlen = 0; - - for(ParamL::iterator i = req->query.p.begin(); i != req->query.p.end(); i++) - { - if (i->size() > maxparamlen) - maxparamlen = i->size(); - } - - /* How many params are there in the query? */ - paramcount = count(req->query.q.c_str(), '?'); - - /* This stores copy of params to be inserted with using numbered params 1;3B*/ - ParamL paramscopy(req->query.p); - - /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be. - * sizeofquery + (maxtotalparamlength*2) + 1 - * - * The +1 is for null-terminating the string - */ - - query = new char[req->query.q.length() + (maxparamlen*paramcount*2) + 1]; - queryend = query; - - for(unsigned long i = 0; i < req->query.q.length(); i++) - { - if(req->query.q[i] == '?') - { - /* We found a place to substitute..what fun. - * use mssql calls to escape and write the - * escaped string onto the end of our query buffer, - * then we "just" need to make sure queryend is - * pointing at the right place. - */ - - /* Is it numbered parameter? - */ - - bool numbered; - numbered = false; - - /* Numbered parameter number :| - */ - unsigned int paramnum; - paramnum = 0; - - /* Let's check if it's a numbered param. And also calculate it's number. - */ - - while ((i < req->query.q.length() - 1) && (req->query.q[i+1] >= '0') && (req->query.q[i+1] <= '9')) - { - numbered = true; - ++i; - paramnum = paramnum * 10 + req->query.q[i] - '0'; - } - - if (paramnum > paramscopy.size() - 1) - { - /* index is out of range! - */ - numbered = false; - } - - if (numbered) - { - /* Custom escaping for this one. converting ' to '' should make SQL Server happy. Ugly but fast :] - */ - char* escaped = new char[(paramscopy[paramnum].length() * 2) + 1]; - char* escend = escaped; - for (std::string::iterator p = paramscopy[paramnum].begin(); p < paramscopy[paramnum].end(); p++) - { - if (*p == '\'') - { - *escend = *p; - escend++; - *escend = *p; - } - *escend = *p; - escend++; - } - *escend = 0; - - for (char* n = escaped; *n; n++) - { - *queryend = *n; - queryend++; - } - delete[] escaped; - } - else if (req->query.p.size()) - { - /* Custom escaping for this one. converting ' to '' should make SQL Server happy. Ugly but fast :] - */ - char* escaped = new char[(req->query.p.front().length() * 2) + 1]; - char* escend = escaped; - for (std::string::iterator p = req->query.p.front().begin(); p < req->query.p.front().end(); p++) - { - if (*p == '\'') - { - *escend = *p; - escend++; - *escend = *p; - } - *escend = *p; - escend++; - } - *escend = 0; - - for (char* n = escaped; *n; n++) - { - *queryend = *n; - queryend++; - } - delete[] escaped; - req->query.p.pop_front(); - } - else - break; - } - else - { - *queryend = req->query.q[i]; - queryend++; - } - } - *queryend = 0; - req->query.q = query; - - MsSQLResult* res = new MsSQLResult((Module*)mod, req->source, req->id); - res->dbid = host.id; - res->query = req->query.q; - - char* msquery = strdup(req->query.q.data()); - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql",DEBUG,"doing Query: %s",msquery); - LoggingMutex->Unlock(); - if (tds_submit_query(sock, msquery) != TDS_SUCCEED) - { - std::string error("failed to execute: "+std::string(req->query.q.data())); - delete[] query; - delete res; - free(msquery); - return SQLerror(SQL_QSEND_FAIL, error); - } - delete[] query; - free(msquery); - - int tds_res; - while (tds_process_tokens(sock, &tds_res, NULL, TDS_TOKEN_RESULTS) == TDS_SUCCEED) - { - //ServerInstance->Logs->Log("m_mssql",DEBUG,"<******> result type: %d", tds_res); - //ServerInstance->Logs->Log("m_mssql",DEBUG,"AFFECTED ROWS: %d", sock->rows_affected); - switch (tds_res) - { - case TDS_ROWFMT_RESULT: - break; - - case TDS_DONE_RESULT: - if (sock->rows_affected > -1) - { - for (int c = 0; c < sock->rows_affected; c++) res->UpdateAffectedCount(); - continue; - } - break; - - case TDS_ROW_RESULT: - while (tds_process_tokens(sock, &tds_res, NULL, TDS_STOPAT_ROWFMT|TDS_RETURN_DONE|TDS_RETURN_ROW) == TDS_SUCCEED) - { - if (tds_res != TDS_ROW_RESULT) - break; - - if (!sock->current_results) - continue; - - if (sock->res_info->row_count > 0) - { - int cols = sock->res_info->num_cols; - char** name = new char*[MAXBUF]; - char** data = new char*[MAXBUF]; - for (int j=0; j<cols; j++) - { - TDSCOLUMN* col = sock->current_results->columns[j]; - name[j] = col->column_name; - - int ctype; - int srclen; - unsigned char* src; - CONV_RESULT dres; - ctype = tds_get_conversion_type(col->column_type, col->column_size); -#if _TDSVER >= 82 - src = col->column_data; -#else - src = &(sock->current_results->current_row[col->column_offset]); -#endif - srclen = col->column_cur_size; - tds_convert(sock->tds_ctx, ctype, (TDS_CHAR *) src, srclen, SYBCHAR, &dres); - data[j] = (char*)dres.ib; - } - ResultReady(res, cols, data, name); - } - } - break; - - default: - break; - } - } - ResultsMutex->Lock(); - results.push_back(res); - ResultsMutex->Unlock(); - return SQLerror(); - } - - static int HandleMessage(const TDSCONTEXT * pContext, TDSSOCKET * pTdsSocket, TDSMESSAGE * pMessage) - { - SQLConn* sc = (SQLConn*)pContext->parent; - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql", DEBUG, "Message for DB with id: %s -> %s", sc->host.id.c_str(), pMessage->message); - LoggingMutex->Unlock(); - return 0; - } - - static int HandleError(const TDSCONTEXT * pContext, TDSSOCKET * pTdsSocket, TDSMESSAGE * pMessage) - { - SQLConn* sc = (SQLConn*)pContext->parent; - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql", DEFAULT, "Error for DB with id: %s -> %s", sc->host.id.c_str(), pMessage->message); - LoggingMutex->Unlock(); - return 0; - } - - void ResultReady(MsSQLResult *res, int cols, char **data, char **colnames) - { - res->AddRow(cols, data, colnames); - } - - void AffectedReady(MsSQLResult *res) - { - res->UpdateAffectedCount(); - } - - bool OpenDB() - { - CloseDB(); - - TDSCONNECTION* conn = NULL; - - login = tds_alloc_login(); - tds_set_app(login, "TSQL"); - tds_set_library(login,"TDS-Library"); - tds_set_host(login, ""); - tds_set_server(login, host.host.c_str()); - tds_set_server_addr(login, host.host.c_str()); - tds_set_user(login, host.user.c_str()); - tds_set_passwd(login, host.pass.c_str()); - tds_set_port(login, host.port); - tds_set_packet(login, 512); - - context = tds_alloc_context(this); - context->msg_handler = HandleMessage; - context->err_handler = HandleError; - - sock = tds_alloc_socket(context, 512); - tds_set_parent(sock, NULL); - - conn = tds_read_config_info(NULL, login, context->locale); - - if (tds_connect(sock, conn) == TDS_SUCCEED) - { - tds_free_connection(conn); - return 1; - } - tds_free_connection(conn); - return 0; - } - - void CloseDB() - { - if (sock) - { - tds_free_socket(sock); - sock = NULL; - } - if (context) - { - tds_free_context(context); - context = NULL; - } - if (login) - { - tds_free_login(login); - login = NULL; - } - } - - SQLhost GetConfHost() - { - return host; - } - - void SendResults() - { - while (results.size()) - { - MsSQLResult* res = results[0]; - ResultsMutex->Lock(); - if (res->dest) - { - res->Send(); - } - else - { - /* If the client module is unloaded partway through a query then the provider will set - * the pointer to NULL. We cannot just cancel the query as the result will still come - * through at some point...and it could get messy if we play with invalid pointers... - */ - delete res; - } - results.pop_front(); - ResultsMutex->Unlock(); - } - } - - void ClearResults() - { - while (results.size()) - { - MsSQLResult* res = results[0]; - delete res; - results.pop_front(); - } - } - - void DoLeadingQuery() - { - SQLrequest* req = queue.front(); - req->error = Query(req); - } - -}; - - -class ModuleMsSQL : public Module -{ - private: - unsigned long currid; - QueryThread* queryDispatcher; - ServiceProvider sqlserv; - - public: - ModuleMsSQL() - : currid(0), sqlserv(this, "SQL/mssql", SERVICE_DATA) - { - LoggingMutex = new Mutex(); - ResultsMutex = new Mutex(); - queryDispatcher = new QueryThread(this); - } - - void init() - { - ReadConf(); - - ServerInstance->Threads->Start(queryDispatcher); - - Implementation eventlist[] = { I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - ServerInstance->Modules->AddService(sqlserv); - } - - virtual ~ModuleMsSQL() - { - queryDispatcher->join(); - delete queryDispatcher; - ClearQueue(); - ClearAllConnections(); - - delete LoggingMutex; - delete ResultsMutex; - } - - void SendQueue() - { - for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++) - { - iter->second->SendResults(); - } - } - - void ClearQueue() - { - for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++) - { - iter->second->ClearResults(); - } - } - - bool HasHost(const SQLhost &host) - { - for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++) - { - if (host == iter->second->GetConfHost()) - return true; - } - return false; - } - - bool HostInConf(const SQLhost &h) - { - ConfigTagList tags = ServerInstance->Config->ConfTags("database"); - for (ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - SQLhost host; - host.id = tag->getString("id"); - host.host = tag->getString("hostname"); - host.port = tag->getInt("port", 1433); - host.name = tag->getString("name"); - host.user = tag->getString("username"); - host.pass = tag->getString("password"); - if (h == host) - return true; - } - return false; - } - - void ReadConf() - { - ClearOldConnections(); - - ConfigTagList tags = ServerInstance->Config->ConfTags("database"); - for (ConfigIter i = tags.first; i != tags.second; ++i) - { - ConfigTag* tag = i->second; - SQLhost host; - - host.id = tag->getString("id"); - host.host = tag->getString("hostname"); - host.port = tag->getInt("port", 1433); - host.name = tag->getString("name"); - host.user = tag->getString("username"); - host.pass = tag->getString("password"); - - if (HasHost(host)) - continue; - - this->AddConn(host); - } - } - - void AddConn(const SQLhost& hi) - { - if (HasHost(hi)) - { - LoggingMutex->Lock(); - ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: A MsSQL connection with id: %s already exists. Aborting database open attempt.", hi.id.c_str()); - LoggingMutex->Unlock(); - return; - } - - SQLConn* newconn; - - newconn = new SQLConn(this, hi); - - connections.insert(std::make_pair(hi.id, newconn)); - } - - void ClearOldConnections() - { - ConnMap::iterator iter,safei; - for (iter = connections.begin(); iter != connections.end(); iter++) - { - if (!HostInConf(iter->second->GetConfHost())) - { - delete iter->second; - safei = iter; - --iter; - connections.erase(safei); - } - } - } - - void ClearAllConnections() - { - for(ConnMap::iterator i = connections.begin(); i != connections.end(); ++i) - delete i->second; - connections.clear(); - } - - virtual void OnRehash(User* user) - { - queryDispatcher->LockQueue(); - ReadConf(); - queryDispatcher->UnlockQueueWakeup(); - } - - void OnRequest(Request& request) - { - if(strcmp(SQLREQID, request.id) == 0) - { - SQLrequest* req = (SQLrequest*)&request; - - queryDispatcher->LockQueue(); - - ConnMap::iterator iter; - - if((iter = connections.find(req->dbid)) != connections.end()) - { - req->id = NewID(); - iter->second->queue.push(new SQLrequest(*req)); - } - else - { - req->error.Id(SQL_BAD_DBID); - } - queryDispatcher->UnlockQueueWakeup(); - } - } - - unsigned long NewID() - { - if (currid+1 == 0) - currid++; - - return ++currid; - } - - virtual Version GetVersion() - { - return Version("MsSQL provider", VF_VENDOR); - } - -}; - -void QueryThread::OnNotify() -{ - Parent->SendQueue(); -} - -void QueryThread::Run() -{ - this->LockQueue(); - while (this->GetExitFlag() == false) - { - SQLConn* conn = NULL; - for (ConnMap::iterator i = connections.begin(); i != connections.end(); i++) - { - if (i->second->queue.totalsize()) - { - conn = i->second; - break; - } - } - if (conn) - { - this->UnlockQueue(); - conn->DoLeadingQuery(); - this->NotifyParent(); - this->LockQueue(); - conn->queue.pop(); - } - else - { - this->WaitForQueue(); - } - } - this->UnlockQueue(); -} - -MODULE_INIT(ModuleMsSQL) diff --git a/src/modules/extra/m_mysql.cpp b/src/modules/extra/m_mysql.cpp index 159a0b8b2..41c3a2a65 100644 --- a/src/modules/extra/m_mysql.cpp +++ b/src/modules/extra/m_mysql.cpp @@ -19,13 +19,26 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: execute("mysql_config --include" "MYSQL_CXXFLAGS") +/// $LinkerFlags: execute("mysql_config --libs_r" "MYSQL_LDFLAGS" "-lmysqlclient") -/* Stop mysql wanting to use long long */ -#define NO_CLIENT_LONG_LONG +/// $PackageInfo: require_system("centos" "6.0" "6.99") mysql-devel +/// $PackageInfo: require_system("centos" "7.0") mariadb-devel +/// $PackageInfo: require_system("darwin") mysql-connector-c +/// $PackageInfo: require_system("debian") libmysqlclient-dev +/// $PackageInfo: require_system("ubuntu") libmysqlclient-dev + + +// Fix warnings about the use of `long long` on C++03. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-long-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wlong-long" +#endif #include "inspircd.h" #include <mysql.h> -#include "sql.h" +#include "modules/sql.h" #ifdef _WIN32 # pragma comment(lib, "libmysql.lib") @@ -33,10 +46,6 @@ /* VERSION 3 API: With nonblocking (threaded) requests */ -/* $ModDesc: SQL Service Provider module for all other m_sql* modules */ -/* $CompileFlags: exec("mysql_config --include") */ -/* $LinkerFlags: exec("mysql_config --libs_r") rpath("mysql_config --libs_r") */ - /* THE NONBLOCKING MYSQL API! * * MySQL provides no nonblocking (asyncronous) API of its own, and its developers recommend @@ -75,20 +84,20 @@ class DispatcherThread; struct QQueueItem { - SQLQuery* q; + SQL::Query* q; std::string query; SQLConnection* c; - QQueueItem(SQLQuery* Q, const std::string& S, SQLConnection* C) : q(Q), query(S), c(C) {} + QQueueItem(SQL::Query* Q, const std::string& S, SQLConnection* C) : q(Q), query(S), c(C) {} }; struct RQueueItem { - SQLQuery* q; + SQL::Query* q; MySQLresult* r; - RQueueItem(SQLQuery* Q, MySQLresult* R) : q(Q), r(R) {} + RQueueItem(SQL::Query* Q, MySQLresult* R) : q(Q), r(R) {} }; -typedef std::map<std::string, SQLConnection*> ConnMap; +typedef insp::flat_map<std::string, SQLConnection*> ConnMap; typedef std::deque<QQueueItem> QueryQueue; typedef std::deque<RQueueItem> ResultQueue; @@ -103,11 +112,11 @@ class ModuleSQL : public Module ConnMap connections; // main thread only ModuleSQL(); - void init(); + void init() CXX11_OVERRIDE; ~ModuleSQL(); - void OnRehash(User* user); - void OnUnloadModule(Module* mod); - Version GetVersion(); + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE; + void OnUnloadModule(Module* mod) CXX11_OVERRIDE; + Version GetVersion() CXX11_OVERRIDE; }; class DispatcherThread : public SocketThread @@ -117,8 +126,8 @@ class DispatcherThread : public SocketThread public: DispatcherThread(ModuleSQL* CreatorModule) : Parent(CreatorModule) { } ~DispatcherThread() { } - virtual void Run(); - virtual void OnNotify(); + void Run() CXX11_OVERRIDE; + void OnNotify() CXX11_OVERRIDE; }; #if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID<32224 @@ -127,16 +136,16 @@ class DispatcherThread : public SocketThread /** Represents a mysql result set */ -class MySQLresult : public SQLResult +class MySQLresult : public SQL::Result { public: - SQLerror err; + SQL::Error err; int currentrow; int rows; std::vector<std::string> colnames; - std::vector<SQLEntries> fieldlists; + std::vector<SQL::Row> fieldlists; - MySQLresult(MYSQL_RES* res, int affected_rows) : err(SQL_NO_ERROR), currentrow(0), rows(0) + MySQLresult(MYSQL_RES* res, int affected_rows) : err(SQL::SUCCESS), currentrow(0), rows(0) { if (affected_rows >= 1) { @@ -165,9 +174,9 @@ class MySQLresult : public SQLResult { std::string a = (fields[field_count].name ? fields[field_count].name : ""); if (row[field_count]) - fieldlists[n].push_back(SQLEntry(row[field_count])); + fieldlists[n].push_back(SQL::Field(row[field_count])); else - fieldlists[n].push_back(SQLEntry()); + fieldlists[n].push_back(SQL::Field()); colnames.push_back(a); field_count++; } @@ -179,35 +188,44 @@ class MySQLresult : public SQLResult } } - MySQLresult(SQLerror& e) : err(e) + MySQLresult(SQL::Error& e) : err(e) { } - ~MySQLresult() + int Rows() CXX11_OVERRIDE { + return rows; } - virtual int Rows() + void GetCols(std::vector<std::string>& result) CXX11_OVERRIDE { - return rows; + result.assign(colnames.begin(), colnames.end()); } - virtual void GetCols(std::vector<std::string>& result) + bool HasColumn(const std::string& column, size_t& index) CXX11_OVERRIDE { - result.assign(colnames.begin(), colnames.end()); + for (size_t i = 0; i < colnames.size(); ++i) + { + if (colnames[i] == column) + { + index = i; + return true; + } + } + return false; } - virtual SQLEntry GetValue(int row, int column) + SQL::Field GetValue(int row, int column) { if ((row >= 0) && (row < rows) && (column >= 0) && (column < (int)fieldlists[row].size())) { return fieldlists[row][column]; } - return SQLEntry(); + return SQL::Field(); } - virtual bool GetRow(SQLEntries& result) + bool GetRow(SQL::Row& result) CXX11_OVERRIDE { if (currentrow < rows) { @@ -225,7 +243,7 @@ class MySQLresult : public SQLResult /** Represents a connection to a mysql database */ -class SQLConnection : public SQLProvider +class SQLConnection : public SQL::Provider { public: reference<ConfigTag> config; @@ -233,7 +251,7 @@ class SQLConnection : public SQLProvider Mutex lock; // This constructor creates an SQLConnection object with the given credentials, but does not connect yet. - SQLConnection(Module* p, ConfigTag* tag) : SQLProvider(p, "SQL/" + tag->getString("id")), + SQLConnection(Module* p, ConfigTag* tag) : SQL::Provider(p, "SQL/" + tag->getString("id")), config(tag), connection(NULL) { } @@ -254,10 +272,16 @@ class SQLConnection : public SQLProvider std::string user = config->getString("user"); std::string pass = config->getString("pass"); std::string dbname = config->getString("name"); - int port = config->getInt("port"); + unsigned int port = config->getUInt("port", 3306); bool rv = mysql_real_connect(connection, host.c_str(), user.c_str(), pass.c_str(), dbname.c_str(), port, NULL, 0); if (!rv) return rv; + + // Enable character set settings + std::string charset = config->getString("charset"); + if ((!charset.empty()) && (mysql_set_character_set(connection, charset.c_str()))) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: Could not set character set to \"%s\"", charset.c_str()); + std::string initquery; if (config->readString("initialquery", initquery)) { @@ -286,7 +310,7 @@ class SQLConnection : public SQLProvider { /* XXX: See /usr/include/mysql/mysqld_error.h for a list of * possible error numbers and error messages */ - SQLerror e(SQL_QREPLY_FAIL, ConvToStr(mysql_errno(connection)) + ": " + mysql_error(connection)); + SQL::Error e(SQL::QREPLY_FAIL, InspIRCd::Format("%u: %s", mysql_errno(connection), mysql_error(connection))); return new MySQLresult(e); } } @@ -308,14 +332,14 @@ class SQLConnection : public SQLProvider mysql_close(connection); } - void submit(SQLQuery* q, const std::string& qs) + void Submit(SQL::Query* q, const std::string& qs) CXX11_OVERRIDE { Parent()->Dispatcher->LockQueue(); Parent()->qq.push_back(QQueueItem(q, qs, this)); Parent()->Dispatcher->UnlockQueueWakeup(); } - void submit(SQLQuery* call, const std::string& q, const ParamL& p) + void Submit(SQL::Query* call, const std::string& q, const SQL::ParamList& p) CXX11_OVERRIDE { std::string res; unsigned int param = 0; @@ -332,18 +356,17 @@ class SQLConnection : public SQLProvider // and one byte is the terminating null std::vector<char> buffer(parm.length() * 2 + 1); - // The return value of mysql_escape_string() is the length of the encoded string, + // The return value of mysql_real_escape_string() is the length of the encoded string, // not including the terminating null - unsigned long escapedsize = mysql_escape_string(&buffer[0], parm.c_str(), parm.length()); -// mysql_real_escape_string(connection, queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length()); + unsigned long escapedsize = mysql_real_escape_string(connection, &buffer[0], parm.c_str(), parm.length()); res.append(&buffer[0], escapedsize); } } } - submit(call, res); + Submit(call, res); } - void submit(SQLQuery* call, const std::string& q, const ParamM& p) + void Submit(SQL::Query* call, const std::string& q, const SQL::ParamMap& p) CXX11_OVERRIDE { std::string res; for(std::string::size_type i = 0; i < q.length(); i++) @@ -358,7 +381,7 @@ class SQLConnection : public SQLProvider field.push_back(q[i++]); i--; - ParamM::const_iterator it = p.find(field); + SQL::ParamMap::const_iterator it = p.find(field); if (it != p.end()) { std::string parm = it->second; @@ -369,7 +392,7 @@ class SQLConnection : public SQLProvider } } } - submit(call, res); + Submit(call, res); } }; @@ -381,12 +404,7 @@ ModuleSQL::ModuleSQL() void ModuleSQL::init() { Dispatcher = new DispatcherThread(this); - ServerInstance->Threads->Start(Dispatcher); - - Implementation eventlist[] = { I_OnRehash, I_OnUnloadModule }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - OnRehash(NULL); + ServerInstance->Threads.Start(Dispatcher); } ModuleSQL::~ModuleSQL() @@ -403,13 +421,13 @@ ModuleSQL::~ModuleSQL() } } -void ModuleSQL::OnRehash(User* user) +void ModuleSQL::ReadConfig(ConfigStatus& status) { ConnMap conns; ConfigTagList tags = ServerInstance->Config->ConfTags("database"); for(ConfigIter i = tags.first; i != tags.second; i++) { - if (i->second->getString("module", "mysql") != "mysql") + if (!stdalgo::string::equalsci(i->second->getString("provider"), "mysql")) continue; std::string id = i->second->getString("id"); ConnMap::iterator curr = connections.find(id); @@ -428,7 +446,7 @@ void ModuleSQL::OnRehash(User* user) // now clean up the deleted databases Dispatcher->LockQueue(); - SQLerror err(SQL_BAD_DBID); + SQL::Error err(SQL::BAD_DBID); for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++) { ServerInstance->Modules->DelService(*i->second); @@ -455,7 +473,7 @@ void ModuleSQL::OnRehash(User* user) void ModuleSQL::OnUnloadModule(Module* mod) { - SQLerror err(SQL_BAD_DBID); + SQL::Error err(SQL::BAD_DBID); Dispatcher->LockQueue(); unsigned int i = qq.size(); while (i > 0) @@ -535,7 +553,7 @@ void DispatcherThread::OnNotify() for(ResultQueue::iterator i = Parent->rq.begin(); i != Parent->rq.end(); i++) { MySQLresult* res = i->r; - if (res->err.id == SQL_NO_ERROR) + if (res->err.code == SQL::SUCCESS) i->q->OnResult(*res); else i->q->OnError(res->err); diff --git a/src/modules/extra/m_pgsql.cpp b/src/modules/extra/m_pgsql.cpp index ac247548a..ec89208dd 100644 --- a/src/modules/extra/m_pgsql.cpp +++ b/src/modules/extra/m_pgsql.cpp @@ -21,16 +21,19 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: -Iexecute("pg_config --includedir" "POSTGRESQL_INCLUDE_DIR") +/// $LinkerFlags: -Lexecute("pg_config --libdir" "POSTGRESQL_LIBRARY_DIR") -lpq + +/// $PackageInfo: require_system("centos") postgresql-devel +/// $PackageInfo: require_system("darwin") postgresql +/// $PackageInfo: require_system("debian") libpq-dev +/// $PackageInfo: require_system("ubuntu") libpq-dev + #include "inspircd.h" #include <cstdlib> -#include <sstream> #include <libpq-fe.h> -#include "sql.h" - -/* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */ -/* $CompileFlags: -Iexec("pg_config --includedir") eval("my $s = `pg_config --version`;$s =~ /^.*?(\d+)\.(\d+)\.(\d+).*?$/;my $v = hex(sprintf("0x%02x%02x%02x", $1, $2, $3));print "-DPGSQL_HAS_ESCAPECONN" if(($v >= 0x080104) || ($v >= 0x07030F && $v < 0x070400) || ($v >= 0x07040D && $v < 0x080000) || ($v >= 0x080008 && $v < 0x080100));") */ -/* $LinkerFlags: -Lexec("pg_config --libdir") -lpq */ +#include "modules/sql.h" /* SQLConn rewritten by peavey to * use EventHandler instead of @@ -43,7 +46,7 @@ class SQLConn; class ModulePgSQL; -typedef std::map<std::string, SQLConn*> ConnMap; +typedef insp::flat_map<std::string, SQLConn*> ConnMap; /* CREAD, Connecting and wants read event * CWRITE, Connecting and wants write event @@ -59,17 +62,17 @@ class ReconnectTimer : public Timer private: ModulePgSQL* mod; public: - ReconnectTimer(ModulePgSQL* m) : Timer(5, ServerInstance->Time(), false), mod(m) + ReconnectTimer(ModulePgSQL* m) : Timer(5, false), mod(m) { } - virtual void Tick(time_t TIME); + bool Tick(time_t TIME) CXX11_OVERRIDE; }; struct QueueItem { - SQLQuery* c; + SQL::Query* c; std::string q; - QueueItem(SQLQuery* C, const std::string& Q) : c(C), q(Q) {} + QueueItem(SQL::Query* C, const std::string& Q) : c(C), q(Q) {} }; /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult. @@ -79,11 +82,21 @@ struct QueueItem * data is passes to the module nearly as directly as if it was using the API directly itself. */ -class PgSQLresult : public SQLResult +class PgSQLresult : public SQL::Result { PGresult* res; int currentrow; int rows; + std::vector<std::string> colnames; + + void getColNames() + { + colnames.resize(PQnfields(res)); + for(unsigned int i=0; i < colnames.size(); i++) + { + colnames[i] = PQfname(res, i); + } + } public: PgSQLresult(PGresult* result) : res(result), currentrow(0) { @@ -97,30 +110,44 @@ class PgSQLresult : public SQLResult PQclear(res); } - virtual int Rows() + int Rows() CXX11_OVERRIDE { return rows; } - virtual void GetCols(std::vector<std::string>& result) + void GetCols(std::vector<std::string>& result) CXX11_OVERRIDE + { + if (colnames.empty()) + getColNames(); + result = colnames; + } + + bool HasColumn(const std::string& column, size_t& index) CXX11_OVERRIDE { - result.resize(PQnfields(res)); - for(unsigned int i=0; i < result.size(); i++) + if (colnames.empty()) + getColNames(); + + for (size_t i = 0; i < colnames.size(); ++i) { - result[i] = PQfname(res, i); + if (colnames[i] == column) + { + index = i; + return true; + } } + return false; } - virtual SQLEntry GetValue(int row, int column) + SQL::Field GetValue(int row, int column) { char* v = PQgetvalue(res, row, column); if (!v || PQgetisnull(res, row, column)) - return SQLEntry(); + return SQL::Field(); - return SQLEntry(std::string(v, PQgetlength(res, row, column))); + return SQL::Field(std::string(v, PQgetlength(res, row, column))); } - virtual bool GetRow(SQLEntries& result) + bool GetRow(SQL::Row& result) CXX11_OVERRIDE { if (currentrow >= PQntuples(res)) return false; @@ -138,7 +165,7 @@ class PgSQLresult : public SQLResult /** SQLConn represents one SQL session. */ -class SQLConn : public SQLProvider, public EventHandler +class SQLConn : public SQL::Provider, public EventHandler { public: reference<ConfigTag> conf; /* The <database> entry */ @@ -148,25 +175,25 @@ class SQLConn : public SQLProvider, public EventHandler QueueItem qinprog; /* If there is currently a query in progress */ SQLConn(Module* Creator, ConfigTag* tag) - : SQLProvider(Creator, "SQL/" + tag->getString("id")), conf(tag), sql(NULL), status(CWRITE), qinprog(NULL, "") + : SQL::Provider(Creator, "SQL/" + tag->getString("id")), conf(tag), sql(NULL), status(CWRITE), qinprog(NULL, "") { if (!DoConnect()) { - ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: Could not connect to database " + tag->getString("id")); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: Could not connect to database " + tag->getString("id")); DelayReconnect(); } } - CullResult cull() + CullResult cull() CXX11_OVERRIDE { - this->SQLProvider::cull(); + this->SQL::Provider::cull(); ServerInstance->Modules->DelService(*this); return this->EventHandler::cull(); } ~SQLConn() { - SQLerror err(SQL_BAD_DBID); + SQL::Error err(SQL::BAD_DBID); if (qinprog.c) { qinprog.c->OnError(err); @@ -174,24 +201,25 @@ class SQLConn : public SQLProvider, public EventHandler } for(std::deque<QueueItem>::iterator i = queue.begin(); i != queue.end(); i++) { - SQLQuery* q = i->c; + SQL::Query* q = i->c; q->OnError(err); delete q; } } - virtual void HandleEvent(EventType et, int errornum) + void OnEventHandlerRead() CXX11_OVERRIDE { - switch (et) - { - case EVENT_READ: - case EVENT_WRITE: - DoEvent(); - break; + DoEvent(); + } - case EVENT_ERROR: - DelayReconnect(); - } + void OnEventHandlerWrite() CXX11_OVERRIDE + { + DoEvent(); + } + + void OnEventHandlerError(int errornum) CXX11_OVERRIDE + { + DelayReconnect(); } std::string GetDSN() @@ -242,9 +270,9 @@ class SQLConn : public SQLProvider, public EventHandler if(this->fd <= -1) return false; - if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ)) + if (!SocketEngine::AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ)) { - ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Couldn't add pgsql socket to socket engine"); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Couldn't add pgsql socket to socket engine"); return false; } @@ -257,17 +285,17 @@ class SQLConn : public SQLProvider, public EventHandler switch(PQconnectPoll(sql)) { case PGRES_POLLING_WRITING: - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ); status = CWRITE; return true; case PGRES_POLLING_READING: - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); status = CREAD; return true; case PGRES_POLLING_FAILED: return false; case PGRES_POLLING_OK: - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); status = WWRITE; DoConnectedPoll(); default: @@ -316,7 +344,7 @@ restart: case PGRES_BAD_RESPONSE: case PGRES_FATAL_ERROR: { - SQLerror err(SQL_QREPLY_FAIL, PQresultErrorMessage(result)); + SQL::Error err(SQL::QREPLY_FAIL, PQresultErrorMessage(result)); qinprog.c->OnError(err); break; } @@ -350,17 +378,17 @@ restart: switch(PQresetPoll(sql)) { case PGRES_POLLING_WRITING: - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ); status = CWRITE; return DoPoll(); case PGRES_POLLING_READING: - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); status = CREAD; return true; case PGRES_POLLING_FAILED: return false; case PGRES_POLLING_OK: - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); status = WWRITE; DoConnectedPoll(); default: @@ -386,7 +414,7 @@ restart: } } - void submit(SQLQuery *req, const std::string& q) + void Submit(SQL::Query *req, const std::string& q) CXX11_OVERRIDE { if (qinprog.q.empty()) { @@ -399,7 +427,7 @@ restart: } } - void submit(SQLQuery *req, const std::string& q, const ParamL& p) + void Submit(SQL::Query *req, const std::string& q, const SQL::ParamList& p) CXX11_OVERRIDE { std::string res; unsigned int param = 0; @@ -413,22 +441,18 @@ restart: { std::string parm = p[param++]; std::vector<char> buffer(parm.length() * 2 + 1); -#ifdef PGSQL_HAS_ESCAPECONN int error; size_t escapedsize = PQescapeStringConn(sql, &buffer[0], parm.data(), parm.length(), &error); if (error) - ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed"); -#else - size_t escapedsize = PQescapeString(&buffer[0], parm.data(), parm.length()); -#endif + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed"); res.append(&buffer[0], escapedsize); } } } - submit(req, res); + Submit(req, res); } - void submit(SQLQuery *req, const std::string& q, const ParamM& p) + void Submit(SQL::Query *req, const std::string& q, const SQL::ParamMap& p) CXX11_OVERRIDE { std::string res; for(std::string::size_type i = 0; i < q.length(); i++) @@ -443,24 +467,20 @@ restart: field.push_back(q[i++]); i--; - ParamM::const_iterator it = p.find(field); + SQL::ParamMap::const_iterator it = p.find(field); if (it != p.end()) { std::string parm = it->second; std::vector<char> buffer(parm.length() * 2 + 1); -#ifdef PGSQL_HAS_ESCAPECONN int error; size_t escapedsize = PQescapeStringConn(sql, &buffer[0], parm.data(), parm.length(), &error); if (error) - ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed"); -#else - size_t escapedsize = PQescapeString(&buffer[0], parm.data(), parm.length()); -#endif + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed"); res.append(&buffer[0], escapedsize); } } } - submit(req, res); + Submit(req, res); } void DoQuery(const QueueItem& req) @@ -468,7 +488,7 @@ restart: if (status != WREAD && status != WWRITE) { // whoops, not connected... - SQLerror err(SQL_BAD_CONN); + SQL::Error err(SQL::BAD_CONN); req.c->OnError(err); delete req.c; return; @@ -480,7 +500,7 @@ restart: } else { - SQLerror err(SQL_QSEND_FAIL, PQerrorMessage(sql)); + SQL::Error err(SQL::QSEND_FAIL, PQerrorMessage(sql)); req.c->OnError(err); delete req.c; } @@ -488,7 +508,7 @@ restart: void Close() { - ServerInstance->SE->DelFd(this); + SocketEngine::DelFd(this); if(sql) { @@ -505,25 +525,17 @@ class ModulePgSQL : public Module ReconnectTimer* retimer; ModulePgSQL() + : retimer(NULL) { } - void init() - { - ReadConf(); - - Implementation eventlist[] = { I_OnUnloadModule, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModulePgSQL() + ~ModulePgSQL() { - if (retimer) - ServerInstance->Timers->DelTimer(retimer); + delete retimer; ClearAllConnections(); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ReadConf(); } @@ -534,7 +546,7 @@ class ModulePgSQL : public Module ConfigTagList tags = ServerInstance->Config->ConfTags("database"); for(ConfigIter i = tags.first; i != tags.second; i++) { - if (i->second->getString("module", "pgsql") != "pgsql") + if (!stdalgo::string::equalsci(i->second->getString("provider"), "pgsql")) continue; std::string id = i->second->getString("id"); ConnMap::iterator curr = connections.find(id); @@ -564,9 +576,9 @@ class ModulePgSQL : public Module connections.clear(); } - void OnUnloadModule(Module* mod) + void OnUnloadModule(Module* mod) CXX11_OVERRIDE { - SQLerror err(SQL_BAD_DBID); + SQL::Error err(SQL::BAD_DBID); for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++) { SQLConn* conn = i->second; @@ -579,7 +591,7 @@ class ModulePgSQL : public Module std::deque<QueueItem>::iterator j = conn->queue.begin(); while (j != conn->queue.end()) { - SQLQuery* q = j->c; + SQL::Query* q = j->c; if (q->creator == mod) { q->OnError(err); @@ -592,16 +604,18 @@ class ModulePgSQL : public Module } } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API", VF_VENDOR); } }; -void ReconnectTimer::Tick(time_t time) +bool ReconnectTimer::Tick(time_t time) { mod->retimer = NULL; mod->ReadConf(); + delete this; + return false; } void SQLConn::DelayReconnect() @@ -615,7 +629,7 @@ void SQLConn::DelayReconnect() if (!mod->retimer) { mod->retimer = new ReconnectTimer(mod); - ServerInstance->Timers->AddTimer(mod->retimer); + ServerInstance->Timers.AddTimer(mod->retimer); } } } diff --git a/src/modules/extra/m_regex_pcre.cpp b/src/modules/extra/m_regex_pcre.cpp index cba234c8c..e8ef96c22 100644 --- a/src/modules/extra/m_regex_pcre.cpp +++ b/src/modules/extra/m_regex_pcre.cpp @@ -17,35 +17,28 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: execute("pcre-config --cflags" "PCRE_CXXFLAGS") +/// $LinkerFlags: execute("pcre-config --libs" "PCRE_LDFLAGS" "-lpcre") + +/// $PackageInfo: require_system("centos") pcre-devel pkgconfig +/// $PackageInfo: require_system("darwin") pcre pkg-config +/// $PackageInfo: require_system("debian") libpcre3-dev pkg-config +/// $PackageInfo: require_system("ubuntu") libpcre3-dev pkg-config + #include "inspircd.h" #include <pcre.h> -#include "m_regex.h" - -/* $ModDesc: Regex Provider Module for PCRE */ -/* $ModDep: m_regex.h */ -/* $CompileFlags: exec("pcre-config --cflags") */ -/* $LinkerFlags: exec("pcre-config --libs") rpath("pcre-config --libs") -lpcre */ +#include "modules/regex.h" #ifdef _WIN32 # pragma comment(lib, "libpcre.lib") #endif -class PCREException : public ModuleException -{ -public: - PCREException(const std::string& rx, const std::string& error, int erroffset) - : ModuleException("Error in regex " + rx + " at offset " + ConvToStr(erroffset) + ": " + error) - { - } -}; - class PCRERegex : public Regex { -private: pcre* regex; -public: + public: PCRERegex(const std::string& rx) : Regex(rx) { const char* error; @@ -53,24 +46,19 @@ public: regex = pcre_compile(rx.c_str(), 0, &error, &erroffset, NULL); if (!regex) { - ServerInstance->Logs->Log("REGEX", DEBUG, "pcre_compile failed: /%s/ [%d] %s", rx.c_str(), erroffset, error); - throw PCREException(rx, error, erroffset); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "pcre_compile failed: /%s/ [%d] %s", rx.c_str(), erroffset, error); + throw RegexException(rx, error, erroffset); } } - virtual ~PCRERegex() + ~PCRERegex() { pcre_free(regex); } - virtual bool Matches(const std::string& text) + bool Matches(const std::string& text) CXX11_OVERRIDE { - if (pcre_exec(regex, NULL, text.c_str(), text.length(), 0, 0, NULL, 0) > -1) - { - // Bang. :D - return true; - } - return false; + return (pcre_exec(regex, NULL, text.c_str(), text.length(), 0, 0, NULL, 0) >= 0); } }; @@ -78,7 +66,7 @@ class PCREFactory : public RegexFactory { public: PCREFactory(Module* m) : RegexFactory(m, "regex/pcre") {} - Regex* Create(const std::string& expr) + Regex* Create(const std::string& expr) CXX11_OVERRIDE { return new PCRERegex(expr); } @@ -86,13 +74,13 @@ class PCREFactory : public RegexFactory class ModuleRegexPCRE : public Module { -public: + public: PCREFactory ref; - ModuleRegexPCRE() : ref(this) { - ServerInstance->Modules->AddService(ref); + ModuleRegexPCRE() : ref(this) + { } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Regex Provider Module for PCRE", VF_VENDOR); } diff --git a/src/modules/extra/m_regex_posix.cpp b/src/modules/extra/m_regex_posix.cpp index b3afd60c8..b5fddfab8 100644 --- a/src/modules/extra/m_regex_posix.cpp +++ b/src/modules/extra/m_regex_posix.cpp @@ -19,28 +19,15 @@ #include "inspircd.h" -#include "m_regex.h" +#include "modules/regex.h" #include <sys/types.h> #include <regex.h> -/* $ModDesc: Regex Provider Module for POSIX Regular Expressions */ -/* $ModDep: m_regex.h */ - -class POSIXRegexException : public ModuleException -{ -public: - POSIXRegexException(const std::string& rx, const std::string& error) - : ModuleException("Error in regex " + rx + ": " + error) - { - } -}; - class POSIXRegex : public Regex { -private: regex_t regbuf; -public: + public: POSIXRegex(const std::string& rx, bool extended) : Regex(rx) { int flags = (extended ? REG_EXTENDED : 0) | REG_NOSUB; @@ -58,23 +45,18 @@ public: error = errbuf; delete[] errbuf; regfree(®buf); - throw POSIXRegexException(rx, error); + throw RegexException(rx, error); } } - virtual ~POSIXRegex() + ~POSIXRegex() { regfree(®buf); } - virtual bool Matches(const std::string& text) + bool Matches(const std::string& text) CXX11_OVERRIDE { - if (regexec(®buf, text.c_str(), 0, NULL, 0) == 0) - { - // Bang. :D - return true; - } - return false; + return (regexec(®buf, text.c_str(), 0, NULL, 0) == 0); } }; @@ -83,7 +65,7 @@ class PosixFactory : public RegexFactory public: bool extended; PosixFactory(Module* m) : RegexFactory(m, "regex/posix") {} - Regex* Create(const std::string& expr) + Regex* Create(const std::string& expr) CXX11_OVERRIDE { return new POSIXRegex(expr, extended); } @@ -92,20 +74,18 @@ class PosixFactory : public RegexFactory class ModuleRegexPOSIX : public Module { PosixFactory ref; -public: - ModuleRegexPOSIX() : ref(this) { - ServerInstance->Modules->AddService(ref); - Implementation eventlist[] = { I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); + + public: + ModuleRegexPOSIX() : ref(this) + { } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Regex Provider Module for POSIX Regular Expressions", VF_VENDOR); } - void OnRehash(User* u) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ref.extended = ServerInstance->Config->ConfValue("posix")->getBool("extended"); } diff --git a/src/modules/extra/m_regex_re2.cpp b/src/modules/extra/m_regex_re2.cpp new file mode 100644 index 000000000..4bcf287ca --- /dev/null +++ b/src/modules/extra/m_regex_re2.cpp @@ -0,0 +1,86 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Peter Powell <petpow@saberuk.com> + * Copyright (C) 2012 ChrisTX <chris@rev-crew.info> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/// $CompilerFlags: find_compiler_flags("re2" "") +/// $LinkerFlags: find_linker_flags("re2" "-lre2") + +/// $PackageInfo: require_system("darwin") pkg-config re2 +/// $PackageInfo: require_system("debian" "8.0") libre2-dev pkg-config +/// $PackageInfo: require_system("ubuntu" "15.10") libre2-dev pkg-config + + +#include "inspircd.h" +#include "modules/regex.h" + +// Fix warnings about the use of `long long` on C++03 and +// shadowing on GCC. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-long-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wlong-long" +# pragma GCC diagnostic ignored "-Wshadow" +#endif + +#include <re2/re2.h> + +class RE2Regex : public Regex +{ + RE2 regexcl; + + public: + RE2Regex(const std::string& rx) : Regex(rx), regexcl(rx, RE2::Quiet) + { + if (!regexcl.ok()) + { + throw RegexException(rx, regexcl.error()); + } + } + + bool Matches(const std::string& text) CXX11_OVERRIDE + { + return RE2::FullMatch(text, regexcl); + } +}; + +class RE2Factory : public RegexFactory +{ + public: + RE2Factory(Module* m) : RegexFactory(m, "regex/re2") { } + Regex* Create(const std::string& expr) CXX11_OVERRIDE + { + return new RE2Regex(expr); + } +}; + +class ModuleRegexRE2 : public Module +{ + RE2Factory ref; + + public: + ModuleRegexRE2() : ref(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Regex Provider Module for RE2", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleRegexRE2) diff --git a/src/modules/extra/m_regex_stdlib.cpp b/src/modules/extra/m_regex_stdlib.cpp index 204728b65..42e5c8bf1 100644 --- a/src/modules/extra/m_regex_stdlib.cpp +++ b/src/modules/extra/m_regex_stdlib.cpp @@ -15,32 +15,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#include "inspircd.h" -#include "m_regex.h" -#include <regex> -/* $ModDesc: Regex Provider Module for std::regex Regular Expressions */ -/* $ModConfig: <stdregex type="ecmascript"> - * Specify the Regular Expression engine to use here. Valid settings are - * bre, ere, awk, grep, egrep, ecmascript (default if not specified)*/ -/* $CompileFlags: -std=c++11 */ -/* $ModDep: m_regex.h */ +/// $CompilerFlags: -std=c++11 -class StdRegexException : public ModuleException -{ -public: - StdRegexException(const std::string& rx, const std::string& error) - : ModuleException(std::string("Error in regex ") + rx + ": " + error) - { - } -}; + +#include "inspircd.h" +#include "modules/regex.h" +#include <regex> class StdRegex : public Regex { -private: std::regex regexcl; -public: + + public: StdRegex(const std::string& rx, std::regex::flag_type fltype) : Regex(rx) { try{ @@ -48,11 +35,11 @@ public: } catch(std::regex_error rxerr) { - throw StdRegexException(rx, rxerr.what()); + throw RegexException(rx, rxerr.what()); } } - - virtual bool Matches(const std::string& text) + + bool Matches(const std::string& text) CXX11_OVERRIDE { return std::regex_search(text, regexcl); } @@ -63,7 +50,7 @@ class StdRegexFactory : public RegexFactory public: std::regex::flag_type regextype; StdRegexFactory(Module* m) : RegexFactory(m, "regex/stdregex") {} - Regex* Create(const std::string& expr) + Regex* Create(const std::string& expr) CXX11_OVERRIDE { return new StdRegex(expr, regextype); } @@ -73,36 +60,33 @@ class ModuleRegexStd : public Module { public: StdRegexFactory ref; - ModuleRegexStd() : ref(this) { - ServerInstance->Modules->AddService(ref); - Implementation eventlist[] = { I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); + ModuleRegexStd() : ref(this) + { } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Regex Provider Module for std::regex", VF_VENDOR); } - - void OnRehash(User* u) + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* Conf = ServerInstance->Config->ConfValue("stdregex"); std::string regextype = Conf->getString("type", "ecmascript"); - - if(regextype == "bre") + + if (stdalgo::string::equalsci(regextype, "bre")) ref.regextype = std::regex::basic; - else if(regextype == "ere") + else if (stdalgo::string::equalsci(regextype, "ere")) ref.regextype = std::regex::extended; - else if(regextype == "awk") + else if (stdalgo::string::equalsci(regextype, "awk")) ref.regextype = std::regex::awk; - else if(regextype == "grep") + else if (stdalgo::string::equalsci(regextype, "grep")) ref.regextype = std::regex::grep; - else if(regextype == "egrep") + else if (stdalgo::string::equalsci(regextype, "egrep")) ref.regextype = std::regex::egrep; else { - if(regextype != "ecmascript") + if (!stdalgo::string::equalsci(regextype, "ecmascript")) ServerInstance->SNO->WriteToSnoMask('a', "WARNING: Non-existent regex engine '%s' specified. Falling back to ECMAScript.", regextype.c_str()); ref.regextype = std::regex::ECMAScript; } diff --git a/src/modules/extra/m_regex_tre.cpp b/src/modules/extra/m_regex_tre.cpp index 4b9eab472..aa3f1d41e 100644 --- a/src/modules/extra/m_regex_tre.cpp +++ b/src/modules/extra/m_regex_tre.cpp @@ -17,29 +17,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: find_compiler_flags("tre") +/// $LinkerFlags: find_linker_flags("tre" "-ltre") + +/// $PackageInfo: require_system("darwin") pkg-config tre +/// $PackageInfo: require_system("debian") libtre-dev pkg-config +/// $PackageInfo: require_system("ubuntu") libtre-dev pkg-config #include "inspircd.h" -#include "m_regex.h" +#include "modules/regex.h" #include <sys/types.h> #include <tre/regex.h> -/* $ModDesc: Regex Provider Module for TRE Regular Expressions */ -/* $CompileFlags: pkgconfincludes("tre","tre/regex.h","") */ -/* $LinkerFlags: pkgconflibs("tre","/libtre.so","-ltre") rpath("pkg-config --libs tre") */ -/* $ModDep: m_regex.h */ - -class TRERegexException : public ModuleException -{ -public: - TRERegexException(const std::string& rx, const std::string& error) - : ModuleException("Error in regex " + rx + ": " + error) - { - } -}; - class TRERegex : public Regex { -private: regex_t regbuf; public: @@ -60,30 +51,26 @@ public: error = errbuf; delete[] errbuf; regfree(®buf); - throw TRERegexException(rx, error); + throw RegexException(rx, error); } } - virtual ~TRERegex() + ~TRERegex() { regfree(®buf); } - virtual bool Matches(const std::string& text) + bool Matches(const std::string& text) CXX11_OVERRIDE { - if (regexec(®buf, text.c_str(), 0, NULL, 0) == 0) - { - // Bang. :D - return true; - } - return false; + return (regexec(®buf, text.c_str(), 0, NULL, 0) == 0); } }; -class TREFactory : public RegexFactory { +class TREFactory : public RegexFactory +{ public: TREFactory(Module* m) : RegexFactory(m, "regex/tre") {} - Regex* Create(const std::string& expr) + Regex* Create(const std::string& expr) CXX11_OVERRIDE { return new TRERegex(expr); } @@ -92,18 +79,15 @@ class TREFactory : public RegexFactory { class ModuleRegexTRE : public Module { TREFactory trf; -public: - ModuleRegexTRE() : trf(this) { - ServerInstance->Modules->AddService(trf); - } - Version GetVersion() + public: + ModuleRegexTRE() : trf(this) { - return Version("Regex Provider Module for TRE Regular Expressions", VF_VENDOR); } - ~ModuleRegexTRE() + Version GetVersion() CXX11_OVERRIDE { + return Version("Regex Provider Module for TRE Regular Expressions", VF_VENDOR); } }; diff --git a/src/modules/extra/m_sqlite3.cpp b/src/modules/extra/m_sqlite3.cpp index 47880c02c..b46810062 100644 --- a/src/modules/extra/m_sqlite3.cpp +++ b/src/modules/extra/m_sqlite3.cpp @@ -19,45 +19,51 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: find_compiler_flags("sqlite3") +/// $LinkerFlags: find_linker_flags("sqlite3" "-lsqlite3") + +/// $PackageInfo: require_system("centos") pkgconfig sqlite-devel +/// $PackageInfo: require_system("darwin") pkg-config sqlite3 +/// $PackageInfo: require_system("debian") libsqlite3-dev pkg-config +/// $PackageInfo: require_system("ubuntu") libsqlite3-dev pkg-config #include "inspircd.h" +#include "modules/sql.h" + +// Fix warnings about the use of `long long` on C++03. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-long-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wlong-long" +#endif + #include <sqlite3.h> -#include "sql.h" #ifdef _WIN32 # pragma comment(lib, "sqlite3.lib") #endif -/* $ModDesc: sqlite3 provider */ -/* $CompileFlags: pkgconfversion("sqlite3","3.3") pkgconfincludes("sqlite3","/sqlite3.h","") */ -/* $LinkerFlags: pkgconflibs("sqlite3","/libsqlite3.so","-lsqlite3") */ -/* $NoPedantic */ - class SQLConn; -typedef std::map<std::string, SQLConn*> ConnMap; +typedef insp::flat_map<std::string, SQLConn*> ConnMap; -class SQLite3Result : public SQLResult +class SQLite3Result : public SQL::Result { public: int currentrow; int rows; std::vector<std::string> columns; - std::vector<SQLEntries> fieldlists; + std::vector<SQL::Row> fieldlists; SQLite3Result() : currentrow(0), rows(0) { } - ~SQLite3Result() - { - } - - virtual int Rows() + int Rows() CXX11_OVERRIDE { return rows; } - virtual bool GetRow(SQLEntries& result) + bool GetRow(SQL::Row& result) CXX11_OVERRIDE { if (currentrow < rows) { @@ -72,20 +78,32 @@ class SQLite3Result : public SQLResult } } - virtual void GetCols(std::vector<std::string>& result) + void GetCols(std::vector<std::string>& result) CXX11_OVERRIDE { result.assign(columns.begin(), columns.end()); } + + bool HasColumn(const std::string& column, size_t& index) CXX11_OVERRIDE + { + for (size_t i = 0; i < columns.size(); ++i) + { + if (columns[i] == column) + { + index = i; + return true; + } + } + return false; + } }; -class SQLConn : public SQLProvider +class SQLConn : public SQL::Provider { - private: sqlite3* conn; reference<ConfigTag> config; public: - SQLConn(Module* Parent, ConfigTag* tag) : SQLProvider(Parent, "SQL/" + tag->getString("id")), config(tag) + SQLConn(Module* Parent, ConfigTag* tag) : SQL::Provider(Parent, "SQL/" + tag->getString("id")), config(tag) { std::string host = tag->getString("hostname"); if (sqlite3_open_v2(host.c_str(), &conn, SQLITE_OPEN_READWRITE, 0) != SQLITE_OK) @@ -93,7 +111,7 @@ class SQLConn : public SQLProvider // Even in case of an error conn must be closed sqlite3_close(conn); conn = NULL; - ServerInstance->Logs->Log("m_sqlite3",DEFAULT, "WARNING: Could not open DB with id: " + tag->getString("id")); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: Could not open DB with id: " + tag->getString("id")); } } @@ -106,14 +124,14 @@ class SQLConn : public SQLProvider } } - void Query(SQLQuery* query, const std::string& q) + void Query(SQL::Query* query, const std::string& q) { SQLite3Result res; sqlite3_stmt *stmt; int err = sqlite3_prepare_v2(conn, q.c_str(), q.length(), &stmt, NULL); if (err != SQLITE_OK) { - SQLerror error(SQL_QSEND_FAIL, sqlite3_errmsg(conn)); + SQL::Error error(SQL::QSEND_FAIL, sqlite3_errmsg(conn)); query->OnError(error); return; } @@ -135,7 +153,7 @@ class SQLConn : public SQLProvider { const char* txt = (const char*)sqlite3_column_text(stmt, i); if (txt) - res.fieldlists[res.rows][i] = SQLEntry(txt); + res.fieldlists[res.rows][i] = SQL::Field(txt); } res.rows++; } @@ -146,7 +164,7 @@ class SQLConn : public SQLProvider } else { - SQLerror error(SQL_QREPLY_FAIL, sqlite3_errmsg(conn)); + SQL::Error error(SQL::QREPLY_FAIL, sqlite3_errmsg(conn)); query->OnError(error); break; } @@ -154,13 +172,13 @@ class SQLConn : public SQLProvider sqlite3_finalize(stmt); } - virtual void submit(SQLQuery* query, const std::string& q) + void Submit(SQL::Query* query, const std::string& q) CXX11_OVERRIDE { Query(query, q); delete query; } - virtual void submit(SQLQuery* query, const std::string& q, const ParamL& p) + void Submit(SQL::Query* query, const std::string& q, const SQL::ParamList& p) CXX11_OVERRIDE { std::string res; unsigned int param = 0; @@ -178,10 +196,10 @@ class SQLConn : public SQLProvider } } } - submit(query, res); + Submit(query, res); } - virtual void submit(SQLQuery* query, const std::string& q, const ParamM& p) + void Submit(SQL::Query* query, const std::string& q, const SQL::ParamMap& p) CXX11_OVERRIDE { std::string res; for(std::string::size_type i = 0; i < q.length(); i++) @@ -196,7 +214,7 @@ class SQLConn : public SQLProvider field.push_back(q[i++]); i--; - ParamM::const_iterator it = p.find(field); + SQL::ParamMap::const_iterator it = p.find(field); if (it != p.end()) { char* escaped = sqlite3_mprintf("%q", it->second.c_str()); @@ -205,29 +223,16 @@ class SQLConn : public SQLProvider } } } - submit(query, res); + Submit(query, res); } }; class ModuleSQLite3 : public Module { - private: ConnMap conns; public: - ModuleSQLite3() - { - } - - void init() - { - ReadConf(); - - Implementation eventlist[] = { I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleSQLite3() + ~ModuleSQLite3() { ClearConns(); } @@ -243,13 +248,13 @@ class ModuleSQLite3 : public Module conns.clear(); } - void ReadConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ClearConns(); ConfigTagList tags = ServerInstance->Config->ConfTags("database"); for(ConfigIter i = tags.first; i != tags.second; i++) { - if (i->second->getString("module", "sqlite") != "sqlite") + if (!stdalgo::string::equalsci(i->second->getString("provider"), "sqlite")) continue; SQLConn* conn = new SQLConn(this, i->second); conns.insert(std::make_pair(i->second->getString("id"), conn)); @@ -257,12 +262,7 @@ class ModuleSQLite3 : public Module } } - void OnRehash(User* user) - { - ReadConf(); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("sqlite3 provider", VF_VENDOR); } diff --git a/src/modules/extra/m_ssl_gnutls.cpp b/src/modules/extra/m_ssl_gnutls.cpp index 2f4acf3f0..8bd73b2bb 100644 --- a/src/modules/extra/m_ssl_gnutls.cpp +++ b/src/modules/extra/m_ssl_gnutls.cpp @@ -20,120 +20,98 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +/// $CompilerFlags: find_compiler_flags("gnutls") +/// $CompilerFlags: require_version("gnutls" "1.0" "2.12") execute("libgcrypt-config --cflags" "LIBGCRYPT_CXXFLAGS") -#include "inspircd.h" -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#include "ssl.h" -#include "m_cap.h" +/// $LinkerFlags: find_linker_flags("gnutls" "-lgnutls") +/// $LinkerFlags: require_version("gnutls" "1.0" "2.12") execute("libgcrypt-config --libs" "LIBGCRYPT_LDFLAGS") -#ifdef _WIN32 -# pragma comment(lib, "libgnutls-30.lib") +/// $PackageInfo: require_system("centos") gnutls-devel pkgconfig +/// $PackageInfo: require_system("darwin") gnutls pkg-config +/// $PackageInfo: require_system("debian" "1.0" "7.99") libgcrypt11-dev +/// $PackageInfo: require_system("debian") gnutls-bin libgnutls28-dev pkg-config +/// $PackageInfo: require_system("ubuntu" "1.0" "13.10") libgcrypt11-dev +/// $PackageInfo: require_system("ubuntu") gnutls-bin libgnutls-dev pkg-config + +#include "inspircd.h" +#include "modules/ssl.h" +#include <memory> + +// Fix warnings about the use of commas at end of enumerator lists on C++03. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-extensions" +#elif defined __GNUC__ +# if __GNUC__ < 6 +# pragma GCC diagnostic ignored "-pedantic" +# endif #endif -/* $ModDesc: Provides SSL support for clients */ -/* $CompileFlags: pkgconfincludes("gnutls","/gnutls/gnutls.h","") iflt("pkg-config --modversion gnutls","2.12") exec("libgcrypt-config --cflags") */ -/* $LinkerFlags: rpath("pkg-config --libs gnutls") pkgconflibs("gnutls","/libgnutls.so","-lgnutls") iflt("pkg-config --modversion gnutls","2.12") exec("libgcrypt-config --libs") */ -/* $NoPedantic */ +// Fix warnings about using std::auto_ptr on C++11 or newer. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#ifndef GNUTLS_VERSION_MAJOR -#define GNUTLS_VERSION_MAJOR LIBGNUTLS_VERSION_MAJOR -#define GNUTLS_VERSION_MINOR LIBGNUTLS_VERSION_MINOR -#define GNUTLS_VERSION_PATCH LIBGNUTLS_VERSION_PATCH -#endif +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> -// These don't exist in older GnuTLS versions -#if ((GNUTLS_VERSION_MAJOR > 2) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR > 1) || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR == 1 && GNUTLS_VERSION_PATCH >= 7)) -#define GNUTLS_NEW_PRIO_API +#ifndef GNUTLS_VERSION_NUMBER +#define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER +#define GNUTLS_VERSION LIBGNUTLS_VERSION #endif -#if(GNUTLS_VERSION_MAJOR < 2) -typedef gnutls_certificate_credentials_t gnutls_certificate_credentials; -typedef gnutls_dh_params_t gnutls_dh_params; +// Check if the GnuTLS library is at least version major.minor.patch +#define INSPIRCD_GNUTLS_HAS_VERSION(major, minor, patch) (GNUTLS_VERSION_NUMBER >= ((major << 16) | (minor << 8) | patch)) + +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 9, 8) +#define GNUTLS_HAS_MAC_GET_ID +#include <gnutls/crypto.h> #endif -#if (GNUTLS_VERSION_MAJOR > 2 || (GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR >= 12)) +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0) # define GNUTLS_HAS_RND -# include <gnutls/crypto.h> #else # include <gcrypt.h> #endif -enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED }; - -struct SSLConfig : public refcountbase -{ - gnutls_certificate_credentials_t x509_cred; - std::vector<gnutls_x509_crt_t> x509_certs; - gnutls_x509_privkey_t x509_key; - gnutls_dh_params_t dh_params; -#ifdef GNUTLS_NEW_PRIO_API - gnutls_priority_t priority; +#ifdef _WIN32 +# pragma comment(lib, "libgnutls-30.lib") #endif - SSLConfig() - : x509_cred(NULL) - , x509_key(NULL) - , dh_params(NULL) -#ifdef GNUTLS_NEW_PRIO_API - , priority(NULL) +// These don't exist in older GnuTLS versions +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 1, 7) +#define GNUTLS_NEW_PRIO_API #endif - { - } - - ~SSLConfig() - { - ServerInstance->Logs->Log("m_ssl_gnutls", DEBUG, "Destroying SSLConfig %p", (void*)this); - - if (x509_cred) - gnutls_certificate_free_credentials(x509_cred); - for (unsigned int i = 0; i < x509_certs.size(); i++) - gnutls_x509_crt_deinit(x509_certs[i]); +enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN }; - if (x509_key) - gnutls_x509_privkey_deinit(x509_key); - - if (dh_params) - gnutls_dh_params_deinit(dh_params); - -#ifdef GNUTLS_NEW_PRIO_API - if (priority) - gnutls_priority_deinit(priority); +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0) +#define INSPIRCD_GNUTLS_HAS_VECTOR_PUSH +#define GNUTLS_NEW_CERT_CALLBACK_API +typedef gnutls_retr2_st cert_cb_last_param_type; +#else +typedef gnutls_retr_st cert_cb_last_param_type; #endif - } -}; - -static reference<SSLConfig> currconf; - -static SSLConfig* GetSessionConfig(gnutls_session_t session); -#if(GNUTLS_VERSION_MAJOR < 2 || ( GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12 ) ) -static int cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, - const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) { +#if INSPIRCD_GNUTLS_HAS_VERSION(3, 3, 5) +#define INSPIRCD_GNUTLS_HAS_RECV_PACKET +#endif - st->type = GNUTLS_CRT_X509; +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 99, 0) +// The second parameter of gnutls_init() has changed in 2.99.0 from gnutls_connection_end_t to unsigned int +// (it became a general flags parameter) and the enum has been deprecated and generates a warning on use. +typedef unsigned int inspircd_gnutls_session_init_flags_t; #else -static int cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, - const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr2_st * st) { - st->cert_type = GNUTLS_CRT_X509; - st->key_type = GNUTLS_PRIVKEY_X509; +typedef gnutls_connection_end_t inspircd_gnutls_session_init_flags_t; #endif - SSLConfig* conf = GetSessionConfig(session); - std::vector<gnutls_x509_crt_t>& x509_certs = conf->x509_certs; - st->ncerts = x509_certs.size(); - st->cert.x509 = &x509_certs[0]; - st->key.x509 = conf->x509_key; - st->deinit_all = 0; - return 0; -} +#if INSPIRCD_GNUTLS_HAS_VERSION(3, 1, 9) +#define INSPIRCD_GNUTLS_HAS_CORK +#endif -class RandGen : public HandlerBase2<void, char*, size_t> +static Module* thismod; + +class RandGen { public: - RandGen() {} - void Call(char* buffer, size_t len) + static void Call(char* buffer, size_t len) { #ifdef GNUTLS_HAS_RND gnutls_rnd(GNUTLS_RND_RANDOM, buffer, len); @@ -143,749 +121,677 @@ class RandGen : public HandlerBase2<void, char*, size_t> } }; -/** Represents an SSL user's extra data - */ -class issl_session -{ -public: - StreamSocket* socket; - gnutls_session_t sess; - issl_status status; - reference<ssl_cert> cert; - reference<SSLConfig> config; - - issl_session() : socket(NULL), sess(NULL), status(ISSL_NONE) {} -}; - -static SSLConfig* GetSessionConfig(gnutls_session_t sess) +namespace GnuTLS { - issl_session* session = reinterpret_cast<issl_session*>(gnutls_transport_get_ptr(sess)); - return session->config; -} - -class CommandStartTLS : public SplitCommand -{ - public: - bool enabled; - CommandStartTLS (Module* mod) : SplitCommand(mod, "STARTTLS") + class Init { - enabled = true; - works_before_reg = true; - } + public: + Init() { gnutls_global_init(); } + ~Init() { gnutls_global_deinit(); } + }; - CmdResult HandleLocal(const std::vector<std::string> ¶meters, LocalUser *user) + class Exception : public ModuleException { - if (!enabled) - { - user->WriteNumeric(691, "%s :STARTTLS is not enabled", user->nick.c_str()); - return CMD_FAILURE; - } + public: + Exception(const std::string& reason) + : ModuleException(reason) { } + }; - if (user->registered == REG_ALL) - { - user->WriteNumeric(691, "%s :STARTTLS is not permitted after client registration is complete", user->nick.c_str()); - } - else + void ThrowOnError(int errcode, const char* msg) + { + if (errcode < 0) { - if (!user->eh.GetIOHook()) - { - user->WriteNumeric(670, "%s :STARTTLS successful, go ahead with TLS handshake", user->nick.c_str()); - /* We need to flush the write buffer prior to adding the IOHook, - * otherwise we'll be sending this line inside the SSL session - which - * won't start its handshake until the client gets this line. Currently, - * we assume the write will not block here; this is usually safe, as - * STARTTLS is sent very early on in the registration phase, where the - * user hasn't built up much sendq. Handling a blocked write here would - * be very annoying. - */ - user->eh.DoWrite(); - user->eh.AddIOHook(creator); - creator->OnStreamSocketAccept(&user->eh, NULL, NULL); - } - else - user->WriteNumeric(691, "%s :STARTTLS failure", user->nick.c_str()); + std::string reason = msg; + reason.append(" :").append(gnutls_strerror(errcode)); + throw Exception(reason); } - - return CMD_FAILURE; } -}; - -class ModuleSSLGnuTLS : public Module -{ - issl_session* sessions; - gnutls_digest_algorithm_t hash; - - std::string sslports; - int dh_bits; + /** Used to create a gnutls_datum_t* from a std::string + */ + class Datum + { + gnutls_datum_t datum; - RandGen randhandler; - CommandStartTLS starttls; + public: + Datum(const std::string& dat) + { + datum.data = (unsigned char*)dat.data(); + datum.size = static_cast<unsigned int>(dat.length()); + } - GenericCap capHandler; - ServiceProvider iohook; + const gnutls_datum_t* get() const { return &datum; } + }; - inline static const char* UnknownIfNULL(const char* str) + class Hash { - return str ? str : "UNKNOWN"; - } + gnutls_digest_algorithm_t hash; - static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size) - { - issl_session* session = reinterpret_cast<issl_session*>(session_wrap); - if (session->socket->GetEventMask() & FD_READ_WILL_BLOCK) + public: + // Nothing to deallocate, constructor may throw freely + Hash(const std::string& hashname) { -#ifdef _WIN32 - gnutls_transport_set_errno(session->sess, EAGAIN); + // As older versions of gnutls can't do this, let's disable it where needed. +#ifdef GNUTLS_HAS_MAC_GET_ID + // As gnutls_digest_algorithm_t and gnutls_mac_algorithm_t are mapped 1:1, we can do this + // There is no gnutls_dig_get_id() at the moment, but it may come later + hash = (gnutls_digest_algorithm_t)gnutls_mac_get_id(hashname.c_str()); + if (hash == GNUTLS_DIG_UNKNOWN) + throw Exception("Unknown hash type " + hashname); + + // Check if the user is giving us something that is a valid MAC but not digest + gnutls_hash_hd_t is_digest; + if (gnutls_hash_init(&is_digest, hash) < 0) + throw Exception("Unknown hash type " + hashname); + gnutls_hash_deinit(is_digest, NULL); #else - errno = EAGAIN; + if (stdalgo::string::equalsci(hashname, "md5")) + hash = GNUTLS_DIG_MD5; + else if (stdalgo::string::equalsci(hashname, "sha1")) + hash = GNUTLS_DIG_SHA1; +#ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT + else if (stdalgo::string::equalsci(hashname, "sha256")) + hash = GNUTLS_DIG_SHA256; +#endif + else + throw Exception("Unknown hash type " + hashname); #endif - return -1; } - int rv = ServerInstance->SE->Recv(session->socket, reinterpret_cast<char *>(buffer), size, 0); + gnutls_digest_algorithm_t get() const { return hash; } + }; -#ifdef _WIN32 - if (rv < 0) + class DHParams + { + gnutls_dh_params_t dh_params; + + DHParams() { - /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError() - * and then set errno appropriately. - * The gnutls library may also have a different errno variable than us, see - * gnutls_transport_set_errno(3). - */ - gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno); + ThrowOnError(gnutls_dh_params_init(&dh_params), "gnutls_dh_params_init() failed"); } -#endif - if (rv < (int)size) - ServerInstance->SE->ChangeEventMask(session->socket, FD_READ_WILL_BLOCK); - return rv; - } - - static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size) - { - issl_session* session = reinterpret_cast<issl_session*>(session_wrap); - if (session->socket->GetEventMask() & FD_WRITE_WILL_BLOCK) + public: + /** Import */ + static std::auto_ptr<DHParams> Import(const std::string& dhstr) { -#ifdef _WIN32 - gnutls_transport_set_errno(session->sess, EAGAIN); -#else - errno = EAGAIN; -#endif - return -1; + std::auto_ptr<DHParams> dh(new DHParams); + int ret = gnutls_dh_params_import_pkcs3(dh->dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM); + ThrowOnError(ret, "Unable to import DH params"); + return dh; } - int rv = ServerInstance->SE->Send(session->socket, reinterpret_cast<const char *>(buffer), size, 0); - -#ifdef _WIN32 - if (rv < 0) + ~DHParams() { - /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError() - * and then set errno appropriately. - * The gnutls library may also have a different errno variable than us, see - * gnutls_transport_set_errno(3). - */ - gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno); + gnutls_dh_params_deinit(dh_params); } -#endif - if (rv < (int)size) - ServerInstance->SE->ChangeEventMask(session->socket, FD_WRITE_WILL_BLOCK); - return rv; - } - - public: + const gnutls_dh_params_t& get() const { return dh_params; } + }; - ModuleSSLGnuTLS() - : starttls(this), capHandler(this, "tls"), iohook(this, "ssl/gnutls", SERVICE_IOHOOK) + class X509Key { -#ifndef GNUTLS_HAS_RND - gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); -#endif - - sessions = new issl_session[ServerInstance->SE->GetMaxFds()]; - - gnutls_global_init(); // This must be called once in the program - } + /** Ensure that the key is deinited in case the constructor of X509Key throws + */ + class RAIIKey + { + public: + gnutls_x509_privkey_t key; - void init() - { - currconf = new SSLConfig; - InitSSLConfig(currconf); + RAIIKey() + { + ThrowOnError(gnutls_x509_privkey_init(&key), "gnutls_x509_privkey_init() failed"); + } - ServerInstance->GenRandom = &randhandler; + ~RAIIKey() + { + gnutls_x509_privkey_deinit(key); + } + } key; - Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnUserConnect, - I_OnEvent, I_OnHookIO, I_OnCheckReady }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + public: + /** Import */ + X509Key(const std::string& keystr) + { + int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM); + ThrowOnError(ret, "Unable to import private key"); + } - ServerInstance->Modules->AddService(iohook); - ServerInstance->Modules->AddService(starttls); - } + gnutls_x509_privkey_t& get() { return key.key; } + }; - void OnRehash(User* user) + class X509CertList { - sslports.clear(); + std::vector<gnutls_x509_crt_t> certs; - ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls"); - starttls.enabled = Conf->getBool("starttls", true); - - if (Conf->getBool("showports", true)) + public: + /** Import */ + X509CertList(const std::string& certstr) { - sslports = Conf->getString("advertisedports"); - if (!sslports.empty()) - return; + unsigned int certcount = 3; + certs.resize(certcount); + Datum datum(certstr); - for (size_t i = 0; i < ServerInstance->ports.size(); i++) + int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { - ListenSocket* port = ServerInstance->ports[i]; - if (port->bind_tag->getString("ssl") != "gnutls") - continue; - - const std::string& portid = port->bind_desc; - ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Enabling SSL for port %s", portid.c_str()); - - if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1") - { - /* - * Found an SSL port for clients that is not bound to 127.0.0.1 and handled by us, display - * the IP:port in ISUPPORT. - * - * We used to advertise all ports seperated by a ';' char that matched the above criteria, - * but this resulted in too long ISUPPORT lines if there were lots of ports to be displayed. - * To solve this by default we now only display the first IP:port found and let the user - * configure the exact value for the 005 token, if necessary. - */ - sslports = portid; - break; - } + // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs, + // try again with a bigger buffer + certs.resize(certcount); + ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); } - } - } - void OnModuleRehash(User* user, const std::string ¶m) - { - if(param != "ssl") - return; + ThrowOnError(ret, "Unable to load certificates"); - reference<SSLConfig> newconf = new SSLConfig; - try - { - InitSSLConfig(newconf); + // Resize the vector to the actual number of certs because we rely on its size being correct + // when deallocating the certs + certs.resize(certcount); } - catch (ModuleException& ex) + + ~X509CertList() { - ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls: Not applying new config. %s", ex.GetReason()); - return; + for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i) + gnutls_x509_crt_deinit(*i); } - ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls: Applying new config, old config is in use by %d connection(s)", currconf->GetReferenceCount()-1); - currconf = newconf; - } + gnutls_x509_crt_t* raw() { return &certs[0]; } + unsigned int size() const { return certs.size(); } + }; - void InitSSLConfig(SSLConfig* config) + class X509CRL : public refcountbase { - ServerInstance->Logs->Log("m_ssl_gnutls", DEBUG, "Initializing new SSLConfig %p", (void*)config); - - std::string keyfile; - std::string certfile; - std::string cafile; - std::string crlfile; - OnRehash(NULL); - - ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls"); - - cafile = Conf->getString("cafile", CONFIG_PATH "/ca.pem"); - crlfile = Conf->getString("crlfile", CONFIG_PATH "/crl.pem"); - certfile = Conf->getString("certfile", CONFIG_PATH "/cert.pem"); - keyfile = Conf->getString("keyfile", CONFIG_PATH "/key.pem"); - dh_bits = Conf->getInt("dhbits"); - std::string hashname = Conf->getString("hash", "md5"); - - // The GnuTLS manual states that the gnutls_set_default_priority() - // call we used previously when initializing the session is the same - // as setting the "NORMAL" priority string. - // Thus if the setting below is not in the config we will behave exactly - // the same as before, when the priority setting wasn't available. - std::string priorities = Conf->getString("priority", "NORMAL"); - - if((dh_bits != 768) && (dh_bits != 1024) && (dh_bits != 2048) && (dh_bits != 3072) && (dh_bits != 4096)) - dh_bits = 1024; - - if (hashname == "md5") - hash = GNUTLS_DIG_MD5; - else if (hashname == "sha1") - hash = GNUTLS_DIG_SHA1; -#ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT - else if (hashname == "sha256") - hash = GNUTLS_DIG_SHA256; -#endif - else - throw ModuleException("Unknown hash type " + hashname); - + class RAIICRL + { + public: + gnutls_x509_crl_t crl; - int ret; + RAIICRL() + { + ThrowOnError(gnutls_x509_crl_init(&crl), "gnutls_x509_crl_init() failed"); + } - gnutls_certificate_credentials_t& x509_cred = config->x509_cred; + ~RAIICRL() + { + gnutls_x509_crl_deinit(crl); + } + } crl; - ret = gnutls_certificate_allocate_credentials(&x509_cred); - if (ret < 0) + public: + /** Import */ + X509CRL(const std::string& crlstr) { - // Set to NULL because we can't be sure what value is in it and we must not try to - // deallocate it in case of an error - x509_cred = NULL; - throw ModuleException("Failed to allocate certificate credentials: " + std::string(gnutls_strerror(ret))); + int ret = gnutls_x509_crl_import(get(), Datum(crlstr).get(), GNUTLS_X509_FMT_PEM); + ThrowOnError(ret, "Unable to load certificate revocation list"); } - if((ret =gnutls_certificate_set_x509_trust_file(x509_cred, cafile.c_str(), GNUTLS_X509_FMT_PEM)) < 0) - ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 trust file '%s': %s", cafile.c_str(), gnutls_strerror(ret)); - - if((ret = gnutls_certificate_set_x509_crl_file (x509_cred, crlfile.c_str(), GNUTLS_X509_FMT_PEM)) < 0) - ServerInstance->Logs->Log("m_ssl_gnutls",DEBUG, "m_ssl_gnutls.so: Failed to set X.509 CRL file '%s': %s", crlfile.c_str(), gnutls_strerror(ret)); + gnutls_x509_crl_t& get() { return crl.crl; } + }; - FileReader reader; - - reader.LoadFile(certfile); - std::string cert_string = reader.Contents(); - gnutls_datum_t cert_datum = { (unsigned char*)cert_string.data(), static_cast<unsigned int>(cert_string.length()) }; +#ifdef GNUTLS_NEW_PRIO_API + class Priority + { + gnutls_priority_t priority; - reader.LoadFile(keyfile); - std::string key_string = reader.Contents(); - gnutls_datum_t key_datum = { (unsigned char*)key_string.data(), static_cast<unsigned int>(key_string.length()) }; + public: + Priority(const std::string& priorities) + { + // Try to set the priorities for ciphers, kex methods etc. to the user supplied string + // If the user did not supply anything then the string is already set to "NORMAL" + const char* priocstr = priorities.c_str(); + const char* prioerror; - std::vector<gnutls_x509_crt_t>& x509_certs = config->x509_certs; + int ret = gnutls_priority_init(&priority, priocstr, &prioerror); + if (ret < 0) + { + // gnutls did not understand the user supplied string + throw Exception("Unable to initialize priorities to \"" + priorities + "\": " + gnutls_strerror(ret) + " Syntax error at position " + ConvToStr((unsigned int) (prioerror - priocstr))); + } + } - // If this fails, no SSL port will work. At all. So, do the smart thing - throw a ModuleException - unsigned int certcount = 3; - x509_certs.resize(certcount); - ret = gnutls_x509_crt_list_import(&x509_certs[0], &certcount, &cert_datum, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); - if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) + ~Priority() { - // the buffer wasn't big enough to hold all certs but gnutls updated certcount to the number of available certs, try again with a bigger buffer - x509_certs.resize(certcount); - ret = gnutls_x509_crt_list_import(&x509_certs[0], &certcount, &cert_datum, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + gnutls_priority_deinit(priority); } - if (ret <= 0) + void SetupSession(gnutls_session_t sess) { - // clear the vector so we won't call gnutls_x509_crt_deinit() on the (uninited) certs later - x509_certs.clear(); - throw ModuleException("Unable to load GnuTLS server certificate (" + certfile + "): " + ((ret < 0) ? (std::string(gnutls_strerror(ret))) : "No certs could be read")); + gnutls_priority_set(sess, priority); } - x509_certs.resize(ret); - gnutls_x509_privkey_t& x509_key = config->x509_key; - if (gnutls_x509_privkey_init(&x509_key) < 0) + static const char* GetDefault() { - // Make sure the destructor does not try to deallocate this, see above - x509_key = NULL; - throw ModuleException("Unable to initialize private key"); + return "NORMAL:%SERVER_PRECEDENCE:-VERS-SSL3.0"; } - if((ret = gnutls_x509_privkey_import(x509_key, &key_datum, GNUTLS_X509_FMT_PEM)) < 0) - throw ModuleException("Unable to load GnuTLS server private key (" + keyfile + "): " + std::string(gnutls_strerror(ret))); + static std::string RemoveUnknownTokens(const std::string& prio) + { + std::string ret; + irc::sepstream ss(prio, ':'); + for (std::string token; ss.GetToken(token); ) + { + // Save current position so we can revert later if needed + const std::string::size_type prevpos = ret.length(); + // Append next token + if (!ret.empty()) + ret.push_back(':'); + ret.append(token); + + gnutls_priority_t test; + if (gnutls_priority_init(&test, ret.c_str(), NULL) < 0) + { + // The new token broke the priority string, revert to the previously working one + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Priority string token not recognized: \"%s\"", token.c_str()); + ret.erase(prevpos); + } + else + { + // Worked + gnutls_priority_deinit(test); + } + } + return ret; + } + }; +#else + /** Dummy class, used when gnutls_priority_set() is not available + */ + class Priority + { + public: + Priority(const std::string& priorities) + { + if (priorities != GetDefault()) + throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it"); + } - if((ret = gnutls_certificate_set_x509_key(x509_cred, &x509_certs[0], certcount, x509_key)) < 0) - throw ModuleException("Unable to set GnuTLS cert/key pair: " + std::string(gnutls_strerror(ret))); + static void SetupSession(gnutls_session_t sess) + { + // Always set the default priorities + gnutls_set_default_priority(sess); + } - #ifdef GNUTLS_NEW_PRIO_API - // Try to set the priorities for ciphers, kex methods etc. to the user supplied string - // If the user did not supply anything then the string is already set to "NORMAL" - const char* priocstr = priorities.c_str(); - const char* prioerror; + static const char* GetDefault() + { + return "NORMAL"; + } - gnutls_priority_t& priority = config->priority; - if ((ret = gnutls_priority_init(&priority, priocstr, &prioerror)) < 0) + static std::string RemoveUnknownTokens(const std::string& prio) { - // gnutls did not understand the user supplied string, log and fall back to the default priorities - ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to set priorities to \"%s\": %s Syntax error at position %u, falling back to default (NORMAL)", priorities.c_str(), gnutls_strerror(ret), (unsigned int) (prioerror - priocstr)); - gnutls_priority_init(&priority, "NORMAL", NULL); + // We don't do anything here because only NORMAL is accepted + return prio; } + }; +#endif - #else - if (priorities != "NORMAL") - ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: You've set <gnutls:priority> to a value other than the default, but this is only supported with GnuTLS v2.1.7 or newer. Your GnuTLS version is older than that so the option will have no effect."); - #endif + class CertCredentials + { + /** DH parameters associated with these credentials + */ + std::auto_ptr<DHParams> dh; - #if(GNUTLS_VERSION_MAJOR < 2 || ( GNUTLS_VERSION_MAJOR == 2 && GNUTLS_VERSION_MINOR < 12 ) ) - gnutls_certificate_client_set_retrieve_function (x509_cred, cert_callback); - #else - gnutls_certificate_set_retrieve_function (x509_cred, cert_callback); - #endif + protected: + gnutls_certificate_credentials_t cred; - gnutls_dh_params_t& dh_params = config->dh_params; - ret = gnutls_dh_params_init(&dh_params); - if (ret < 0) + public: + CertCredentials() { - // Make sure the destructor does not try to deallocate this, see above - dh_params = NULL; - ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to initialise DH parameters: %s", gnutls_strerror(ret)); - return; + ThrowOnError(gnutls_certificate_allocate_credentials(&cred), "Cannot allocate certificate credentials"); } - std::string dhfile = Conf->getString("dhfile"); - if (!dhfile.empty()) + ~CertCredentials() { - // Try to load DH params from file - reader.LoadFile(dhfile); - std::string dhstring = reader.Contents(); - gnutls_datum_t dh_datum = { (unsigned char*)dhstring.data(), static_cast<unsigned int>(dhstring.length()) }; - - if ((ret = gnutls_dh_params_import_pkcs3(dh_params, &dh_datum, GNUTLS_X509_FMT_PEM)) < 0) - { - // File unreadable or GnuTLS was unhappy with the contents, generate the DH primes now - ServerInstance->Logs->Log("m_ssl_gnutls", DEFAULT, "m_ssl_gnutls.so: Generating DH parameters because I failed to load them from file '%s': %s", dhfile.c_str(), gnutls_strerror(ret)); - GenerateDHParams(dh_params); - } + gnutls_certificate_free_credentials(cred); } - else + + /** Associates these credentials with the session + */ + void SetupSession(gnutls_session_t sess) { - GenerateDHParams(dh_params); + gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred); } - gnutls_certificate_set_dh_params(x509_cred, dh_params); - } + /** Set the given DH parameters to be used with these credentials + */ + void SetDH(std::auto_ptr<DHParams>& DH) + { + dh = DH; + gnutls_certificate_set_dh_params(cred, dh->get()); + } + }; - void GenerateDHParams(gnutls_dh_params_t dh_params) + class X509Credentials : public CertCredentials { - // Generate Diffie Hellman parameters - for use with DHE - // kx algorithms. These should be discarded and regenerated - // once a day, once a week or once a month. Depending on the - // security requirements. + /** Private key + */ + X509Key key; - int ret; + /** Certificate list, presented to the peer + */ + X509CertList certs; - if((ret = gnutls_dh_params_generate2(dh_params, dh_bits)) < 0) - ServerInstance->Logs->Log("m_ssl_gnutls",DEFAULT, "m_ssl_gnutls.so: Failed to generate DH parameters (%d bits): %s", dh_bits, gnutls_strerror(ret)); - } + /** Trusted CA, may be NULL + */ + std::auto_ptr<X509CertList> trustedca; - ~ModuleSSLGnuTLS() - { - currconf = NULL; + /** Certificate revocation list, may be NULL + */ + std::auto_ptr<X509CRL> crl; - gnutls_global_deinit(); - delete[] sessions; - ServerInstance->GenRandom = &ServerInstance->HandleGenRandom; - } + static int cert_callback(gnutls_session_t session, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st); - void OnCleanup(int target_type, void* item) - { - if(target_type == TYPE_USER) + public: + X509Credentials(const std::string& certstr, const std::string& keystr) + : key(keystr) + , certs(certstr) { - LocalUser* user = IS_LOCAL(static_cast<User*>(item)); + // Throwing is ok here, the destructor of Credentials is called in that case + int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get()); + ThrowOnError(ret, "Unable to set cert/key pair"); - if (user && user->eh.GetIOHook() == this) - { - // User is using SSL, they're a local user, and they're using one of *our* SSL ports. - // Potentially there could be multiple SSL modules loaded at once on different ports. - ServerInstance->Users->QuitUser(user, "SSL module unloading"); - } +#ifdef GNUTLS_NEW_CERT_CALLBACK_API + gnutls_certificate_set_retrieve_function(cred, cert_callback); +#else + gnutls_certificate_client_set_retrieve_function(cred, cert_callback); +#endif } - } - Version GetVersion() - { - return Version("Provides SSL support for clients", VF_VENDOR); - } + /** Sets the trusted CA and the certificate revocation list + * to use when verifying certificates + */ + void SetCA(std::auto_ptr<X509CertList>& certlist, std::auto_ptr<X509CRL>& CRL) + { + // Do nothing if certlist is NULL + if (certlist.get()) + { + int ret = gnutls_certificate_set_x509_trust(cred, certlist->raw(), certlist->size()); + ThrowOnError(ret, "gnutls_certificate_set_x509_trust() failed"); + + if (CRL.get()) + { + ret = gnutls_certificate_set_x509_crl(cred, &CRL->get(), 1); + ThrowOnError(ret, "gnutls_certificate_set_x509_crl() failed"); + } + trustedca = certlist; + crl = CRL; + } + } + }; - void On005Numeric(std::string &output) + class DataReader { - if (!sslports.empty()) - output.append(" SSL=" + sslports); - if (starttls.enabled) - output.append(" STARTTLS"); - } + int retval; +#ifdef INSPIRCD_GNUTLS_HAS_RECV_PACKET + gnutls_packet_t packet; - void OnHookIO(StreamSocket* user, ListenSocket* lsb) - { - if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "gnutls") + public: + DataReader(gnutls_session_t sess) { - /* Hook the user with our module */ - user->AddIOHook(this); + // Using the packet API avoids the final copy of the data which GnuTLS does if we supply + // our own buffer. Instead, we get the buffer containing the data from GnuTLS and copy it + // to the recvq directly from there in appendto(). + retval = gnutls_record_recv_packet(sess, &packet); } - } - void OnRequest(Request& request) - { - if (strcmp("GET_SSL_CERT", request.id) == 0) + void appendto(std::string& recvq) { - SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request); - int fd = req.sock->GetFd(); - issl_session* session = &sessions[fd]; + // Copy data from GnuTLS buffers to recvq + gnutls_datum_t datum; + gnutls_packet_get(packet, &datum, NULL); + recvq.append(reinterpret_cast<const char*>(datum.data), datum.size); - req.cert = session->cert; + gnutls_packet_deinit(packet); } - else if (!strcmp("GET_RAW_SSL_SESSION", request.id)) +#else + char* const buffer; + + public: + DataReader(gnutls_session_t sess) + : buffer(ServerInstance->GetReadBuffer()) { - SSLRawSessionRequest& req = static_cast<SSLRawSessionRequest&>(request); - if ((req.fd >= 0) && (req.fd < ServerInstance->SE->GetMaxFds())) - req.data = reinterpret_cast<void*>(sessions[req.fd].sess); + // Read data from GnuTLS buffers into ReadBuffer + retval = gnutls_record_recv(sess, buffer, ServerInstance->Config->NetBufferSize); } - } - void InitSession(StreamSocket* user, bool me_server) - { - issl_session* session = &sessions[user->GetFd()]; - - gnutls_init(&session->sess, me_server ? GNUTLS_SERVER : GNUTLS_CLIENT); - session->socket = user; - session->config = currconf; - - #ifdef GNUTLS_NEW_PRIO_API - gnutls_priority_set(session->sess, currconf->priority); - #else - gnutls_set_default_priority(session->sess); - #endif - gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, currconf->x509_cred); - gnutls_dh_set_prime_bits(session->sess, dh_bits); - gnutls_transport_set_ptr(session->sess, reinterpret_cast<gnutls_transport_ptr_t>(session)); - gnutls_transport_set_push_function(session->sess, gnutls_push_wrapper); - gnutls_transport_set_pull_function(session->sess, gnutls_pull_wrapper); - - if (me_server) - gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any. + void appendto(std::string& recvq) + { + // Copy data from ReadBuffer to recvq + recvq.append(buffer, retval); + } +#endif - Handshake(session, user); - } + int ret() const { return retval; } + }; - void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) + class Profile { - issl_session* session = &sessions[user->GetFd()]; + /** Name of this profile + */ + const std::string name; - /* For STARTTLS: Don't try and init a session on a socket that already has a session */ - if (session->sess) - return; + /** X509 certificate(s) and key + */ + X509Credentials x509cred; - InitSession(user, true); - } + /** The minimum length in bits for the DH prime to be accepted as a client + */ + unsigned int min_dh_bits; - void OnStreamSocketConnect(StreamSocket* user) - { - InitSession(user, false); - } + /** Hashing algorithm to use when generating certificate fingerprints + */ + Hash hash; - void OnStreamSocketClose(StreamSocket* user) - { - CloseSession(&sessions[user->GetFd()]); - } + /** Priorities for ciphers, compression methods, etc. + */ + Priority priority; - int OnStreamSocketRead(StreamSocket* user, std::string& recvq) - { - issl_session* session = &sessions[user->GetFd()]; + /** Rough max size of records to send + */ + const unsigned int outrecsize; - if (!session->sess) + /** True to request a client certificate as a server + */ + const bool requestclientcert; + + static std::string ReadFile(const std::string& filename) { - CloseSession(session); - user->SetError("No SSL session"); - return -1; + FileReader reader(filename); + std::string ret = reader.GetString(); + if (ret.empty()) + throw Exception("Cannot read file " + filename); + return ret; } - if (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) + static std::string GetPrioStr(const std::string& profilename, ConfigTag* tag) { - // The handshake isn't finished, try to finish it. - - if(!Handshake(session, user)) + // Use default priority string if this tag does not specify one + std::string priostr = GnuTLS::Priority::GetDefault(); + bool found = tag->readString("priority", priostr); + // If the prio string isn't set in the config don't be strict about the default one because it doesn't work on all versions of GnuTLS + if (!tag->getBool("strictpriority", found)) { - if (session->status != ISSL_CLOSING) - return 0; - return -1; + std::string stripped = GnuTLS::Priority::RemoveUnknownTokens(priostr); + if (stripped.empty()) + { + // Stripping failed, act as if a prio string wasn't set + stripped = GnuTLS::Priority::RemoveUnknownTokens(GnuTLS::Priority::GetDefault()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens and stripping it didn't yield a working one either, falling back to \"%s\"", profilename.c_str(), stripped.c_str()); + } + else if ((found) && (stripped != priostr)) + { + // Prio string was set in the config and we ended up with something that works but different + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Priority string for profile \"%s\" contains unknown tokens, stripped to \"%s\"", profilename.c_str(), stripped.c_str()); + } + priostr.swap(stripped); } + return priostr; } - // If we resumed the handshake then session->status will be ISSL_HANDSHAKEN. - - if (session->status == ISSL_HANDSHAKEN) + public: + struct Config { - char* buffer = ServerInstance->GetReadBuffer(); - size_t bufsiz = ServerInstance->Config->NetBufferSize; - int ret = gnutls_record_recv(session->sess, buffer, bufsiz); - if (ret > 0) - { - recvq.append(buffer, ret); - // Schedule a read if there is still data in the GnuTLS buffer - if (gnutls_record_check_pending(session->sess) > 0) - ServerInstance->SE->ChangeEventMask(user, FD_ADD_TRIAL_READ); - return 1; - } - else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) - { - return 0; - } - else if (ret == 0) + std::string name; + + std::auto_ptr<X509CertList> ca; + std::auto_ptr<X509CRL> crl; + + std::string certstr; + std::string keystr; + std::auto_ptr<DHParams> dh; + + std::string priostr; + unsigned int mindh; + std::string hashstr; + + unsigned int outrecsize; + bool requestclientcert; + + Config(const std::string& profilename, ConfigTag* tag) + : name(profilename) + , certstr(ReadFile(tag->getString("certfile", "cert.pem"))) + , keystr(ReadFile(tag->getString("keyfile", "key.pem"))) + , dh(DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem")))) + , priostr(GetPrioStr(profilename, tag)) + , mindh(tag->getUInt("mindhbits", 1024)) + , hashstr(tag->getString("hash", "md5")) + , requestclientcert(tag->getBool("requestclientcert", true)) { - user->SetError("Connection closed"); - CloseSession(session); - return -1; - } - else - { - user->SetError(gnutls_strerror(ret)); - CloseSession(session); - return -1; - } - } - else if (session->status == ISSL_CLOSING) - return -1; - - return 0; - } + // Load trusted CA and revocation list, if set + std::string filename = tag->getString("cafile"); + if (!filename.empty()) + { + ca.reset(new X509CertList(ReadFile(filename))); - int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) - { - issl_session* session = &sessions[user->GetFd()]; + filename = tag->getString("crlfile"); + if (!filename.empty()) + crl.reset(new X509CRL(ReadFile(filename))); + } - if (!session->sess) +#ifdef INSPIRCD_GNUTLS_HAS_CORK + // If cork support is available outrecsize represents the (rough) max amount of data we give GnuTLS while corked + outrecsize = tag->getUInt("outrecsize", 2048, 512); +#else + outrecsize = tag->getUInt("outrecsize", 2048, 512, 16384); +#endif + } + }; + + Profile(Config& config) + : name(config.name) + , x509cred(config.certstr, config.keystr) + , min_dh_bits(config.mindh) + , hash(config.hashstr) + , priority(config.priostr) + , outrecsize(config.outrecsize) + , requestclientcert(config.requestclientcert) { - CloseSession(session); - user->SetError("No SSL session"); - return -1; + x509cred.SetDH(config.dh); + x509cred.SetCA(config.ca, config.crl); } - - if (session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ) + /** Set up the given session with the settings in this profile + */ + void SetupSession(gnutls_session_t sess) { - // The handshake isn't finished, try to finish it. - Handshake(session, user); - if (session->status != ISSL_CLOSING) - return 0; - return -1; + priority.SetupSession(sess); + x509cred.SetupSession(sess); + gnutls_dh_set_prime_bits(sess, min_dh_bits); + + // Request client certificate if enabled and we are a server, no-op if we're a client + if (requestclientcert) + gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST); } - int ret = 0; + const std::string& GetName() const { return name; } + X509Credentials& GetX509Credentials() { return x509cred; } + gnutls_digest_algorithm_t GetHash() const { return hash.get(); } + unsigned int GetOutgoingRecordSize() const { return outrecsize; } + }; +} - if (session->status == ISSL_HANDSHAKEN) - { - ret = gnutls_record_send(session->sess, sendq.data(), sendq.length()); +class GnuTLSIOHook : public SSLIOHook +{ + private: + gnutls_session_t sess; + issl_status status; +#ifdef INSPIRCD_GNUTLS_HAS_CORK + size_t gbuffersize; +#endif - if (ret == (int)sendq.length()) - { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_WRITE); - return 1; - } - else if (ret > 0) - { - sendq = sendq.substr(ret); - ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE); - return 0; - } - else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0) - { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE); - return 0; - } - else // (ret < 0) - { - user->SetError(gnutls_strerror(ret)); - CloseSession(session); - return -1; - } + void CloseSession() + { + if (this->sess) + { + gnutls_bye(this->sess, GNUTLS_SHUT_WR); + gnutls_deinit(this->sess); } - - return 0; + sess = NULL; + certificate = NULL; + status = ISSL_NONE; } - bool Handshake(issl_session* session, StreamSocket* user) + // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed + int Handshake(StreamSocket* user) { - int ret = gnutls_handshake(session->sess); + int ret = gnutls_handshake(this->sess); if (ret < 0) { if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) { // Handshake needs resuming later, read() or write() would have blocked. + this->status = ISSL_HANDSHAKING; - if(gnutls_record_get_direction(session->sess) == 0) + if (gnutls_record_get_direction(this->sess) == 0) { // gnutls_handshake() wants to read() again. - session->status = ISSL_HANDSHAKING_READ; - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); } else { // gnutls_handshake() wants to write() again. - session->status = ISSL_HANDSHAKING_WRITE; - ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); + SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); } + + return 0; } else { user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret))); - CloseSession(session); - session->status = ISSL_CLOSING; + CloseSession(); + return -1; } - - return false; } else { // Change the seesion state - session->status = ISSL_HANDSHAKEN; + this->status = ISSL_HANDSHAKEN; - VerifyCertificate(session,user); + VerifyCertificate(); // Finish writing, if any left - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); - - return true; - } - } - - void OnUserConnect(LocalUser* user) - { - if (user->eh.GetIOHook() == this) - { - if (sessions[user->eh.GetFd()].sess) - { - const gnutls_session_t& sess = sessions[user->eh.GetFd()].sess; - std::string cipher = UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess))); - cipher.append("-").append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).append("-"); - cipher.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess)))); - - ssl_cert* cert = sessions[user->eh.GetFd()].cert; - if (cert->fingerprint.empty()) - user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), cipher.c_str()); - else - user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"" - " and your SSL fingerprint is %s", user->nick.c_str(), cipher.c_str(), cert->fingerprint.c_str()); - } - } - } + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); - void CloseSession(issl_session* session) - { - if (session->sess) - { - gnutls_bye(session->sess, GNUTLS_SHUT_WR); - gnutls_deinit(session->sess); + return 1; } - session->socket = NULL; - session->sess = NULL; - session->cert = NULL; - session->status = ISSL_NONE; - session->config = NULL; } - void VerifyCertificate(issl_session* session, StreamSocket* user) + void VerifyCertificate() { - if (!session->sess || !user) - return; - - unsigned int status; + unsigned int certstatus; const gnutls_datum_t* cert_list; int ret; unsigned int cert_list_size; gnutls_x509_crt_t cert; - char name[MAXBUF]; - unsigned char digest[MAXBUF]; + char str[512]; + unsigned char digest[512]; size_t digest_size = sizeof(digest); - size_t name_size = sizeof(name); + size_t name_size = sizeof(str); ssl_cert* certinfo = new ssl_cert; - session->cert = certinfo; + this->certificate = certinfo; /* This verification function uses the trusted CAs in the credentials * structure. So you must have installed one or more CA certificates. */ - ret = gnutls_certificate_verify_peers2(session->sess, &status); + ret = gnutls_certificate_verify_peers2(this->sess, &certstatus); if (ret < 0) { @@ -893,16 +799,16 @@ class ModuleSSLGnuTLS : public Module return; } - certinfo->invalid = (status & GNUTLS_CERT_INVALID); - certinfo->unknownsigner = (status & GNUTLS_CERT_SIGNER_NOT_FOUND); - certinfo->revoked = (status & GNUTLS_CERT_REVOKED); - certinfo->trusted = !(status & GNUTLS_CERT_SIGNER_NOT_CA); + certinfo->invalid = (certstatus & GNUTLS_CERT_INVALID); + certinfo->unknownsigner = (certstatus & GNUTLS_CERT_SIGNER_NOT_FOUND); + certinfo->revoked = (certstatus & GNUTLS_CERT_REVOKED); + certinfo->trusted = !(certstatus & GNUTLS_CERT_SIGNER_NOT_CA); /* Up to here the process is the same for X.509 certificates and * OpenPGP keys. From now on X.509 certificates are assumed. This can * be easily extended to work with openpgp keys as well. */ - if (gnutls_certificate_type_get(session->sess) != GNUTLS_CRT_X509) + if (gnutls_certificate_type_get(this->sess) != GNUTLS_CRT_X509) { certinfo->error = "No X509 keys sent"; return; @@ -916,7 +822,7 @@ class ModuleSSLGnuTLS : public Module } cert_list_size = 0; - cert_list = gnutls_certificate_get_peers(session->sess, &cert_list_size); + cert_list = gnutls_certificate_get_peers(this->sess, &cert_list_size); if (cert_list == NULL) { certinfo->error = "No certificate was found"; @@ -934,31 +840,31 @@ class ModuleSSLGnuTLS : public Module goto info_done_dealloc; } - if (gnutls_x509_crt_get_dn(cert, name, &name_size) == 0) + if (gnutls_x509_crt_get_dn(cert, str, &name_size) == 0) { std::string& dn = certinfo->dn; - dn = name; + dn = str; // Make sure there are no chars in the string that we consider invalid if (dn.find_first_of("\r\n") != std::string::npos) dn.clear(); } - name_size = sizeof(name); - if (gnutls_x509_crt_get_issuer_dn(cert, name, &name_size) == 0) + name_size = sizeof(str); + if (gnutls_x509_crt_get_issuer_dn(cert, str, &name_size) == 0) { std::string& issuer = certinfo->issuer; - issuer = name; + issuer = str; if (issuer.find_first_of("\r\n") != std::string::npos) issuer.clear(); } - if ((ret = gnutls_x509_crt_get_fingerprint(cert, hash, digest, &digest_size)) < 0) + if ((ret = gnutls_x509_crt_get_fingerprint(cert, GetProfile().GetHash(), digest, &digest_size)) < 0) { certinfo->error = gnutls_strerror(ret); } else { - certinfo->fingerprint = irc::hex(digest, digest_size); + certinfo->fingerprint = BinToHex(digest, digest_size); } /* Beware here we do not check for errors. @@ -972,15 +878,522 @@ info_done_dealloc: gnutls_x509_crt_deinit(cert); } - void OnEvent(Event& ev) + // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error + int PrepareIO(StreamSocket* sock) + { + if (status == ISSL_HANDSHAKEN) + return 1; + else if (status == ISSL_HANDSHAKING) + { + // The handshake isn't finished, try to finish it + return Handshake(sock); + } + + CloseSession(); + sock->SetError("No SSL session"); + return -1; + } + +#ifdef INSPIRCD_GNUTLS_HAS_CORK + int FlushBuffer(StreamSocket* sock) + { + // If GnuTLS has some data buffered, write it + if (gbuffersize) + return HandleWriteRet(sock, gnutls_record_uncork(this->sess, 0)); + return 1; + } +#endif + + int HandleWriteRet(StreamSocket* sock, int ret) + { + if (ret > 0) + { +#ifdef INSPIRCD_GNUTLS_HAS_CORK + gbuffersize -= ret; + if (gbuffersize) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE); + return 0; + } +#endif + return ret; + } + else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED || ret == 0) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE); + return 0; + } + else // (ret < 0) + { + sock->SetError(gnutls_strerror(ret)); + CloseSession(); + return -1; + } + } + + static const char* UnknownIfNULL(const char* str) + { + return str ? str : "UNKNOWN"; + } + + static ssize_t gnutls_pull_wrapper(gnutls_transport_ptr_t session_wrap, void* buffer, size_t size) + { + StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap); +#ifdef _WIN32 + GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod)); +#endif + + if (sock->GetEventMask() & FD_READ_WILL_BLOCK) + { +#ifdef _WIN32 + gnutls_transport_set_errno(session->sess, EAGAIN); +#else + errno = EAGAIN; +#endif + return -1; + } + + int rv = SocketEngine::Recv(sock, reinterpret_cast<char *>(buffer), size, 0); + +#ifdef _WIN32 + if (rv < 0) + { + /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError() + * and then set errno appropriately. + * The gnutls library may also have a different errno variable than us, see + * gnutls_transport_set_errno(3). + */ + gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno); + } +#endif + + if (rv < (int)size) + SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK); + return rv; + } + +#ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH + static ssize_t VectorPush(gnutls_transport_ptr_t transportptr, const giovec_t* iov, int iovcnt) + { + StreamSocket* sock = reinterpret_cast<StreamSocket*>(transportptr); +#ifdef _WIN32 + GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod)); +#endif + + if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK) + { +#ifdef _WIN32 + gnutls_transport_set_errno(session->sess, EAGAIN); +#else + errno = EAGAIN; +#endif + return -1; + } + + // Cast the giovec_t to iovec not to IOVector so the correct function is called on Windows + int ret = SocketEngine::WriteV(sock, reinterpret_cast<const iovec*>(iov), iovcnt); +#ifdef _WIN32 + // See the function above for more info about the usage of gnutls_transport_set_errno() on Windows + if (ret < 0) + gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno); +#endif + + int size = 0; + for (int i = 0; i < iovcnt; i++) + size += iov[i].iov_len; + + if (ret < size) + SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK); + return ret; + } + +#else // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH + static ssize_t gnutls_push_wrapper(gnutls_transport_ptr_t session_wrap, const void* buffer, size_t size) + { + StreamSocket* sock = reinterpret_cast<StreamSocket*>(session_wrap); +#ifdef _WIN32 + GnuTLSIOHook* session = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod)); +#endif + + if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK) + { +#ifdef _WIN32 + gnutls_transport_set_errno(session->sess, EAGAIN); +#else + errno = EAGAIN; +#endif + return -1; + } + + int rv = SocketEngine::Send(sock, reinterpret_cast<const char *>(buffer), size, 0); + +#ifdef _WIN32 + if (rv < 0) + { + /* Windows doesn't use errno, but gnutls does, so check SocketEngine::IgnoreError() + * and then set errno appropriately. + * The gnutls library may also have a different errno variable than us, see + * gnutls_transport_set_errno(3). + */ + gnutls_transport_set_errno(session->sess, SocketEngine::IgnoreError() ? EAGAIN : errno); + } +#endif + + if (rv < (int)size) + SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK); + return rv; + } +#endif // INSPIRCD_GNUTLS_HAS_VECTOR_PUSH + + public: + GnuTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, inspircd_gnutls_session_init_flags_t flags) + : SSLIOHook(hookprov) + , sess(NULL) + , status(ISSL_NONE) +#ifdef INSPIRCD_GNUTLS_HAS_CORK + , gbuffersize(0) +#endif + { + gnutls_init(&sess, flags); + gnutls_transport_set_ptr(sess, reinterpret_cast<gnutls_transport_ptr_t>(sock)); +#ifdef INSPIRCD_GNUTLS_HAS_VECTOR_PUSH + gnutls_transport_set_vec_push_function(sess, VectorPush); +#else + gnutls_transport_set_push_function(sess, gnutls_push_wrapper); +#endif + gnutls_transport_set_pull_function(sess, gnutls_pull_wrapper); + GetProfile().SetupSession(sess); + + sock->AddIOHook(this); + Handshake(sock); + } + + void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE + { + CloseSession(); + } + + int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE + { + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; + + // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN. + { + GnuTLS::DataReader reader(sess); + int ret = reader.ret(); + if (ret > 0) + { + reader.appendto(recvq); + // Schedule a read if there is still data in the GnuTLS buffer + if (gnutls_record_check_pending(sess) > 0) + SocketEngine::ChangeEventMask(user, FD_ADD_TRIAL_READ); + return 1; + } + else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) + { + return 0; + } + else if (ret == 0) + { + user->SetError("Connection closed"); + CloseSession(); + return -1; + } + else + { + user->SetError(gnutls_strerror(ret)); + CloseSession(); + return -1; + } + } + } + + int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE + { + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; + + // Session is ready for transferring application data + +#ifdef INSPIRCD_GNUTLS_HAS_CORK + while (true) + { + // If there is something in the GnuTLS buffer try to send() it + int ret = FlushBuffer(user); + if (ret <= 0) + return ret; // Couldn't flush entire buffer, retry later (or close on error) + + // GnuTLS buffer is empty, if the sendq is empty as well then break to set FD_WANT_NO_WRITE + if (sendq.empty()) + break; + + // GnuTLS buffer is empty but sendq is not, begin sending data from the sendq + gnutls_record_cork(this->sess); + while ((!sendq.empty()) && (gbuffersize < GetProfile().GetOutgoingRecordSize())) + { + const StreamSocket::SendQueue::Element& elem = sendq.front(); + gbuffersize += elem.length(); + ret = gnutls_record_send(this->sess, elem.data(), elem.length()); + if (ret < 0) + { + CloseSession(); + return -1; + } + sendq.pop_front(); + } + } +#else + int ret = 0; + + while (!sendq.empty()) + { + FlattenSendQueue(sendq, GetProfile().GetOutgoingRecordSize()); + const StreamSocket::SendQueue::Element& buffer = sendq.front(); + ret = HandleWriteRet(user, gnutls_record_send(this->sess, buffer.data(), buffer.length())); + + if (ret <= 0) + return ret; + else if (ret < (int)buffer.length()) + { + sendq.erase_front(ret); + SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE); + return 0; + } + + // Wrote entire record, continue sending + sendq.pop_front(); + } +#endif + + SocketEngine::ChangeEventMask(user, FD_WANT_NO_WRITE); + return 1; + } + + void GetCiphersuite(std::string& out) const CXX11_OVERRIDE + { + if (!IsHandshakeDone()) + return; + out.append(UnknownIfNULL(gnutls_protocol_get_name(gnutls_protocol_get_version(sess)))).push_back('-'); + out.append(UnknownIfNULL(gnutls_kx_get_name(gnutls_kx_get(sess)))).push_back('-'); + out.append(UnknownIfNULL(gnutls_cipher_get_name(gnutls_cipher_get(sess)))).push_back('-'); + out.append(UnknownIfNULL(gnutls_mac_get_name(gnutls_mac_get(sess)))); + } + + bool GetServerName(std::string& out) const CXX11_OVERRIDE + { + std::vector<char> nameBuffer; + size_t nameLength = 0; + unsigned int nameType = GNUTLS_NAME_DNS; + + // First, determine the size of the hostname. + if (gnutls_server_name_get(sess, &nameBuffer[0], &nameLength, &nameType, 0) != GNUTLS_E_SHORT_MEMORY_BUFFER) + return false; + + // Then retrieve the hostname. + nameBuffer.resize(nameLength); + if (gnutls_server_name_get(sess, &nameBuffer[0], &nameLength, &nameType, 0) != GNUTLS_E_SUCCESS) + return false; + + out.append(&nameBuffer[0]); + return true; + } + + GnuTLS::Profile& GetProfile(); + bool IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); } +}; + +int GnuTLS::X509Credentials::cert_callback(gnutls_session_t sess, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st) +{ +#ifndef GNUTLS_NEW_CERT_CALLBACK_API + st->type = GNUTLS_CRT_X509; +#else + st->cert_type = GNUTLS_CRT_X509; + st->key_type = GNUTLS_PRIVKEY_X509; +#endif + StreamSocket* sock = reinterpret_cast<StreamSocket*>(gnutls_transport_get_ptr(sess)); + GnuTLS::X509Credentials& cred = static_cast<GnuTLSIOHook*>(sock->GetModHook(thismod))->GetProfile().GetX509Credentials(); + + st->ncerts = cred.certs.size(); + st->cert.x509 = cred.certs.raw(); + st->key.x509 = cred.key.get(); + st->deinit_all = 0; + + return 0; +} + +class GnuTLSIOHookProvider : public IOHookProvider +{ + GnuTLS::Profile profile; + + public: + GnuTLSIOHookProvider(Module* mod, GnuTLS::Profile::Config& config) + : IOHookProvider(mod, "ssl/" + config.name, IOHookProvider::IOH_SSL) + , profile(config) + { + ServerInstance->Modules->AddService(*this); + } + + ~GnuTLSIOHookProvider() + { + ServerInstance->Modules->DelService(*this); + } + + void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE + { + new GnuTLSIOHook(this, sock, GNUTLS_SERVER); + } + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE { - if (starttls.enabled) - capHandler.HandleEvent(ev); + new GnuTLSIOHook(this, sock, GNUTLS_CLIENT); + } + + GnuTLS::Profile& GetProfile() { return profile; } +}; + +GnuTLS::Profile& GnuTLSIOHook::GetProfile() +{ + IOHookProvider* hookprov = prov; + return static_cast<GnuTLSIOHookProvider*>(hookprov)->GetProfile(); +} + +class ModuleSSLGnuTLS : public Module +{ + typedef std::vector<reference<GnuTLSIOHookProvider> > ProfileList; + + // First member of the class, gets constructed first and destructed last + GnuTLS::Init libinit; + ProfileList profiles; + + void ReadProfiles() + { + // First, store all profiles in a new, temporary container. If no problems occur, swap the two + // containers; this way if something goes wrong we can go back and continue using the current profiles, + // avoiding unpleasant situations where no new SSL connections are possible. + ProfileList newprofiles; + + ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile"); + if (tags.first == tags.second) + { + // No <sslprofile> tags found, create a profile named "gnutls" from settings in the <gnutls> block + const std::string defname = "gnutls"; + ConfigTag* tag = ServerInstance->Config->ConfValue(defname); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found; using settings from the <gnutls> tag"); + + try + { + GnuTLS::Profile::Config profileconfig(defname, tag); + newprofiles.push_back(new GnuTLSIOHookProvider(this, profileconfig)); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason()); + } + } + + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + if (!stdalgo::string::equalsci(tag->getString("provider"), "gnutls")) + continue; + + std::string name = tag->getString("name"); + if (name.empty()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation()); + continue; + } + + reference<GnuTLSIOHookProvider> prov; + try + { + GnuTLS::Profile::Config profileconfig(name, tag); + prov = new GnuTLSIOHookProvider(this, profileconfig); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); + } + + newprofiles.push_back(prov); + } + + // New profiles are ok, begin using them + // Old profiles are deleted when their refcount drops to zero + for (ProfileList::iterator i = profiles.begin(); i != profiles.end(); ++i) + { + GnuTLSIOHookProvider& prov = **i; + ServerInstance->Modules.DelService(prov); + } + + profiles.swap(newprofiles); + } + + public: + ModuleSSLGnuTLS() + { +#ifndef GNUTLS_HAS_RND + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif + thismod = this; + } + + void init() CXX11_OVERRIDE + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "GnuTLS lib version %s module was compiled for " GNUTLS_VERSION, gnutls_check_version(NULL)); + ReadProfiles(); + ServerInstance->GenRandom = RandGen::Call; + } + + void OnModuleRehash(User* user, const std::string ¶m) CXX11_OVERRIDE + { + if(param != "ssl") + return; + + try + { + ReadProfiles(); + } + catch (ModuleException& ex) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings."); + } + } + + ~ModuleSSLGnuTLS() + { + ServerInstance->GenRandom = &InspIRCd::DefaultGenRandom; + } + + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE + { + if (type == ExtensionItem::EXT_USER) + { + LocalUser* user = IS_LOCAL(static_cast<User*>(item)); + + if ((user) && (user->eh.GetModHook(this))) + { + // User is using SSL, they're a local user, and they're using one of *our* SSL ports. + // Potentially there could be multiple SSL modules loaded at once on different ports. + ServerInstance->Users->QuitUser(user, "SSL module unloading"); + } + } + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides SSL support for clients", VF_VENDOR); } - ModResult OnCheckReady(LocalUser* user) + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { - if ((user->eh.GetIOHook() == this) && (sessions[user->eh.GetFd()].status != ISSL_HANDSHAKEN)) + const GnuTLSIOHook* const iohook = static_cast<GnuTLSIOHook*>(user->eh.GetModHook(this)); + if ((iohook) && (!iohook->IsHandshakeDone())) return MOD_RES_DENY; return MOD_RES_PASSTHRU; } diff --git a/src/modules/extra/m_ssl_mbedtls.cpp b/src/modules/extra/m_ssl_mbedtls.cpp new file mode 100644 index 000000000..75b25fbc4 --- /dev/null +++ b/src/modules/extra/m_ssl_mbedtls.cpp @@ -0,0 +1,969 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/// $LinkerFlags: -lmbedtls + +/// $PackageInfo: require_system("darwin") mbedtls +/// $PackageInfo: require_system("debian" "9.0") libmbedtls-dev +/// $PackageInfo: require_system("ubuntu" "16.04") libmbedtls-dev + + +#include "inspircd.h" +#include "modules/ssl.h" + +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/dhm.h> +#include <mbedtls/ecp.h> +#include <mbedtls/entropy.h> +#include <mbedtls/error.h> +#include <mbedtls/md.h> +#include <mbedtls/pk.h> +#include <mbedtls/ssl.h> +#include <mbedtls/ssl_ciphersuites.h> +#include <mbedtls/version.h> +#include <mbedtls/x509.h> +#include <mbedtls/x509_crt.h> +#include <mbedtls/x509_crl.h> + +#ifdef INSPIRCD_MBEDTLS_LIBRARY_DEBUG +#include <mbedtls/debug.h> +#endif + +namespace mbedTLS +{ + class Exception : public ModuleException + { + public: + Exception(const std::string& reason) + : ModuleException(reason) { } + }; + + std::string ErrorToString(int errcode) + { + char buf[256]; + mbedtls_strerror(errcode, buf, sizeof(buf)); + return buf; + } + + void ThrowOnError(int errcode, const char* msg) + { + if (errcode != 0) + { + std::string reason = msg; + reason.append(" :").append(ErrorToString(errcode)); + throw Exception(reason); + } + } + + template <typename T, void (*init)(T*), void (*deinit)(T*)> + class RAIIObj + { + T obj; + + public: + RAIIObj() + { + init(&obj); + } + + ~RAIIObj() + { + deinit(&obj); + } + + T* get() { return &obj; } + const T* get() const { return &obj; } + }; + + typedef RAIIObj<mbedtls_entropy_context, mbedtls_entropy_init, mbedtls_entropy_free> Entropy; + + class CTRDRBG : private RAIIObj<mbedtls_ctr_drbg_context, mbedtls_ctr_drbg_init, mbedtls_ctr_drbg_free> + { + public: + bool Seed(Entropy& entropy) + { + return (mbedtls_ctr_drbg_seed(get(), mbedtls_entropy_func, entropy.get(), NULL, 0) == 0); + } + + void SetupConf(mbedtls_ssl_config* conf) + { + mbedtls_ssl_conf_rng(conf, mbedtls_ctr_drbg_random, get()); + } + }; + + class DHParams : public RAIIObj<mbedtls_dhm_context, mbedtls_dhm_init, mbedtls_dhm_free> + { + public: + void set(const std::string& dhstr) + { + // Last parameter is buffer size, must include the terminating null + int ret = mbedtls_dhm_parse_dhm(get(), reinterpret_cast<const unsigned char*>(dhstr.c_str()), dhstr.size()+1); + ThrowOnError(ret, "Unable to import DH params"); + } + }; + + class X509Key : public RAIIObj<mbedtls_pk_context, mbedtls_pk_init, mbedtls_pk_free> + { + public: + /** Import */ + X509Key(const std::string& keystr) + { + int ret = mbedtls_pk_parse_key(get(), reinterpret_cast<const unsigned char*>(keystr.c_str()), keystr.size()+1, NULL, 0); + ThrowOnError(ret, "Unable to import private key"); + } + }; + + class Ciphersuites + { + std::vector<int> list; + + public: + Ciphersuites(const std::string& str) + { + // mbedTLS uses the ciphersuite format "TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256" internally. + // This is a bit verbose, so we make life a bit simpler for admins by not requiring them to supply the static parts. + irc::sepstream ss(str, ':'); + for (std::string token; ss.GetToken(token); ) + { + // Prepend "TLS-" if not there + if (token.compare(0, 4, "TLS-", 4)) + token.insert(0, "TLS-"); + + const int id = mbedtls_ssl_get_ciphersuite_id(token.c_str()); + if (!id) + throw Exception("Unknown ciphersuite " + token); + list.push_back(id); + } + list.push_back(0); + } + + const int* get() const { return &list.front(); } + bool empty() const { return (list.size() <= 1); } + }; + + class Curves + { + std::vector<mbedtls_ecp_group_id> list; + + public: + Curves(const std::string& str) + { + irc::sepstream ss(str, ':'); + for (std::string token; ss.GetToken(token); ) + { + const mbedtls_ecp_curve_info* curve = mbedtls_ecp_curve_info_from_name(token.c_str()); + if (!curve) + throw Exception("Unknown curve " + token); + list.push_back(curve->grp_id); + } + list.push_back(MBEDTLS_ECP_DP_NONE); + } + + const mbedtls_ecp_group_id* get() const { return &list.front(); } + bool empty() const { return (list.size() <= 1); } + }; + + class X509CertList : public RAIIObj<mbedtls_x509_crt, mbedtls_x509_crt_init, mbedtls_x509_crt_free> + { + public: + /** Import or create empty */ + X509CertList(const std::string& certstr, bool allowempty = false) + { + if ((allowempty) && (certstr.empty())) + return; + int ret = mbedtls_x509_crt_parse(get(), reinterpret_cast<const unsigned char*>(certstr.c_str()), certstr.size()+1); + ThrowOnError(ret, "Unable to load certificates"); + } + + bool empty() const { return (get()->raw.p != NULL); } + }; + + class X509CRL : public RAIIObj<mbedtls_x509_crl, mbedtls_x509_crl_init, mbedtls_x509_crl_free> + { + public: + X509CRL(const std::string& crlstr) + { + if (crlstr.empty()) + return; + int ret = mbedtls_x509_crl_parse(get(), reinterpret_cast<const unsigned char*>(crlstr.c_str()), crlstr.size()+1); + ThrowOnError(ret, "Unable to load CRL"); + } + }; + + class X509Credentials + { + /** Private key + */ + X509Key key; + + /** Certificate list, presented to the peer + */ + X509CertList certs; + + public: + X509Credentials(const std::string& certstr, const std::string& keystr) + : key(keystr) + , certs(certstr) + { + // Verify that one of the certs match the private key + bool found = false; + for (mbedtls_x509_crt* cert = certs.get(); cert; cert = cert->next) + { + if (mbedtls_pk_check_pair(&cert->pk, key.get()) == 0) + { + found = true; + break; + } + } + if (!found) + throw Exception("Public/private key pair does not match"); + } + + mbedtls_pk_context* getkey() { return key.get(); } + mbedtls_x509_crt* getcerts() { return certs.get(); } + }; + + class Context + { + mbedtls_ssl_config conf; + +#ifdef INSPIRCD_MBEDTLS_LIBRARY_DEBUG + static void DebugLogFunc(void* userptr, int level, const char* file, int line, const char* msg) + { + // Remove trailing \n + size_t len = strlen(msg); + if ((len > 0) && (msg[len-1] == '\n')) + len--; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "%s:%d %.*s", file, line, len, msg); + } +#endif + + public: + Context(CTRDRBG& ctrdrbg, unsigned int endpoint) + { + mbedtls_ssl_config_init(&conf); +#ifdef INSPIRCD_MBEDTLS_LIBRARY_DEBUG + mbedtls_debug_set_threshold(INT_MAX); + mbedtls_ssl_conf_dbg(&conf, DebugLogFunc, NULL); +#endif + + // TODO: check ret of mbedtls_ssl_config_defaults + mbedtls_ssl_config_defaults(&conf, endpoint, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + ctrdrbg.SetupConf(&conf); + } + + ~Context() + { + mbedtls_ssl_config_free(&conf); + } + + void SetMinDHBits(unsigned int mindh) + { + mbedtls_ssl_conf_dhm_min_bitlen(&conf, mindh); + } + + void SetDHParams(DHParams& dh) + { + mbedtls_ssl_conf_dh_param_ctx(&conf, dh.get()); + } + + void SetX509CertAndKey(X509Credentials& x509cred) + { + mbedtls_ssl_conf_own_cert(&conf, x509cred.getcerts(), x509cred.getkey()); + } + + void SetCiphersuites(const Ciphersuites& ciphersuites) + { + mbedtls_ssl_conf_ciphersuites(&conf, ciphersuites.get()); + } + + void SetCurves(const Curves& curves) + { + mbedtls_ssl_conf_curves(&conf, curves.get()); + } + + void SetVersion(int minver, int maxver) + { + // SSL v3 support cannot be enabled + if (minver) + mbedtls_ssl_conf_min_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, minver); + if (maxver) + mbedtls_ssl_conf_max_version(&conf, MBEDTLS_SSL_MAJOR_VERSION_3, maxver); + } + + void SetCA(X509CertList& certs, X509CRL& crl) + { + mbedtls_ssl_conf_ca_chain(&conf, certs.get(), crl.get()); + } + + void SetOptionalVerifyCert() + { + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + } + + const mbedtls_ssl_config* GetConf() const { return &conf; } + }; + + class Hash + { + const mbedtls_md_info_t* md; + + /** Buffer where cert hashes are written temporarily + */ + mutable std::vector<unsigned char> buf; + + public: + Hash(std::string hashstr) + { + std::transform(hashstr.begin(), hashstr.end(), hashstr.begin(), ::toupper); + md = mbedtls_md_info_from_string(hashstr.c_str()); + if (!md) + throw Exception("Unknown hash: " + hashstr); + + buf.resize(mbedtls_md_get_size(md)); + } + + std::string hash(const unsigned char* input, size_t length) const + { + mbedtls_md(md, input, length, &buf.front()); + return BinToHex(&buf.front(), buf.size()); + } + }; + + class Profile + { + /** Name of this profile + */ + const std::string name; + + X509Credentials x509cred; + + /** Ciphersuites to use + */ + Ciphersuites ciphersuites; + + /** Curves accepted for use in ECDHE and in the peer's end-entity certificate + */ + Curves curves; + + Context serverctx; + Context clientctx; + + DHParams dhparams; + + X509CertList cacerts; + + X509CRL crl; + + /** Hashing algorithm to use when generating certificate fingerprints + */ + Hash hash; + + /** Rough max size of records to send + */ + const unsigned int outrecsize; + + public: + struct Config + { + const std::string name; + + CTRDRBG& ctrdrbg; + + const std::string certstr; + const std::string keystr; + const std::string dhstr; + + const std::string ciphersuitestr; + const std::string curvestr; + const unsigned int mindh; + const std::string hashstr; + + std::string crlstr; + std::string castr; + + const int minver; + const int maxver; + const unsigned int outrecsize; + const bool requestclientcert; + + Config(const std::string& profilename, ConfigTag* tag, CTRDRBG& ctr_drbg) + : name(profilename) + , ctrdrbg(ctr_drbg) + , certstr(ReadFile(tag->getString("certfile", "cert.pem"))) + , keystr(ReadFile(tag->getString("keyfile", "key.pem"))) + , dhstr(ReadFile(tag->getString("dhfile", "dhparams.pem"))) + , ciphersuitestr(tag->getString("ciphersuites")) + , curvestr(tag->getString("curves")) + , mindh(tag->getUInt("mindhbits", 2048)) + , hashstr(tag->getString("hash", "sha256")) + , castr(tag->getString("cafile")) + , minver(tag->getUInt("minver", 0)) + , maxver(tag->getUInt("maxver", 0)) + , outrecsize(tag->getUInt("outrecsize", 2048, 512, 16384)) + , requestclientcert(tag->getBool("requestclientcert", true)) + { + if (!castr.empty()) + { + castr = ReadFile(castr); + crlstr = tag->getString("crlfile"); + if (!crlstr.empty()) + crlstr = ReadFile(crlstr); + } + } + }; + + Profile(Config& config) + : name(config.name) + , x509cred(config.certstr, config.keystr) + , ciphersuites(config.ciphersuitestr) + , curves(config.curvestr) + , serverctx(config.ctrdrbg, MBEDTLS_SSL_IS_SERVER) + , clientctx(config.ctrdrbg, MBEDTLS_SSL_IS_CLIENT) + , cacerts(config.castr, true) + , crl(config.crlstr) + , hash(config.hashstr) + , outrecsize(config.outrecsize) + { + serverctx.SetX509CertAndKey(x509cred); + clientctx.SetX509CertAndKey(x509cred); + clientctx.SetMinDHBits(config.mindh); + + if (!ciphersuites.empty()) + { + serverctx.SetCiphersuites(ciphersuites); + clientctx.SetCiphersuites(ciphersuites); + } + + if (!curves.empty()) + { + serverctx.SetCurves(curves); + clientctx.SetCurves(curves); + } + + serverctx.SetVersion(config.minver, config.maxver); + clientctx.SetVersion(config.minver, config.maxver); + + if (!config.dhstr.empty()) + { + dhparams.set(config.dhstr); + serverctx.SetDHParams(dhparams); + } + + clientctx.SetOptionalVerifyCert(); + clientctx.SetCA(cacerts, crl); + // The default for servers is to not request a client certificate from the peer + if (config.requestclientcert) + { + serverctx.SetOptionalVerifyCert(); + serverctx.SetCA(cacerts, crl); + } + } + + static std::string ReadFile(const std::string& filename) + { + FileReader reader(filename); + std::string ret = reader.GetString(); + if (ret.empty()) + throw Exception("Cannot read file " + filename); + return ret; + } + + /** Set up the given session with the settings in this profile + */ + void SetupClientSession(mbedtls_ssl_context* sess) + { + mbedtls_ssl_setup(sess, clientctx.GetConf()); + } + + void SetupServerSession(mbedtls_ssl_context* sess) + { + mbedtls_ssl_setup(sess, serverctx.GetConf()); + } + + const std::string& GetName() const { return name; } + X509Credentials& GetX509Credentials() { return x509cred; } + unsigned int GetOutgoingRecordSize() const { return outrecsize; } + const Hash& GetHash() const { return hash; } + }; +} + +class mbedTLSIOHook : public SSLIOHook +{ + enum Status + { + ISSL_NONE, + ISSL_HANDSHAKING, + ISSL_HANDSHAKEN + }; + + mbedtls_ssl_context sess; + Status status; + + void CloseSession() + { + if (status == ISSL_NONE) + return; + + mbedtls_ssl_close_notify(&sess); + mbedtls_ssl_free(&sess); + certificate = NULL; + status = ISSL_NONE; + } + + // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed + int Handshake(StreamSocket* sock) + { + int ret = mbedtls_ssl_handshake(&sess); + if (ret == 0) + { + // Change the seesion state + this->status = ISSL_HANDSHAKEN; + + VerifyCertificate(); + + // Finish writing, if any left + SocketEngine::ChangeEventMask(sock, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); + + return 1; + } + + this->status = ISSL_HANDSHAKING; + if (ret == MBEDTLS_ERR_SSL_WANT_READ) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + return 0; + } + else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); + return 0; + } + + sock->SetError("Handshake Failed - " + mbedTLS::ErrorToString(ret)); + CloseSession(); + return -1; + } + + // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error + int PrepareIO(StreamSocket* sock) + { + if (status == ISSL_HANDSHAKEN) + return 1; + else if (status == ISSL_HANDSHAKING) + { + // The handshake isn't finished, try to finish it + return Handshake(sock); + } + + CloseSession(); + sock->SetError("No SSL session"); + return -1; + } + + void VerifyCertificate() + { + this->certificate = new ssl_cert; + const mbedtls_x509_crt* const cert = mbedtls_ssl_get_peer_cert(&sess); + if (!cert) + { + certificate->error = "No client certificate sent"; + return; + } + + // If there is a certificate we can always generate a fingerprint + certificate->fingerprint = GetProfile().GetHash().hash(cert->raw.p, cert->raw.len); + + // At this point mbedTLS verified the cert already, we just need to check the results + const uint32_t flags = mbedtls_ssl_get_verify_result(&sess); + if (flags == 0xFFFFFFFF) + { + certificate->error = "Internal error during verification"; + return; + } + + if (flags == 0) + { + // Verification succeeded + certificate->trusted = true; + } + else + { + // Verification failed + certificate->trusted = false; + if ((flags & MBEDTLS_X509_BADCERT_EXPIRED) || (flags & MBEDTLS_X509_BADCERT_FUTURE)) + certificate->error = "Not activated, or expired certificate"; + } + + certificate->unknownsigner = (flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED); + certificate->revoked = (flags & MBEDTLS_X509_BADCERT_REVOKED); + certificate->invalid = ((flags & MBEDTLS_X509_BADCERT_BAD_KEY) || (flags & MBEDTLS_X509_BADCERT_BAD_MD) || (flags & MBEDTLS_X509_BADCERT_BAD_PK)); + + GetDNString(&cert->subject, certificate->dn); + GetDNString(&cert->issuer, certificate->issuer); + } + + static void GetDNString(const mbedtls_x509_name* x509name, std::string& out) + { + char buf[512]; + const int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), x509name); + if (ret <= 0) + return; + + out.assign(buf, ret); + } + + static int Pull(void* userptr, unsigned char* buffer, size_t size) + { + StreamSocket* const sock = reinterpret_cast<StreamSocket*>(userptr); + if (sock->GetEventMask() & FD_READ_WILL_BLOCK) + return MBEDTLS_ERR_SSL_WANT_READ; + + const int ret = SocketEngine::Recv(sock, reinterpret_cast<char*>(buffer), size, 0); + if (ret < (int)size) + { + SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK); + if ((ret == -1) && (SocketEngine::IgnoreError())) + return MBEDTLS_ERR_SSL_WANT_READ; + } + return ret; + } + + static int Push(void* userptr, const unsigned char* buffer, size_t size) + { + StreamSocket* const sock = reinterpret_cast<StreamSocket*>(userptr); + if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK) + return MBEDTLS_ERR_SSL_WANT_WRITE; + + const int ret = SocketEngine::Send(sock, buffer, size, 0); + if (ret < (int)size) + { + SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK); + if ((ret == -1) && (SocketEngine::IgnoreError())) + return MBEDTLS_ERR_SSL_WANT_WRITE; + } + return ret; + } + + public: + mbedTLSIOHook(IOHookProvider* hookprov, StreamSocket* sock, bool isserver) + : SSLIOHook(hookprov) + , status(ISSL_NONE) + { + mbedtls_ssl_init(&sess); + if (isserver) + GetProfile().SetupServerSession(&sess); + else + GetProfile().SetupClientSession(&sess); + + mbedtls_ssl_set_bio(&sess, reinterpret_cast<void*>(sock), Push, Pull, NULL); + + sock->AddIOHook(this); + Handshake(sock); + } + + void OnStreamSocketClose(StreamSocket* sock) CXX11_OVERRIDE + { + CloseSession(); + } + + int OnStreamSocketRead(StreamSocket* sock, std::string& recvq) CXX11_OVERRIDE + { + // Finish handshake if needed + int prepret = PrepareIO(sock); + if (prepret <= 0) + return prepret; + + // If we resumed the handshake then this->status will be ISSL_HANDSHAKEN. + char* const readbuf = ServerInstance->GetReadBuffer(); + const size_t readbufsize = ServerInstance->Config->NetBufferSize; + int ret = mbedtls_ssl_read(&sess, reinterpret_cast<unsigned char*>(readbuf), readbufsize); + if (ret > 0) + { + recvq.append(readbuf, ret); + + // Schedule a read if there is still data in the mbedTLS buffer + if (mbedtls_ssl_get_bytes_avail(&sess) > 0) + SocketEngine::ChangeEventMask(sock, FD_ADD_TRIAL_READ); + return 1; + } + else if (ret == MBEDTLS_ERR_SSL_WANT_READ) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_POLL_READ); + return 0; + } + else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); + return 0; + } + else if (ret == 0) + { + sock->SetError("Connection closed"); + CloseSession(); + return -1; + } + else // error or MBEDTLS_ERR_SSL_CLIENT_RECONNECT which we treat as an error + { + sock->SetError(mbedTLS::ErrorToString(ret)); + CloseSession(); + return -1; + } + } + + int OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE + { + // Finish handshake if needed + int prepret = PrepareIO(sock); + if (prepret <= 0) + return prepret; + + // Session is ready for transferring application data + while (!sendq.empty()) + { + FlattenSendQueue(sendq, GetProfile().GetOutgoingRecordSize()); + const StreamSocket::SendQueue::Element& buffer = sendq.front(); + int ret = mbedtls_ssl_write(&sess, reinterpret_cast<const unsigned char*>(buffer.data()), buffer.length()); + if (ret == (int)buffer.length()) + { + // Wrote entire record, continue sending + sendq.pop_front(); + } + else if (ret > 0) + { + sendq.erase_front(ret); + SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE); + return 0; + } + else if (ret == 0) + { + sock->SetError("Connection closed"); + CloseSession(); + return -1; + } + else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_SINGLE_WRITE); + return 0; + } + else if (ret == MBEDTLS_ERR_SSL_WANT_READ) + { + SocketEngine::ChangeEventMask(sock, FD_WANT_POLL_READ); + return 0; + } + else + { + sock->SetError(mbedTLS::ErrorToString(ret)); + CloseSession(); + return -1; + } + } + + SocketEngine::ChangeEventMask(sock, FD_WANT_NO_WRITE); + return 1; + } + + void GetCiphersuite(std::string& out) const CXX11_OVERRIDE + { + if (!IsHandshakeDone()) + return; + out.append(mbedtls_ssl_get_version(&sess)).push_back('-'); + + // All mbedTLS ciphersuite names currently begin with "TLS-" which provides no useful information so skip it, but be prepared if it changes + const char* const ciphersuitestr = mbedtls_ssl_get_ciphersuite(&sess); + const char prefix[] = "TLS-"; + unsigned int skip = sizeof(prefix)-1; + if (strncmp(ciphersuitestr, prefix, sizeof(prefix)-1)) + skip = 0; + out.append(ciphersuitestr + skip); + } + + bool GetServerName(std::string& out) const CXX11_OVERRIDE + { + // TODO: Implement SNI support. + return false; + } + + mbedTLS::Profile& GetProfile(); + bool IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); } +}; + +class mbedTLSIOHookProvider : public IOHookProvider +{ + mbedTLS::Profile profile; + + public: + mbedTLSIOHookProvider(Module* mod, mbedTLS::Profile::Config& config) + : IOHookProvider(mod, "ssl/" + config.name, IOHookProvider::IOH_SSL) + , profile(config) + { + ServerInstance->Modules->AddService(*this); + } + + ~mbedTLSIOHookProvider() + { + ServerInstance->Modules->DelService(*this); + } + + void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE + { + new mbedTLSIOHook(this, sock, true); + } + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + new mbedTLSIOHook(this, sock, false); + } + + mbedTLS::Profile& GetProfile() { return profile; } +}; + +mbedTLS::Profile& mbedTLSIOHook::GetProfile() +{ + IOHookProvider* hookprov = prov; + return static_cast<mbedTLSIOHookProvider*>(hookprov)->GetProfile(); +} + +class ModuleSSLmbedTLS : public Module +{ + typedef std::vector<reference<mbedTLSIOHookProvider> > ProfileList; + + mbedTLS::Entropy entropy; + mbedTLS::CTRDRBG ctr_drbg; + ProfileList profiles; + + void ReadProfiles() + { + // First, store all profiles in a new, temporary container. If no problems occur, swap the two + // containers; this way if something goes wrong we can go back and continue using the current profiles, + // avoiding unpleasant situations where no new SSL connections are possible. + ProfileList newprofiles; + + ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile"); + if (tags.first == tags.second) + { + // No <sslprofile> tags found, create a profile named "mbedtls" from settings in the <mbedtls> block + const std::string defname = "mbedtls"; + ConfigTag* tag = ServerInstance->Config->ConfValue(defname); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found; using settings from the <mbedtls> tag"); + + try + { + mbedTLS::Profile::Config profileconfig(defname, tag, ctr_drbg); + newprofiles.push_back(new mbedTLSIOHookProvider(this, profileconfig)); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason()); + } + } + + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + if (!stdalgo::string::equalsci(tag->getString("provider"), "mbedtls")) + continue; + + std::string name = tag->getString("name"); + if (name.empty()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation()); + continue; + } + + reference<mbedTLSIOHookProvider> prov; + try + { + mbedTLS::Profile::Config profileconfig(name, tag, ctr_drbg); + prov = new mbedTLSIOHookProvider(this, profileconfig); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); + } + + newprofiles.push_back(prov); + } + + // New profiles are ok, begin using them + // Old profiles are deleted when their refcount drops to zero + for (ProfileList::iterator i = profiles.begin(); i != profiles.end(); ++i) + { + mbedTLSIOHookProvider& prov = **i; + ServerInstance->Modules.DelService(prov); + } + + profiles.swap(newprofiles); + } + + public: + void init() CXX11_OVERRIDE + { + char verbuf[16]; // Should be at least 9 bytes in size + mbedtls_version_get_string(verbuf); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "mbedTLS lib version %s module was compiled for " MBEDTLS_VERSION_STRING, verbuf); + + if (!ctr_drbg.Seed(entropy)) + throw ModuleException("CTR DRBG seed failed"); + ReadProfiles(); + } + + void OnModuleRehash(User* user, const std::string ¶m) CXX11_OVERRIDE + { + if (param != "ssl") + return; + + try + { + ReadProfiles(); + } + catch (ModuleException& ex) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings."); + } + } + + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE + { + if (type != ExtensionItem::EXT_USER) + return; + + LocalUser* user = IS_LOCAL(static_cast<User*>(item)); + if ((user) && (user->eh.GetModHook(this))) + { + // User is using SSL, they're a local user, and they're using our IOHook. + // Potentially there could be multiple SSL modules loaded at once on different ports. + ServerInstance->Users.QuitUser(user, "SSL module unloading"); + } + } + + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE + { + const mbedTLSIOHook* const iohook = static_cast<mbedTLSIOHook*>(user->eh.GetModHook(this)); + if ((iohook) && (!iohook->IsHandshakeDone())) + return MOD_RES_DENY; + return MOD_RES_PASSTHRU; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides SSL support via mbedTLS (PolarSSL)", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleSSLmbedTLS) diff --git a/src/modules/extra/m_ssl_openssl.cpp b/src/modules/extra/m_ssl_openssl.cpp index f2189f257..5f61c71a9 100644 --- a/src/modules/extra/m_ssl_openssl.cpp +++ b/src/modules/extra/m_ssl_openssl.cpp @@ -21,852 +21,1050 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - /* HACK: This prevents OpenSSL on OS X 10.7 and later from spewing deprecation - * warnings for every single function call. As far as I (SaberUK) know, Apple - * have no plans to remove OpenSSL so this warning just causes needless spam. - */ -#ifdef __APPLE__ -# define __AVAILABILITYMACROS__ -# define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER -#endif - +/// $CompilerFlags: find_compiler_flags("openssl") +/// $LinkerFlags: find_linker_flags("openssl" "-lssl -lcrypto") + +/// $PackageInfo: require_system("centos") openssl-devel pkgconfig +/// $PackageInfo: require_system("darwin") openssl pkg-config +/// $PackageInfo: require_system("debian") libssl-dev openssl pkg-config +/// $PackageInfo: require_system("ubuntu") libssl-dev openssl pkg-config + + #include "inspircd.h" +#include "iohook.h" +#include "modules/ssl.h" + +// Ignore OpenSSL deprecation warnings on OS X Lion and newer. +#if defined __APPLE__ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +// Fix warnings about the use of `long long` on C++03. +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-long-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wlong-long" +#endif + #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/dh.h> -#include "ssl.h" #ifdef _WIN32 # pragma comment(lib, "ssleay32.lib") # pragma comment(lib, "libeay32.lib") -# undef MAX_DESCRIPTORS -# define MAX_DESCRIPTORS 10000 #endif // Compatibility layer to allow OpenSSL 1.0 to use the 1.1 API. #if ((defined LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x10100000L)) + +// BIO is opaque in OpenSSL 1.1 but the access API does not exist in 1.0. +# define BIO_get_data(BIO) BIO->ptr +# define BIO_set_data(BIO, VALUE) BIO->ptr = VALUE; +# define BIO_set_init(BIO, VALUE) BIO->init = VALUE; + +// These functions have been renamed in OpenSSL 1.1. +# define OpenSSL_version SSLeay_version # define X509_getm_notAfter X509_get_notAfter # define X509_getm_notBefore X509_get_notBefore # define OPENSSL_init_ssl(OPTIONS, SETTINGS) \ SSL_library_init(); \ SSL_load_error_strings(); -#endif -/* $ModDesc: Provides SSL support for clients */ +// These macros have been renamed in OpenSSL 1.1. +# define OPENSSL_VERSION SSLEAY_VERSION -/* $LinkerFlags: if("USE_FREEBSD_BASE_SSL") -lssl -lcrypto */ -/* $CompileFlags: if(!"USE_FREEBSD_BASE_SSL") pkgconfversion("openssl","0.9.7") pkgconfincludes("openssl","/openssl/ssl.h","") */ -/* $LinkerFlags: if(!"USE_FREEBSD_BASE_SSL") rpath("pkg-config --libs openssl") pkgconflibs("openssl","/libssl.so","-lssl -lcrypto -ldl") */ - -/* $NoPedantic */ - - -class ModuleSSLOpenSSL; +#else +# define INSPIRCD_OPENSSL_OPAQUE_BIO +#endif enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_OPEN }; static bool SelfSigned = false; - -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION -static ModuleSSLOpenSSL* opensslmod = NULL; -#endif +static int exdataindex; char* get_error() { return ERR_error_string(ERR_get_error(), NULL); } -static int error_callback(const char *str, size_t len, void *u); +static int OnVerify(int preverify_ok, X509_STORE_CTX* ctx); +static void StaticSSLInfoCallback(const SSL* ssl, int where, int rc); -/** Represents an SSL user's extra data - */ -class issl_session +namespace OpenSSL { -public: - SSL* sess; - issl_status status; - reference<ssl_cert> cert; - - bool outbound; - bool data_to_write; - - issl_session() - : sess(NULL) - , status(ISSL_NONE) + class Exception : public ModuleException { - outbound = false; - data_to_write = false; - } -}; - -static int OnVerify(int preverify_ok, X509_STORE_CTX *ctx) -{ - /* XXX: This will allow self signed certificates. - * In the future if we want an option to not allow this, - * we can just return preverify_ok here, and openssl - * will boot off self-signed and invalid peer certs. - */ - int ve = X509_STORE_CTX_get_error(ctx); - - SelfSigned = (ve == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + public: + Exception(const std::string& reason) + : ModuleException(reason) { } + }; - return 1; -} + class DHParams + { + DH* dh; -class ModuleSSLOpenSSL : public Module -{ - issl_session* sessions; + public: + DHParams(const std::string& filename) + { + BIO* dhpfile = BIO_new_file(filename.c_str(), "r"); + if (dhpfile == NULL) + throw Exception("Couldn't open DH file " + filename); - SSL_CTX* ctx; - SSL_CTX* clictx; + dh = PEM_read_bio_DHparams(dhpfile, NULL, NULL, NULL); + BIO_free(dhpfile); - long ctx_options; - long clictx_options; + if (!dh) + throw Exception("Couldn't read DH params from file " + filename); + } - std::string sslports; - bool use_sha; + ~DHParams() + { + DH_free(dh); + } - ServiceProvider iohook; + DH* get() + { + return dh; + } + }; - static void SetContextOptions(SSL_CTX* ctx, long defoptions, const std::string& ctxname, ConfigTag* tag) + class Context { - long setoptions = tag->getInt(ctxname + "setoptions"); - // User-friendly config options for setting context options -#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE - if (tag->getBool("cipherserverpref")) - setoptions |= SSL_OP_CIPHER_SERVER_PREFERENCE; + SSL_CTX* const ctx; + long ctx_options; + + public: + Context(SSL_CTX* context) + : ctx(context) + { + // Sane default options for OpenSSL see https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html + // and when choosing a cipher, use the server's preferences instead of the client preferences. + long opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE; + // Only turn options on if they exist +#ifdef SSL_OP_SINGLE_ECDH_USE + opts |= SSL_OP_SINGLE_ECDH_USE; #endif -#ifdef SSL_OP_NO_COMPRESSION - if (!tag->getBool("compression", true)) - setoptions |= SSL_OP_NO_COMPRESSION; +#ifdef SSL_OP_NO_TICKET + opts |= SSL_OP_NO_TICKET; #endif - if (!tag->getBool("sslv3", true)) - setoptions |= SSL_OP_NO_SSLv3; - if (!tag->getBool("tlsv1", true)) - setoptions |= SSL_OP_NO_TLSv1; - long clearoptions = tag->getInt(ctxname + "clearoptions"); - ServerInstance->Logs->Log("m_ssl_openssl", DEBUG, "Setting OpenSSL %s context options, default: %ld set: %ld clear: %ld", ctxname.c_str(), defoptions, setoptions, clearoptions); + ctx_options = SSL_CTX_set_options(ctx, opts); - // Clear everything - SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx)); - - // Set the default options and what is in the conf - SSL_CTX_set_options(ctx, defoptions | setoptions); - long final = SSL_CTX_clear_options(ctx, clearoptions); - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "OpenSSL %s context options: %ld", ctxname.c_str(), final); - } - -#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH - void SetupECDH(ConfigTag* tag) - { - std::string curvename = tag->getString("ecdhcurve", "prime256v1"); - if (curvename.empty()) - return; + long mode = SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; +#ifdef SSL_MODE_RELEASE_BUFFERS + mode |= SSL_MODE_RELEASE_BUFFERS; +#endif + SSL_CTX_set_mode(ctx, mode); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_info_callback(ctx, StaticSSLInfoCallback); + } - int nid = OBJ_sn2nid(curvename.c_str()); - if (nid == 0) + ~Context() { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Unknown curve: \"%s\"", curvename.c_str()); - return; + SSL_CTX_free(ctx); } - EC_KEY* eckey = EC_KEY_new_by_curve_name(nid); - if (!eckey) + bool SetDH(DHParams& dh) { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Unable to create EC key object"); - return; + ERR_clear_error(); + return (SSL_CTX_set_tmp_dh(ctx, dh.get()) >= 0); } - ERR_clear_error(); - if (SSL_CTX_set_tmp_ecdh(ctx, eckey) < 0) +#ifndef OPENSSL_NO_ECDH + void SetECDH(const std::string& curvename) { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set ECDH parameters"); - ERR_print_errors_cb(error_callback, this); - } + int nid = OBJ_sn2nid(curvename.c_str()); + if (nid == 0) + throw Exception("Unknown curve: " + curvename); - EC_KEY_free(eckey); - } + EC_KEY* eckey = EC_KEY_new_by_curve_name(nid); + if (!eckey) + throw Exception("Unable to create EC key object"); + + ERR_clear_error(); + bool ret = (SSL_CTX_set_tmp_ecdh(ctx, eckey) >= 0); + EC_KEY_free(eckey); + if (!ret) + throw Exception("Couldn't set ECDH parameters"); + } #endif -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION - static void SSLInfoCallback(const SSL* ssl, int where, int rc) - { - int fd = SSL_get_fd(const_cast<SSL*>(ssl)); - issl_session& session = opensslmod->sessions[fd]; + bool SetCiphers(const std::string& ciphers) + { + ERR_clear_error(); + return SSL_CTX_set_cipher_list(ctx, ciphers.c_str()); + } - if ((where & SSL_CB_HANDSHAKE_START) && (session.status == ISSL_OPEN)) + bool SetCerts(const std::string& filename) { - // The other side is trying to renegotiate, kill the connection and change status - // to ISSL_NONE so CheckRenego() closes the session - session.status = ISSL_NONE; - ServerInstance->SE->Shutdown(fd, 2); + ERR_clear_error(); + return SSL_CTX_use_certificate_chain_file(ctx, filename.c_str()); } - } - bool CheckRenego(StreamSocket* sock, issl_session* session) - { - if (session->status != ISSL_NONE) - return true; + bool SetPrivateKey(const std::string& filename) + { + ERR_clear_error(); + return SSL_CTX_use_PrivateKey_file(ctx, filename.c_str(), SSL_FILETYPE_PEM); + } - ServerInstance->Logs->Log("m_ssl_openssl", DEBUG, "Session %p killed, attempted to renegotiate", (void*)session->sess); - CloseSession(session); - sock->SetError("Renegotiation is not allowed"); - return false; - } -#endif + bool SetCA(const std::string& filename) + { + ERR_clear_error(); + return SSL_CTX_load_verify_locations(ctx, filename.c_str(), 0); + } - public: + void SetCRL(const std::string& crlfile, const std::string& crlpath, const std::string& crlmode) + { + if (crlfile.empty() && crlpath.empty()) + return; - ModuleSSLOpenSSL() : iohook(this, "ssl/openssl", SERVICE_IOHOOK) - { -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION - opensslmod = this; -#endif - sessions = new issl_session[ServerInstance->SE->GetMaxFds()]; + /* Set CRL mode */ + unsigned long crlflags = X509_V_FLAG_CRL_CHECK; + if (stdalgo::string::equalsci(crlmode, "chain")) + { + crlflags |= X509_V_FLAG_CRL_CHECK_ALL; + } + else if (!stdalgo::string::equalsci(crlmode, "leaf")) + { + throw ModuleException("Unknown mode '" + crlmode + "'; expected either 'chain' (default) or 'leaf'"); + } - /* Global SSL library initialization*/ - OPENSSL_init_ssl(0, NULL); + /* Load CRL files */ + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + if (!store) + { + throw ModuleException("Unable to get X509_STORE from SSL context; this should never happen"); + } + ERR_clear_error(); + if (!X509_STORE_load_locations(store, + crlfile.empty() ? NULL : crlfile.c_str(), + crlpath.empty() ? NULL : crlpath.c_str())) + { + int err = ERR_get_error(); + throw ModuleException("Unable to load CRL file '" + crlfile + "' or CRL path '" + crlpath + "': '" + (err ? ERR_error_string(err, NULL) : "unknown") + "'"); + } - /* Build our SSL contexts: - * NOTE: OpenSSL makes us have two contexts, one for servers and one for clients. ICK. - */ - ctx = SSL_CTX_new( SSLv23_server_method() ); - clictx = SSL_CTX_new( SSLv23_client_method() ); + /* Set CRL mode */ + if (X509_STORE_set_flags(store, crlflags) != 1) + { + throw ModuleException("Unable to set X509 CRL flags"); + } + } - SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_mode(clictx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify); - SSL_CTX_set_verify(clictx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify); + long GetDefaultContextOptions() const + { + return ctx_options; + } - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); - SSL_CTX_set_session_cache_mode(clictx, SSL_SESS_CACHE_OFF); + long SetRawContextOptions(long setoptions, long clearoptions) + { + // Clear everything + SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx)); - long opts = SSL_OP_NO_SSLv2 | SSL_OP_SINGLE_DH_USE; - // Only turn options on if they exist -#ifdef SSL_OP_SINGLE_ECDH_USE - opts |= SSL_OP_SINGLE_ECDH_USE; -#endif -#ifdef SSL_OP_NO_TICKET - opts |= SSL_OP_NO_TICKET; -#endif + // Set the default options and what is in the conf + SSL_CTX_set_options(ctx, ctx_options | setoptions); + return SSL_CTX_clear_options(ctx, clearoptions); + } - ctx_options = SSL_CTX_set_options(ctx, opts); - clictx_options = SSL_CTX_set_options(clictx, opts); - } + void SetVerifyCert() + { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify); + } - void init() - { - // Needs the flag as it ignores a plain /rehash - OnModuleRehash(NULL,"ssl"); - Implementation eventlist[] = { I_On005Numeric, I_OnRehash, I_OnModuleRehash, I_OnHookIO, I_OnUserConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - ServerInstance->Modules->AddService(iohook); - } + SSL* CreateServerSession() + { + SSL* sess = SSL_new(ctx); + SSL_set_accept_state(sess); // Act as server + return sess; + } - void OnHookIO(StreamSocket* user, ListenSocket* lsb) - { - if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "openssl") + SSL* CreateClientSession() { - /* Hook the user with our module */ - user->AddIOHook(this); + SSL* sess = SSL_new(ctx); + SSL_set_connect_state(sess); // Act as client + return sess; } - } + }; - void OnRehash(User* user) + class Profile { - sslports.clear(); + /** Name of this profile + */ + const std::string name; + + /** DH parameters in use + */ + DHParams dh; - ConfigTag* Conf = ServerInstance->Config->ConfValue("openssl"); + /** OpenSSL makes us have two contexts, one for servers and one for clients + */ + Context ctx; + Context clictx; + + /** Digest to use when generating fingerprints + */ + const EVP_MD* digest; + + /** Last error, set by error_callback() + */ + std::string lasterr; + + /** True if renegotiations are allowed, false if not + */ + const bool allowrenego; -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION - // Set the callback if we are not allowing renegotiations, unset it if we do - if (Conf->getBool("renegotiation", true)) + /** Rough max size of records to send + */ + const unsigned int outrecsize; + + static int error_callback(const char* str, size_t len, void* u) { - SSL_CTX_set_info_callback(ctx, NULL); - SSL_CTX_set_info_callback(clictx, NULL); + Profile* profile = reinterpret_cast<Profile*>(u); + profile->lasterr = std::string(str, len - 1); + return 0; } - else + + /** Set raw OpenSSL context (SSL_CTX) options from a config tag + * @param ctxname Name of the context, client or server + * @param tag Config tag defining this profile + * @param context Context object to manipulate + */ + void SetContextOptions(const std::string& ctxname, ConfigTag* tag, Context& context) { - SSL_CTX_set_info_callback(ctx, SSLInfoCallback); - SSL_CTX_set_info_callback(clictx, SSLInfoCallback); - } + long setoptions = tag->getInt(ctxname + "setoptions", 0); + long clearoptions = tag->getInt(ctxname + "clearoptions", 0); +#ifdef SSL_OP_NO_COMPRESSION + if (!tag->getBool("compression", false)) // Disable compression by default + setoptions |= SSL_OP_NO_COMPRESSION; #endif + // Disable TLSv1.0 by default. + if (!tag->getBool("tlsv1", false)) + setoptions |= SSL_OP_NO_TLSv1; - if (Conf->getBool("showports", true)) - { - sslports = Conf->getString("advertisedports"); - if (!sslports.empty()) - return; + if (!setoptions && !clearoptions) + return; // Nothing to do - for (size_t i = 0; i < ServerInstance->ports.size(); i++) - { - ListenSocket* port = ServerInstance->ports[i]; - if (port->bind_tag->getString("ssl") != "openssl") - continue; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Setting %s %s context options, default: %ld set: %ld clear: %ld", name.c_str(), ctxname.c_str(), ctx.GetDefaultContextOptions(), setoptions, clearoptions); + long final = context.SetRawContextOptions(setoptions, clearoptions); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "%s %s context options: %ld", name.c_str(), ctxname.c_str(), final); + } + + public: + Profile(const std::string& profilename, ConfigTag* tag) + : name(profilename) + , dh(ServerInstance->Config->Paths.PrependConfig(tag->getString("dhfile", "dhparams.pem"))) + , ctx(SSL_CTX_new(SSLv23_server_method())) + , clictx(SSL_CTX_new(SSLv23_client_method())) + , allowrenego(tag->getBool("renegotiation")) // Disallow by default + , outrecsize(tag->getUInt("outrecsize", 2048, 512, 16384)) + { + if ((!ctx.SetDH(dh)) || (!clictx.SetDH(dh))) + throw Exception("Couldn't set DH parameters"); - const std::string& portid = port->bind_desc; - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Enabling SSL for port %s", portid.c_str()); + std::string hash = tag->getString("hash", "md5"); + digest = EVP_get_digestbyname(hash.c_str()); + if (digest == NULL) + throw Exception("Unknown hash type " + hash); - if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1") + std::string ciphers = tag->getString("ciphers"); + if (!ciphers.empty()) + { + if ((!ctx.SetCiphers(ciphers)) || (!clictx.SetCiphers(ciphers))) { - /* - * Found an SSL port for clients that is not bound to 127.0.0.1 and handled by us, display - * the IP:port in ISUPPORT. - * - * We used to advertise all ports seperated by a ';' char that matched the above criteria, - * but this resulted in too long ISUPPORT lines if there were lots of ports to be displayed. - * To solve this by default we now only display the first IP:port found and let the user - * configure the exact value for the 005 token, if necessary. - */ - sslports = portid; - break; + ERR_print_errors_cb(error_callback, this); + throw Exception("Can't set cipher list to \"" + ciphers + "\" " + lasterr); } } - } - } - - void OnModuleRehash(User* user, const std::string ¶m) - { - if (param != "ssl") - return; - - std::string keyfile; - std::string certfile; - std::string cafile; - std::string dhfile; - OnRehash(user); - ConfigTag* conf = ServerInstance->Config->ConfValue("openssl"); +#ifndef OPENSSL_NO_ECDH + std::string curvename = tag->getString("ecdhcurve", "prime256v1"); + if (!curvename.empty()) + ctx.SetECDH(curvename); +#endif - cafile = conf->getString("cafile", CONFIG_PATH "/ca.pem"); - certfile = conf->getString("certfile", CONFIG_PATH "/cert.pem"); - keyfile = conf->getString("keyfile", CONFIG_PATH "/key.pem"); - dhfile = conf->getString("dhfile", CONFIG_PATH "/dhparams.pem"); - std::string hash = conf->getString("hash", "md5"); - if (hash != "sha1" && hash != "md5") - throw ModuleException("Unknown hash type " + hash); - use_sha = (hash == "sha1"); + SetContextOptions("server", tag, ctx); + SetContextOptions("client", tag, clictx); - if (conf->getBool("customcontextoptions")) - { - SetContextOptions(ctx, ctx_options, "server", conf); - SetContextOptions(clictx, clictx_options, "client", conf); - } + /* Load our keys and certificates + * NOTE: OpenSSL's error logging API sucks, don't blame us for this clusterfuck. + */ + std::string filename = ServerInstance->Config->Paths.PrependConfig(tag->getString("certfile", "cert.pem")); + if ((!ctx.SetCerts(filename)) || (!clictx.SetCerts(filename))) + { + ERR_print_errors_cb(error_callback, this); + throw Exception("Can't read certificate file: " + lasterr); + } - std::string ciphers = conf->getString("ciphers", ""); + filename = ServerInstance->Config->Paths.PrependConfig(tag->getString("keyfile", "key.pem")); + if ((!ctx.SetPrivateKey(filename)) || (!clictx.SetPrivateKey(filename))) + { + ERR_print_errors_cb(error_callback, this); + throw Exception("Can't read key file: " + lasterr); + } - if (!ciphers.empty()) - { - ERR_clear_error(); - if ((!SSL_CTX_set_cipher_list(ctx, ciphers.c_str())) || (!SSL_CTX_set_cipher_list(clictx, ciphers.c_str()))) + // Load the CAs we trust + filename = ServerInstance->Config->Paths.PrependConfig(tag->getString("cafile", "ca.pem")); + if ((!ctx.SetCA(filename)) || (!clictx.SetCA(filename))) { - ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't set cipher list to %s.", ciphers.c_str()); ERR_print_errors_cb(error_callback, this); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Can't read CA list from %s. This is only a problem if you want to verify client certificates, otherwise it's safe to ignore this message. Error: %s", filename.c_str(), lasterr.c_str()); } + + // Load the CRLs. + std::string crlfile = tag->getString("crlfile"); + std::string crlpath = tag->getString("crlpath"); + std::string crlmode = tag->getString("crlmode", "chain"); + ctx.SetCRL(crlfile, crlpath, crlmode); + + clictx.SetVerifyCert(); + if (tag->getBool("requestclientcert", true)) + ctx.SetVerifyCert(); } - /* Load our keys and certificates - * NOTE: OpenSSL's error logging API sucks, don't blame us for this clusterfuck. - */ - ERR_clear_error(); - if ((!SSL_CTX_use_certificate_chain_file(ctx, certfile.c_str())) || (!SSL_CTX_use_certificate_chain_file(clictx, certfile.c_str()))) + const std::string& GetName() const { return name; } + SSL* CreateServerSession() { return ctx.CreateServerSession(); } + SSL* CreateClientSession() { return clictx.CreateClientSession(); } + const EVP_MD* GetDigest() { return digest; } + bool AllowRenegotiation() const { return allowrenego; } + unsigned int GetOutgoingRecordSize() const { return outrecsize; } + }; + + namespace BIOMethod + { + static int create(BIO* bio) { - ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't read certificate file %s. %s", certfile.c_str(), strerror(errno)); - ERR_print_errors_cb(error_callback, this); + BIO_set_init(bio, 1); + return 1; } - ERR_clear_error(); - if (((!SSL_CTX_use_PrivateKey_file(ctx, keyfile.c_str(), SSL_FILETYPE_PEM))) || (!SSL_CTX_use_PrivateKey_file(clictx, keyfile.c_str(), SSL_FILETYPE_PEM))) + static int destroy(BIO* bio) { - ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't read key file %s. %s", keyfile.c_str(), strerror(errno)); - ERR_print_errors_cb(error_callback, this); + // XXX: Dummy function to avoid a memory leak in OpenSSL. + // The memory leak happens in BIO_free() (bio_lib.c) when the destroy func of the BIO is NULL. + // This is fixed in OpenSSL but some distros still ship the unpatched version hence we provide this workaround. + return 1; } - /* Load the CAs we trust*/ - ERR_clear_error(); - if (((!SSL_CTX_load_verify_locations(ctx, cafile.c_str(), 0))) || (!SSL_CTX_load_verify_locations(clictx, cafile.c_str(), 0))) + static long ctrl(BIO* bio, int cmd, long num, void* ptr) { - ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so: Can't read CA list from %s. This is only a problem if you want to verify client certificates, otherwise it's safe to ignore this message. Error: %s", cafile.c_str(), strerror(errno)); - ERR_print_errors_cb(error_callback, this); + if (cmd == BIO_CTRL_FLUSH) + return 1; + return 0; } -#ifdef _WIN32 - BIO* dhpfile = BIO_new_file(dhfile.c_str(), "r"); -#else - FILE* dhpfile = fopen(dhfile.c_str(), "r"); -#endif - DH* ret; + static int read(BIO* bio, char* buf, int len); + static int write(BIO* bio, const char* buf, int len); - if (dhpfile == NULL) +#ifdef INSPIRCD_OPENSSL_OPAQUE_BIO + static BIO_METHOD* alloc() { - ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "m_ssl_openssl.so Couldn't open DH file %s: %s", dhfile.c_str(), strerror(errno)); - throw ModuleException("Couldn't open DH file " + dhfile + ": " + strerror(errno)); + BIO_METHOD* meth = BIO_meth_new(100 | BIO_TYPE_SOURCE_SINK, "inspircd"); + BIO_meth_set_write(meth, OpenSSL::BIOMethod::write); + BIO_meth_set_read(meth, OpenSSL::BIOMethod::read); + BIO_meth_set_ctrl(meth, OpenSSL::BIOMethod::ctrl); + BIO_meth_set_create(meth, OpenSSL::BIOMethod::create); + BIO_meth_set_destroy(meth, OpenSSL::BIOMethod::destroy); + return meth; } - else - { -#ifdef _WIN32 - ret = PEM_read_bio_DHparams(dhpfile, NULL, NULL, NULL); - BIO_free(dhpfile); +#endif + } +} + +// BIO_METHOD is opaque in OpenSSL 1.1 so we can't do this. +// See OpenSSL::BIOMethod::alloc for the new method. +#ifndef INSPIRCD_OPENSSL_OPAQUE_BIO +static BIO_METHOD biomethods = +{ + (100 | BIO_TYPE_SOURCE_SINK), + "inspircd", + OpenSSL::BIOMethod::write, + OpenSSL::BIOMethod::read, + NULL, // puts + NULL, // gets + OpenSSL::BIOMethod::ctrl, + OpenSSL::BIOMethod::create, + OpenSSL::BIOMethod::destroy, // destroy, does nothing, see function body for more info + NULL // callback_ctrl +}; #else - ret = PEM_read_DHparams(dhpfile, NULL, NULL, NULL); +static BIO_METHOD* biomethods; #endif - ERR_clear_error(); - if (ret) +static int OnVerify(int preverify_ok, X509_STORE_CTX *ctx) +{ + /* XXX: This will allow self signed certificates. + * In the future if we want an option to not allow this, + * we can just return preverify_ok here, and openssl + * will boot off self-signed and invalid peer certs. + */ + int ve = X509_STORE_CTX_get_error(ctx); + + SelfSigned = (ve == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + + return 1; +} + +class OpenSSLIOHook : public SSLIOHook +{ + private: + SSL* sess; + issl_status status; + bool data_to_write; + + // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed + int Handshake(StreamSocket* user) + { + ERR_clear_error(); + int ret = SSL_do_handshake(sess); + if (ret < 0) + { + int err = SSL_get_error(sess, ret); + + if (err == SSL_ERROR_WANT_READ) { - if ((SSL_CTX_set_tmp_dh(ctx, ret) < 0) || (SSL_CTX_set_tmp_dh(clictx, ret) < 0)) - { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set DH parameters %s. SSL errors follow:", dhfile.c_str()); - ERR_print_errors_cb(error_callback, this); - } - DH_free(ret); + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + this->status = ISSL_HANDSHAKING; + return 0; + } + else if (err == SSL_ERROR_WANT_WRITE) + { + SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); + this->status = ISSL_HANDSHAKING; + return 0; } else { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set DH parameters %s.", dhfile.c_str()); + CloseSession(); + return -1; } } + else if (ret > 0) + { + // Handshake complete. + VerifyCertificate(); -#ifndef _WIN32 - fclose(dhpfile); -#endif - -#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH - SetupECDH(conf); -#endif - } - - void On005Numeric(std::string &output) - { - if (!sslports.empty()) - output.append(" SSL=" + sslports); - } + status = ISSL_OPEN; - ~ModuleSSLOpenSSL() - { - SSL_CTX_free(ctx); - SSL_CTX_free(clictx); - delete[] sessions; - } + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); - void OnUserConnect(LocalUser* user) - { - if (user->eh.GetIOHook() == this) + return 1; + } + else if (ret == 0) { - if (sessions[user->eh.GetFd()].sess) - { - if (!sessions[user->eh.GetFd()].cert->fingerprint.empty()) - user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"" - " and your SSL fingerprint is %s", user->nick.c_str(), SSL_get_cipher(sessions[user->eh.GetFd()].sess), sessions[user->eh.GetFd()].cert->fingerprint.c_str()); - else - user->WriteServ("NOTICE %s :*** You are connected using SSL cipher \"%s\"", user->nick.c_str(), SSL_get_cipher(sessions[user->eh.GetFd()].sess)); - } + CloseSession(); } + return -1; } - void OnCleanup(int target_type, void* item) + void CloseSession() { - if (target_type == TYPE_USER) + if (sess) { - LocalUser* user = IS_LOCAL((User*)item); - - if (user && user->eh.GetIOHook() == this) - { - // User is using SSL, they're a local user, and they're using one of *our* SSL ports. - // Potentially there could be multiple SSL modules loaded at once on different ports. - ServerInstance->Users->QuitUser(user, "SSL module unloading"); - } + SSL_shutdown(sess); + SSL_free(sess); } + sess = NULL; + certificate = NULL; + status = ISSL_NONE; } - Version GetVersion() + void VerifyCertificate() { - return Version("Provides SSL support for clients", VF_VENDOR); - } + X509* cert; + ssl_cert* certinfo = new ssl_cert; + this->certificate = certinfo; + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; - void OnRequest(Request& request) - { - if (strcmp("GET_SSL_CERT", request.id) == 0) + cert = SSL_get_peer_certificate(sess); + + if (!cert) { - SocketCertificateRequest& req = static_cast<SocketCertificateRequest&>(request); - int fd = req.sock->GetFd(); - issl_session* session = &sessions[fd]; + certinfo->error = "Could not get peer certificate: "+std::string(get_error()); + return; + } + + certinfo->invalid = (SSL_get_verify_result(sess) != X509_V_OK); - req.cert = session->cert; + if (!SelfSigned) + { + certinfo->unknownsigner = false; + certinfo->trusted = true; } - else if (!strcmp("GET_RAW_SSL_SESSION", request.id)) + else { - SSLRawSessionRequest& req = static_cast<SSLRawSessionRequest&>(request); - if ((req.fd >= 0) && (req.fd < ServerInstance->SE->GetMaxFds())) - req.data = reinterpret_cast<void*>(sessions[req.fd].sess); + certinfo->unknownsigner = true; + certinfo->trusted = false; } - } - void OnStreamSocketAccept(StreamSocket* user, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) - { - int fd = user->GetFd(); - - issl_session* session = &sessions[fd]; + char buf[512]; + X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); + certinfo->dn = buf; + // Make sure there are no chars in the string that we consider invalid + if (certinfo->dn.find_first_of("\r\n") != std::string::npos) + certinfo->dn.clear(); - session->sess = SSL_new(ctx); - session->status = ISSL_NONE; - session->outbound = false; - session->data_to_write = false; + X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); + certinfo->issuer = buf; + if (certinfo->issuer.find_first_of("\r\n") != std::string::npos) + certinfo->issuer.clear(); - if (session->sess == NULL) - return; + if (!X509_digest(cert, GetProfile().GetDigest(), md, &n)) + { + certinfo->error = "Out of memory generating fingerprint"; + } + else + { + certinfo->fingerprint = BinToHex(md, n); + } - if (SSL_set_fd(session->sess, fd) == 0) + if ((ASN1_UTCTIME_cmp_time_t(X509_getm_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_getm_notBefore(cert), ServerInstance->Time()) == 0)) { - ServerInstance->Logs->Log("m_ssl_openssl",DEBUG,"BUG: Can't set fd with SSL_set_fd: %d", fd); - return; + certinfo->error = "Not activated, or expired certificate"; } - Handshake(user, session); + X509_free(cert); } - void OnStreamSocketConnect(StreamSocket* user) + void SSLInfoCallback(int where, int rc) { - int fd = user->GetFd(); - /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */ - if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() -1)) - return; + if ((where & SSL_CB_HANDSHAKE_START) && (status == ISSL_OPEN)) + { + if (GetProfile().AllowRenegotiation()) + return; - issl_session* session = &sessions[fd]; + // The other side is trying to renegotiate, kill the connection and change status + // to ISSL_NONE so CheckRenego() closes the session + status = ISSL_NONE; + BIO* bio = SSL_get_rbio(sess); + EventHandler* eh = static_cast<StreamSocket*>(BIO_get_data(bio)); + SocketEngine::Shutdown(eh, 2); + } + } - session->sess = SSL_new(clictx); - session->status = ISSL_NONE; - session->outbound = true; - session->data_to_write = false; + bool CheckRenego(StreamSocket* sock) + { + if (status != ISSL_NONE) + return true; - if (session->sess == NULL) - return; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Session %p killed, attempted to renegotiate", (void*)sess); + CloseSession(); + sock->SetError("Renegotiation is not allowed"); + return false; + } - if (SSL_set_fd(session->sess, fd) == 0) + // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error + int PrepareIO(StreamSocket* sock) + { + if (status == ISSL_OPEN) + return 1; + else if (status == ISSL_HANDSHAKING) { - ServerInstance->Logs->Log("m_ssl_openssl",DEBUG,"BUG: Can't set fd with SSL_set_fd: %d", fd); - return; + // The handshake isn't finished, try to finish it + return Handshake(sock); } - Handshake(user, session); + CloseSession(); + return -1; } - void OnStreamSocketClose(StreamSocket* user) + // Calls our private SSLInfoCallback() + friend void StaticSSLInfoCallback(const SSL* ssl, int where, int rc); + + public: + OpenSSLIOHook(IOHookProvider* hookprov, StreamSocket* sock, SSL* session) + : SSLIOHook(hookprov) + , sess(session) + , status(ISSL_NONE) + , data_to_write(false) { - int fd = user->GetFd(); - /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */ - if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1)) - return; + // Create BIO instance and store a pointer to the socket in it which will be used by the read and write functions +#ifdef INSPIRCD_OPENSSL_OPAQUE_BIO + BIO* bio = BIO_new(biomethods); +#else + BIO* bio = BIO_new(&biomethods); +#endif + BIO_set_data(bio, sock); + SSL_set_bio(sess, bio, bio); - CloseSession(&sessions[fd]); + SSL_set_ex_data(sess, exdataindex, this); + sock->AddIOHook(this); + Handshake(sock); } - int OnStreamSocketRead(StreamSocket* user, std::string& recvq) + void OnStreamSocketClose(StreamSocket* user) CXX11_OVERRIDE { - int fd = user->GetFd(); - /* Are there any possibilities of an out of range fd? Hope not, but lets be paranoid */ - if ((fd < 0) || (fd > ServerInstance->SE->GetMaxFds() - 1)) - return -1; - - issl_session* session = &sessions[fd]; - - if (!session->sess) - { - CloseSession(session); - return -1; - } - - if (session->status == ISSL_HANDSHAKING) - { - // The handshake isn't finished and it wants to read, try to finish it. - if (!Handshake(user, session)) - { - // Couldn't resume handshake. - if (session->status == ISSL_NONE) - return -1; - return 0; - } - } + CloseSession(); + } - // If we resumed the handshake then session->status will be ISSL_OPEN + int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE + { + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; - if (session->status == ISSL_OPEN) + // If we resumed the handshake then this->status will be ISSL_OPEN { ERR_clear_error(); char* buffer = ServerInstance->GetReadBuffer(); size_t bufsiz = ServerInstance->Config->NetBufferSize; - int ret = SSL_read(session->sess, buffer, bufsiz); + int ret = SSL_read(sess, buffer, bufsiz); -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION - if (!CheckRenego(user, session)) + if (!CheckRenego(user)) return -1; -#endif if (ret > 0) { recvq.append(buffer, ret); - int mask = 0; // Schedule a read if there is still data in the OpenSSL buffer - if (SSL_pending(session->sess) > 0) + if (SSL_pending(sess) > 0) mask |= FD_ADD_TRIAL_READ; - if (session->data_to_write) + if (data_to_write) mask |= FD_WANT_POLL_READ | FD_WANT_SINGLE_WRITE; if (mask != 0) - ServerInstance->SE->ChangeEventMask(user, mask); + SocketEngine::ChangeEventMask(user, mask); return 1; } else if (ret == 0) { // Client closed connection. - CloseSession(session); + CloseSession(); user->SetError("Connection closed"); return -1; } - else if (ret < 0) + else // if (ret < 0) { - int err = SSL_get_error(session->sess, ret); + int err = SSL_get_error(sess, ret); if (err == SSL_ERROR_WANT_READ) { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ); + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ); return 0; } else if (err == SSL_ERROR_WANT_WRITE) { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); + SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); return 0; } else { - CloseSession(session); + CloseSession(); return -1; } } } - - return 0; } - int OnStreamSocketWrite(StreamSocket* user, std::string& buffer) + int OnStreamSocketWrite(StreamSocket* user, StreamSocket::SendQueue& sendq) CXX11_OVERRIDE { - int fd = user->GetFd(); - - issl_session* session = &sessions[fd]; - - if (!session->sess) - { - CloseSession(session); - return -1; - } - - session->data_to_write = true; + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; - if (session->status == ISSL_HANDSHAKING) - { - if (!Handshake(user, session)) - { - // Couldn't resume handshake. - if (session->status == ISSL_NONE) - return -1; - return 0; - } - } + data_to_write = true; - if (session->status == ISSL_OPEN) + // Session is ready for transferring application data + while (!sendq.empty()) { ERR_clear_error(); - int ret = SSL_write(session->sess, buffer.data(), buffer.size()); + FlattenSendQueue(sendq, GetProfile().GetOutgoingRecordSize()); + const StreamSocket::SendQueue::Element& buffer = sendq.front(); + int ret = SSL_write(sess, buffer.data(), buffer.size()); -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION - if (!CheckRenego(user, session)) + if (!CheckRenego(user)) return -1; -#endif if (ret == (int)buffer.length()) { - session->data_to_write = false; - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); - return 1; + // Wrote entire record, continue sending + sendq.pop_front(); } else if (ret > 0) { - buffer = buffer.substr(ret); - ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE); + sendq.erase_front(ret); + SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE); return 0; } else if (ret == 0) { - CloseSession(session); + CloseSession(); return -1; } - else if (ret < 0) + else // if (ret < 0) { - int err = SSL_get_error(session->sess, ret); + int err = SSL_get_error(sess, ret); if (err == SSL_ERROR_WANT_WRITE) { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_SINGLE_WRITE); + SocketEngine::ChangeEventMask(user, FD_WANT_SINGLE_WRITE); return 0; } else if (err == SSL_ERROR_WANT_READ) { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ); + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ); return 0; } else { - CloseSession(session); + CloseSession(); return -1; } } } - return 0; + + data_to_write = false; + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + return 1; } - bool Handshake(StreamSocket* user, issl_session* session) + void GetCiphersuite(std::string& out) const CXX11_OVERRIDE { - int ret; + if (!IsHandshakeDone()) + return; + out.append(SSL_get_version(sess)).push_back('-'); + out.append(SSL_get_cipher(sess)); + } - ERR_clear_error(); - if (session->outbound) - ret = SSL_connect(session->sess); - else - ret = SSL_accept(session->sess); + bool GetServerName(std::string& out) const CXX11_OVERRIDE + { + const char* name = SSL_get_servername(sess, TLSEXT_NAMETYPE_host_name); + if (!name) + return false; - if (ret < 0) + out.append(name); + return true; + } + + bool IsHandshakeDone() const { return (status == ISSL_OPEN); } + OpenSSL::Profile& GetProfile(); +}; + +static void StaticSSLInfoCallback(const SSL* ssl, int where, int rc) +{ + OpenSSLIOHook* hook = static_cast<OpenSSLIOHook*>(SSL_get_ex_data(ssl, exdataindex)); + hook->SSLInfoCallback(where, rc); +} + +static int OpenSSL::BIOMethod::write(BIO* bio, const char* buffer, int size) +{ + BIO_clear_retry_flags(bio); + + StreamSocket* sock = static_cast<StreamSocket*>(BIO_get_data(bio)); + if (sock->GetEventMask() & FD_WRITE_WILL_BLOCK) + { + // Writes blocked earlier, don't retry syscall + BIO_set_retry_write(bio); + return -1; + } + + int ret = SocketEngine::Send(sock, buffer, size, 0); + if ((ret < size) && ((ret > 0) || (SocketEngine::IgnoreError()))) + { + // Blocked, set retry flag for OpenSSL + SocketEngine::ChangeEventMask(sock, FD_WRITE_WILL_BLOCK); + BIO_set_retry_write(bio); + } + + return ret; +} + +static int OpenSSL::BIOMethod::read(BIO* bio, char* buffer, int size) +{ + BIO_clear_retry_flags(bio); + + StreamSocket* sock = static_cast<StreamSocket*>(BIO_get_data(bio)); + if (sock->GetEventMask() & FD_READ_WILL_BLOCK) + { + // Reads blocked earlier, don't retry syscall + BIO_set_retry_read(bio); + return -1; + } + + int ret = SocketEngine::Recv(sock, buffer, size, 0); + if ((ret < size) && ((ret > 0) || (SocketEngine::IgnoreError()))) + { + // Blocked, set retry flag for OpenSSL + SocketEngine::ChangeEventMask(sock, FD_READ_WILL_BLOCK); + BIO_set_retry_read(bio); + } + + return ret; +} + +class OpenSSLIOHookProvider : public IOHookProvider +{ + OpenSSL::Profile profile; + + public: + OpenSSLIOHookProvider(Module* mod, const std::string& profilename, ConfigTag* tag) + : IOHookProvider(mod, "ssl/" + profilename, IOHookProvider::IOH_SSL) + , profile(profilename, tag) + { + ServerInstance->Modules->AddService(*this); + } + + ~OpenSSLIOHookProvider() + { + ServerInstance->Modules->DelService(*this); + } + + void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE + { + new OpenSSLIOHook(this, sock, profile.CreateServerSession()); + } + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + new OpenSSLIOHook(this, sock, profile.CreateClientSession()); + } + + OpenSSL::Profile& GetProfile() { return profile; } +}; + +OpenSSL::Profile& OpenSSLIOHook::GetProfile() +{ + IOHookProvider* hookprov = prov; + return static_cast<OpenSSLIOHookProvider*>(hookprov)->GetProfile(); +} + +class ModuleSSLOpenSSL : public Module +{ + typedef std::vector<reference<OpenSSLIOHookProvider> > ProfileList; + + ProfileList profiles; + + void ReadProfiles() + { + ProfileList newprofiles; + ConfigTagList tags = ServerInstance->Config->ConfTags("sslprofile"); + if (tags.first == tags.second) { - int err = SSL_get_error(session->sess, ret); + // Create a default profile named "openssl" + const std::string defname = "openssl"; + ConfigTag* tag = ServerInstance->Config->ConfValue(defname); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "No <sslprofile> tags found, using settings from the <openssl> tag"); - if (err == SSL_ERROR_WANT_READ) + try { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); - session->status = ISSL_HANDSHAKING; - return true; + newprofiles.push_back(new OpenSSLIOHookProvider(this, defname, tag)); } - else if (err == SSL_ERROR_WANT_WRITE) - { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); - session->status = ISSL_HANDSHAKING; - return true; - } - else + catch (OpenSSL::Exception& ex) { - CloseSession(session); + throw ModuleException("Error while initializing the default SSL profile - " + ex.GetReason()); } - - return false; } - else if (ret > 0) + + for (ConfigIter i = tags.first; i != tags.second; ++i) { - // Handshake complete. - VerifyCertificate(session, user); + ConfigTag* tag = i->second; + if (!stdalgo::string::equalsci(tag->getString("provider"), "openssl")) + continue; - session->status = ISSL_OPEN; + std::string name = tag->getString("name"); + if (name.empty()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring <sslprofile> tag without name at " + tag->getTagLocation()); + continue; + } - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); + reference<OpenSSLIOHookProvider> prov; + try + { + prov = new OpenSSLIOHookProvider(this, name, tag); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); + } - return true; + newprofiles.push_back(prov); } - else if (ret == 0) + + for (ProfileList::iterator i = profiles.begin(); i != profiles.end(); ++i) { - CloseSession(session); + OpenSSLIOHookProvider& prov = **i; + ServerInstance->Modules.DelService(prov); } - return false; + + profiles.swap(newprofiles); } - void CloseSession(issl_session* session) + public: + ModuleSSLOpenSSL() { - if (session->sess) - { - SSL_shutdown(session->sess); - SSL_free(session->sess); - } + // Initialize OpenSSL + OPENSSL_init_ssl(0, NULL); +#ifdef INSPIRCD_OPENSSL_OPAQUE_BIO + biomethods = OpenSSL::BIOMethod::alloc(); + } - session->sess = NULL; - session->status = ISSL_NONE; - session->cert = NULL; + ~ModuleSSLOpenSSL() + { + BIO_meth_free(biomethods); +#endif } - void VerifyCertificate(issl_session* session, StreamSocket* user) + void init() CXX11_OVERRIDE { - if (!session->sess || !user) - return; + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "OpenSSL lib version \"%s\" module was compiled for \"" OPENSSL_VERSION_TEXT "\"", OpenSSL_version(OPENSSL_VERSION)); - X509* cert; - ssl_cert* certinfo = new ssl_cert; - session->cert = certinfo; - unsigned int n; - unsigned char md[EVP_MAX_MD_SIZE]; - const EVP_MD *digest = use_sha ? EVP_sha1() : EVP_md5(); + // Register application specific data + char exdatastr[] = "inspircd"; + exdataindex = SSL_get_ex_new_index(0, exdatastr, NULL, NULL, NULL); + if (exdataindex < 0) + throw ModuleException("Failed to register application specific data"); - cert = SSL_get_peer_certificate((SSL*)session->sess); + ReadProfiles(); + } - if (!cert) - { - certinfo->error = "Could not get peer certificate: "+std::string(get_error()); + void OnModuleRehash(User* user, const std::string ¶m) CXX11_OVERRIDE + { + if (param != "ssl") return; - } - - certinfo->invalid = (SSL_get_verify_result(session->sess) != X509_V_OK); - if (!SelfSigned) + try { - certinfo->unknownsigner = false; - certinfo->trusted = true; + ReadProfiles(); } - else + catch (ModuleException& ex) { - certinfo->unknownsigner = true; - certinfo->trusted = false; + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason() + " Not applying settings."); } + } - char buf[512]; - X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); - certinfo->dn = buf; - // Make sure there are no chars in the string that we consider invalid - if (certinfo->dn.find_first_of("\r\n") != std::string::npos) - certinfo->dn.clear(); - - X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); - certinfo->issuer = buf; - if (certinfo->issuer.find_first_of("\r\n") != std::string::npos) - certinfo->issuer.clear(); - - if (!X509_digest(cert, digest, md, &n)) - { - certinfo->error = "Out of memory generating fingerprint"; - } - else + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE + { + if (type == ExtensionItem::EXT_USER) { - certinfo->fingerprint = irc::hex(md, n); - } + LocalUser* user = IS_LOCAL((User*)item); - if ((ASN1_UTCTIME_cmp_time_t(X509_getm_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_getm_notBefore(cert), ServerInstance->Time()) == 0)) - { - certinfo->error = "Not activated, or expired certificate"; + if ((user) && (user->eh.GetModHook(this))) + { + // User is using SSL, they're a local user, and they're using one of *our* SSL ports. + // Potentially there could be multiple SSL modules loaded at once on different ports. + ServerInstance->Users->QuitUser(user, "SSL module unloading"); + } } + } - X509_free(cert); + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE + { + const OpenSSLIOHook* const iohook = static_cast<OpenSSLIOHook*>(user->eh.GetModHook(this)); + if ((iohook) && (!iohook->IsHandshakeDone())) + return MOD_RES_DENY; + return MOD_RES_PASSTHRU; } -}; -static int error_callback(const char *str, size_t len, void *u) -{ - ServerInstance->Logs->Log("m_ssl_openssl",DEFAULT, "SSL error: " + std::string(str, len - 1)); - - // - // XXX: Remove this line, it causes valgrind warnings... - // - // MD_update(&m, buf, j); - // - // - // ... ONLY JOKING! :-) - // - - return 0; -} + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides SSL support for clients", VF_VENDOR); + } +}; MODULE_INIT(ModuleSSLOpenSSL) diff --git a/src/modules/extra/m_sslrehashsignal.cpp b/src/modules/extra/m_sslrehashsignal.cpp new file mode 100644 index 000000000..fea32326a --- /dev/null +++ b/src/modules/extra/m_sslrehashsignal.cpp @@ -0,0 +1,64 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2018 Peter Powell <petpow@saberuk.com> + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +static volatile sig_atomic_t signaled; + +class ModuleSSLRehashSignal : public Module +{ + private: + static void SignalHandler(int) + { + signaled = 1; + } + + public: + ~ModuleSSLRehashSignal() + { + signal(SIGUSR1, SIG_DFL); + } + + void init() + { + signal(SIGUSR1, SignalHandler); + } + + void OnBackgroundTimer(time_t) + { + if (!signaled) + return; + + const std::string feedbackmsg = "Got SIGUSR1, reloading SSL credentials"; + ServerInstance->SNO->WriteGlobalSno('a', feedbackmsg); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, feedbackmsg); + + const std::string str = "ssl"; + FOREACH_MOD(OnModuleRehash, (NULL, str)); + signaled = 0; + } + + Version GetVersion() + { + return Version("Reloads SSL credentials on SIGUSR1", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleSSLRehashSignal) diff --git a/src/modules/hash.h b/src/modules/hash.h deleted file mode 100644 index f7bf85e20..000000000 --- a/src/modules/hash.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2010 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#ifndef HASH_H -#define HASH_H - -#include "modules.h" - -class HashProvider : public DataProvider -{ - public: - const unsigned int out_size; - const unsigned int block_size; - HashProvider(Module* mod, const std::string& Name, int osiz, int bsiz) - : DataProvider(mod, Name), out_size(osiz), block_size(bsiz) {} - virtual std::string sum(const std::string& data) = 0; - inline std::string hexsum(const std::string& data) - { - return BinToHex(sum(data)); - } - - inline std::string b64sum(const std::string& data) - { - return BinToBase64(sum(data), NULL, 0); - } - - /** Allows the IVs for the hash to be specified. As the choice of initial IV is - * important for the security of a hash, this should not be used except to - * maintain backwards compatability. This also allows you to change the hex - * sequence from its default of "0123456789abcdef", which does not improve the - * strength of the output, but helps confuse those attempting to implement it. - * - * Example: - * \code - * unsigned int iv[] = { 0xFFFFFFFF, 0x00000000, 0xAAAAAAAA, 0xCCCCCCCC }; - * std::string result = Hash.sumIV(iv, "fedcba9876543210", "data"); - * \endcode - */ - virtual std::string sumIV(unsigned int* IV, const char* HexMap, const std::string &sdata) = 0; - - /** HMAC algorithm, RFC 2104 */ - std::string hmac(const std::string& key, const std::string& msg) - { - std::string hmac1, hmac2; - std::string kbuf = key.length() > block_size ? sum(key) : key; - kbuf.resize(block_size); - - for (size_t n = 0; n < block_size; n++) - { - hmac1.push_back(static_cast<char>(kbuf[n] ^ 0x5C)); - hmac2.push_back(static_cast<char>(kbuf[n] ^ 0x36)); - } - hmac2.append(msg); - hmac1.append(sum(hmac2)); - return sum(hmac1); - } -}; - -#endif - diff --git a/src/modules/httpd.h b/src/modules/httpd.h deleted file mode 100644 index 56fd22da0..000000000 --- a/src/modules/httpd.h +++ /dev/null @@ -1,207 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> - * Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "base.h" - -#ifndef HTTPD_H -#define HTTPD_H - -#include <string> -#include <sstream> -#include <map> - -/** A modifyable list of HTTP header fields - */ -class HTTPHeaders -{ - protected: - std::map<std::string,std::string> headers; - public: - - /** Set the value of a header - * Sets the value of the named header. If the header is already present, it will be replaced - */ - void SetHeader(const std::string &name, const std::string &data) - { - headers[name] = data; - } - - /** Set the value of a header, only if it doesn't exist already - * Sets the value of the named header. If the header is already present, it will NOT be updated - */ - void CreateHeader(const std::string &name, const std::string &data) - { - if (!IsSet(name)) - SetHeader(name, data); - } - - /** Remove the named header - */ - void RemoveHeader(const std::string &name) - { - headers.erase(name); - } - - /** Remove all headers - */ - void Clear() - { - headers.clear(); - } - - /** Get the value of a header - * @return The value of the header, or an empty string - */ - std::string GetHeader(const std::string &name) - { - std::map<std::string,std::string>::iterator it = headers.find(name); - if (it == headers.end()) - return std::string(); - - return it->second; - } - - /** Check if the given header is specified - * @return true if the header is specified - */ - bool IsSet(const std::string &name) - { - std::map<std::string,std::string>::iterator it = headers.find(name); - return (it != headers.end()); - } - - /** Get all headers, formatted by the HTTP protocol - * @return Returns all headers, formatted according to the HTTP protocol. There is no request terminator at the end - */ - std::string GetFormattedHeaders() - { - std::string re; - - for (std::map<std::string,std::string>::iterator i = headers.begin(); i != headers.end(); i++) - re += i->first + ": " + i->second + "\r\n"; - - return re; - } -}; - -class HttpServerSocket; - -/** This class represents a HTTP request. - */ -class HTTPRequest : public Event -{ - protected: - std::string type; - std::string document; - std::string ipaddr; - std::string postdata; - - public: - - HTTPHeaders *headers; - int errorcode; - - /** A socket pointer, which you must return in your HTTPDocument class - * if you reply to this request. - */ - HttpServerSocket* sock; - - /** Initialize HTTPRequest. - * This constructor is called by m_httpd.so to initialize the class. - * @param request_type The request type, e.g. GET, POST, HEAD - * @param uri The URI, e.g. /page - * @param hdr The headers sent with the request - * @param opaque An opaque pointer used internally by m_httpd, which you must pass back to the module in your reply. - * @param ip The IP address making the web request. - * @param pdata The post data (content after headers) received with the request, up to Content-Length in size - */ - HTTPRequest(Module* me, const std::string &eventid, const std::string &request_type, const std::string &uri, - HTTPHeaders* hdr, HttpServerSocket* socket, const std::string &ip, const std::string &pdata) - : Event(me, eventid), type(request_type), document(uri), ipaddr(ip), postdata(pdata), headers(hdr), sock(socket) - { - } - - /** Get the post data (request content). - * All post data will be returned, including carriage returns and linefeeds. - * @return The postdata - */ - std::string& GetPostData() - { - return postdata; - } - - /** Get the request type. - * Any request type can be intercepted, even ones which are invalid in the HTTP/1.1 spec. - * @return The request type, e.g. GET, POST, HEAD - */ - std::string& GetType() - { - return type; - } - - /** Get URI. - * The URI string (URL minus hostname and scheme) will be provided by this function. - * @return The URI being requested - */ - std::string& GetURI() - { - return document; - } - - /** Get IP address of requester. - * The requesting system's ip address will be returned. - * @return The IP address as a string - */ - std::string& GetIP() - { - return ipaddr; - } -}; - -/** You must return a HTTPDocument to the httpd module by using the Request class. - * When you initialize this class you may initialize it with all components required to - * form a valid HTTP response, including document data, headers, and a response code. - */ -class HTTPDocumentResponse : public Request -{ - public: - std::stringstream* document; - int responsecode; - HTTPHeaders headers; - HTTPRequest& src; - - /** Initialize a HTTPRequest ready for sending to m_httpd.so. - * @param opaque The socket pointer you obtained from the HTTPRequest at an earlier time - * @param doc A stringstream containing the document body - * @param response A valid HTTP/1.0 or HTTP/1.1 response code. The response text will be determined for you - * based upon the response code. - * @param extra Any extra headers to include with the defaults, seperated by carriage return and linefeed. - */ - HTTPDocumentResponse(Module* me, HTTPRequest& req, std::stringstream* doc, int response) - : Request(me, req.source, "HTTP-DOC"), document(doc), responsecode(response), src(req) - { - } -}; - -#endif - diff --git a/src/modules/m_abbreviation.cpp b/src/modules/m_abbreviation.cpp index 1e8f71176..f5065c73f 100644 --- a/src/modules/m_abbreviation.cpp +++ b/src/modules/m_abbreviation.cpp @@ -19,49 +19,43 @@ #include "inspircd.h" -/* $ModDesc: Provides the ability to abbreviate commands a-la BBC BASIC keywords. */ +enum +{ + // InspIRCd-specific. + ERR_AMBIGUOUSCOMMAND = 420 +}; class ModuleAbbreviation : public Module { public: - void init() - { - ServerInstance->Modules->Attach(I_OnPreCommand, this); - } - - void Prioritize() + void Prioritize() CXX11_OVERRIDE { ServerInstance->Modules->SetPriority(this, I_OnPreCommand, PRIORITY_FIRST); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the ability to abbreviate commands a-la BBC BASIC keywords.",VF_VENDOR); } - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { /* Command is already validated, has a length of 0, or last character is not a . */ if (validated || command.empty() || *command.rbegin() != '.') return MOD_RES_PASSTHRU; - /* Whack the . off the end */ - command.erase(command.end() - 1); - /* Look for any command that starts with the same characters, if it does, replace the command string with it */ - size_t clen = command.length(); + size_t clen = command.length() - 1; std::string foundcommand, matchlist; bool foundmatch = false; - for (Commandtable::iterator n = ServerInstance->Parser->cmdlist.begin(); n != ServerInstance->Parser->cmdlist.end(); ++n) + const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands(); + for (CommandParser::CommandMap::const_iterator n = commands.begin(); n != commands.end(); ++n) { - if (n->first.length() < clen) - continue; - - if (command == n->first.substr(0, clen)) + if (!command.compare(0, clen, n->first, 0, clen)) { if (matchlist.length() > 450) { - user->WriteNumeric(420, "%s :Ambiguous abbreviation and too many possible matches.", user->nick.c_str()); + user->WriteNumeric(ERR_AMBIGUOUSCOMMAND, "Ambiguous abbreviation and too many possible matches."); return MOD_RES_DENY; } @@ -79,16 +73,11 @@ class ModuleAbbreviation : public Module /* Ambiguous command, list the matches */ if (!matchlist.empty()) { - user->WriteNumeric(420, "%s :Ambiguous abbreviation, possible matches: %s%s", user->nick.c_str(), foundcommand.c_str(), matchlist.c_str()); + user->WriteNumeric(ERR_AMBIGUOUSCOMMAND, InspIRCd::Format("Ambiguous abbreviation, possible matches: %s%s", foundcommand.c_str(), matchlist.c_str())); return MOD_RES_DENY; } - if (foundcommand.empty()) - { - /* No match, we have to put the . back again so that the invalid command numeric looks correct. */ - command += '.'; - } - else + if (!foundcommand.empty()) { command = foundcommand; } diff --git a/src/modules/m_alias.cpp b/src/modules/m_alias.cpp index 32fc80b64..6f27ecc09 100644 --- a/src/modules/m_alias.cpp +++ b/src/modules/m_alias.cpp @@ -22,15 +22,13 @@ #include "inspircd.h" -/* $ModDesc: Provides aliases of commands. */ - /** An alias definition */ class Alias { public: /** The text of the alias command */ - irc::string AliasedCommand; + std::string AliasedCommand; /** Text to replace with */ std::string ReplaceFormat; @@ -44,9 +42,6 @@ class Alias /** Requires oper? */ bool OperOnly; - /* is case sensitive params */ - bool CaseSensitive; - /* whether or not it may be executed via fantasy (default OFF) */ bool ChannelCommand; @@ -59,25 +54,29 @@ class Alias class ModuleAlias : public Module { - private: - - char fprefix; + std::string fprefix; /* We cant use a map, there may be multiple aliases with the same name. * We can, however, use a fancy invention: the multimap. Maps a key to one or more values. * -- w00t - */ - std::multimap<irc::string, Alias> Aliases; + */ + typedef insp::flat_multimap<std::string, Alias, irc::insensitive_swo> AliasMap; + + AliasMap Aliases; /* whether or not +B users are allowed to use fantasy commands */ bool AllowBots; + UserModeReference botmode; + + // Whether we are actively executing an alias. + bool active; - virtual void ReadAliases() + public: + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* fantasy = ServerInstance->Config->ConfValue("fantasy"); AllowBots = fantasy->getBool("allowbots", false); - std::string fpre = fantasy->getString("prefix", "!"); - fprefix = fpre.empty() ? '!' : fpre[0]; + fprefix = fantasy->getString("prefix", "!", 1, ServerInstance->Config->Limits.MaxLine); Aliases.clear(); ConfigTagList tags = ServerInstance->Config->ConfTags("alias"); @@ -85,8 +84,8 @@ class ModuleAlias : public Module { ConfigTag* tag = i->second; Alias a; - std::string aliastext = tag->getString("text"); - a.AliasedCommand = aliastext.c_str(); + a.AliasedCommand = tag->getString("text"); + std::transform(a.AliasedCommand.begin(), a.AliasedCommand.end(), a.AliasedCommand.begin(), ::toupper); tag->readString("replace", a.ReplaceFormat, true); a.RequiredNick = tag->getString("requires"); a.ULineOnly = tag->getBool("uline"); @@ -94,25 +93,16 @@ class ModuleAlias : public Module a.UserCommand = tag->getBool("usercommand", true); a.OperOnly = tag->getBool("operonly"); a.format = tag->getString("format"); - a.CaseSensitive = tag->getBool("matchcase"); Aliases.insert(std::make_pair(a.AliasedCommand, a)); } } - public: - - void init() - { - ReadAliases(); - Implementation eventlist[] = { I_OnPreCommand, I_OnRehash, I_OnUserMessage }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleAlias() + ModuleAlias() + : botmode(this, "bot") { } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides aliases of commands.", VF_VENDOR); } @@ -142,10 +132,22 @@ class ModuleAlias : public Module return word; } - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + std::string CreateRFCMessage(const std::string& command, CommandBase::Params& parameters) { - std::multimap<irc::string, Alias>::iterator i, upperbound; + std::string message(command); + for (CommandBase::Params::const_iterator iter = parameters.begin(); iter != parameters.end();) + { + const std::string& parameter = *iter++; + message.push_back(' '); + if (iter == parameters.end() && (parameter.empty() || parameter.find(' ') != std::string::npos)) + message.push_back(':'); + message.append(parameter); + } + return message; + } + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE + { /* If theyre not registered yet, we dont want * to know. */ @@ -153,19 +155,17 @@ class ModuleAlias : public Module return MOD_RES_PASSTHRU; /* We dont have any commands looking like this? Stop processing. */ - i = Aliases.find(command.c_str()); - if (i == Aliases.end()) + std::pair<AliasMap::iterator, AliasMap::iterator> iters = Aliases.equal_range(command); + if (iters.first == iters.second) return MOD_RES_PASSTHRU; - /* Avoid iterating on to different aliases if no patterns match. */ - upperbound = Aliases.upper_bound(command.c_str()); - irc::string c = command.c_str(); /* The parameters for the command in their original form, with the command stripped off */ - std::string compare = original_line.substr(command.length()); + std::string original_line = CreateRFCMessage(command, parameters); + std::string compare(original_line, command.length()); while (*(compare.c_str()) == ' ') compare.erase(compare.begin()); - while (i != upperbound) + for (AliasMap::iterator i = iters.first; i != iters.second; ++i) { if (i->second.UserCommand) { @@ -174,120 +174,107 @@ class ModuleAlias : public Module return MOD_RES_DENY; } } - - i++; } // If we made it here, no aliases actually matched. return MOD_RES_PASSTHRU; } - virtual void OnUserMessage(User *user, void *dest, int target_type, const std::string &text, char status, const CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (target_type != TYPE_CHANNEL) + // Don't echo anything which is caused by an alias. + if (active) + details.echo = false; + + return MOD_RES_PASSTHRU; + } + + void OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE + { + if ((target.type != MessageTarget::TYPE_CHANNEL) || (details.type != MSG_PRIVMSG)) { return; } // fcommands are only for local users. Spanningtree will send them back out as their original cmd. - if (!user || !IS_LOCAL(user)) + if (!IS_LOCAL(user)) { return; } /* Stop here if the user is +B and allowbot is set to no. */ - if (!AllowBots && user->IsModeSet('B')) + if (!AllowBots && user->IsModeSet(botmode)) { return; } - Channel *c = (Channel *)dest; + Channel *c = target.Get<Channel>(); std::string scommand; // text is like "!moo cows bite me", we want "!moo" first - irc::spacesepstream ss(text); + irc::spacesepstream ss(details.text); ss.GetToken(scommand); - irc::string fcommand = scommand.c_str(); - if (fcommand.empty()) + if (scommand.size() <= fprefix.size()) { return; // wtfbbq } // we don't want to touch non-fantasy stuff - if (*fcommand.c_str() != fprefix) + if (scommand.compare(0, fprefix.size(), fprefix) != 0) { return; } // nor do we give a shit about the prefix - fcommand.erase(fcommand.begin()); - - std::multimap<irc::string, Alias>::iterator i = Aliases.find(fcommand); + scommand.erase(0, fprefix.size()); - if (i == Aliases.end()) + std::pair<AliasMap::iterator, AliasMap::iterator> iters = Aliases.equal_range(scommand); + if (iters.first == iters.second) return; - /* Avoid iterating on to other aliases if no patterns match */ - std::multimap<irc::string, Alias>::iterator upperbound = Aliases.upper_bound(fcommand); - - /* The parameters for the command in their original form, with the command stripped off */ - std::string compare = text.substr(fcommand.length() + 1); + std::string compare(details.text, scommand.length() + fprefix.size()); while (*(compare.c_str()) == ' ') compare.erase(compare.begin()); - while (i != upperbound) + for (AliasMap::iterator i = iters.first; i != iters.second; ++i) { if (i->second.ChannelCommand) { - // We use substr(1) here to remove the fantasy prefix - if (DoAlias(user, c, &(i->second), compare, text.substr(1))) + // We use substr here to remove the fantasy prefix + if (DoAlias(user, c, &(i->second), compare, details.text.substr(fprefix.size()))) return; } - - i++; } } int DoAlias(User *user, Channel *c, Alias *a, const std::string& compare, const std::string& safe) { - User *u = NULL; - /* Does it match the pattern? */ if (!a->format.empty()) { - if (a->CaseSensitive) - { - if (!InspIRCd::Match(compare, a->format, rfc_case_sensitive_map)) - return 0; - } - else - { - if (!InspIRCd::Match(compare, a->format)) - return 0; - } + if (!InspIRCd::Match(compare, a->format)) + return 0; } - if ((a->OperOnly) && (!IS_OPER(user))) + if ((a->OperOnly) && (!user->IsOper())) return 0; if (!a->RequiredNick.empty()) { - u = ServerInstance->FindNick(a->RequiredNick); + User* u = ServerInstance->FindNick(a->RequiredNick); if (!u) { - user->WriteNumeric(401, ""+user->nick+" "+a->RequiredNick+" :is currently unavailable. Please try again later."); + user->WriteNumeric(ERR_NOSUCHNICK, a->RequiredNick, "is currently unavailable. Please try again later."); return 1; } - } - if ((u != NULL) && (!a->RequiredNick.empty()) && (a->ULineOnly)) - { - if (!ServerInstance->ULine(u->server)) + + if ((a->ULineOnly) && (!u->server->IsULine())) { - ServerInstance->SNO->WriteToSnoMask('a', "NOTICE -- Service "+a->RequiredNick+" required by alias "+std::string(a->AliasedCommand.c_str())+" is not on a u-lined server, possibly underhanded antics detected!"); - user->WriteNumeric(401, ""+user->nick+" "+a->RequiredNick+" :is an imposter! Please inform an IRC operator as soon as possible."); + ServerInstance->SNO->WriteToSnoMask('a', "NOTICE -- Service "+a->RequiredNick+" required by alias "+a->AliasedCommand+" is not on a u-lined server, possibly underhanded antics detected!"); + user->WriteNumeric(ERR_NOSUCHNICK, a->RequiredNick, "is an imposter! Please inform an IRC operator as soon as possible."); return 1; } } @@ -298,7 +285,7 @@ class ModuleAlias : public Module if (crlf == std::string::npos) { - DoCommand(a->ReplaceFormat, user, c, safe); + DoCommand(a->ReplaceFormat, user, c, safe, a); return 1; } else @@ -307,16 +294,16 @@ class ModuleAlias : public Module std::string scommand; while (commands.GetToken(scommand)) { - DoCommand(scommand, user, c, safe); + DoCommand(scommand, user, c, safe, a); } return 1; } } - void DoCommand(const std::string& newline, User* user, Channel *chan, const std::string &original_line) + void DoCommand(const std::string& newline, User* user, Channel *chan, const std::string &original_line, Alias* a) { std::string result; - result.reserve(MAXBUF); + result.reserve(newline.length()); for (unsigned int i = 0; i < newline.length(); i++) { char c = newline[i]; @@ -324,37 +311,42 @@ class ModuleAlias : public Module { if (isdigit(newline[i+1])) { - int len = ((i + 2 < newline.length()) && (newline[i+2] == '-')) ? 3 : 2; + size_t len = ((i + 2 < newline.length()) && (newline[i+2] == '-')) ? 3 : 2; std::string var = newline.substr(i, len); result.append(GetVar(var, original_line)); i += len - 1; } - else if (newline.substr(i, 5) == "$nick") + else if (!newline.compare(i, 5, "$nick", 5)) { result.append(user->nick); i += 4; } - else if (newline.substr(i, 5) == "$host") + else if (!newline.compare(i, 5, "$host", 5)) { - result.append(user->host); + result.append(user->GetRealHost()); i += 4; } - else if (newline.substr(i, 5) == "$chan") + else if (!newline.compare(i, 5, "$chan", 5)) { if (chan) result.append(chan->name); i += 4; } - else if (newline.substr(i, 6) == "$ident") + else if (!newline.compare(i, 6, "$ident", 6)) { result.append(user->ident); i += 5; } - else if (newline.substr(i, 6) == "$vhost") + else if (!newline.compare(i, 6, "$vhost", 6)) { - result.append(user->dhost); + result.append(user->GetDisplayedHost()); i += 5; } + else if (!newline.compare(i, 12, "$requirement", 12)) + { + result.append(a->RequiredNick); + i += 11; + } else result.push_back(c); } @@ -363,27 +355,25 @@ class ModuleAlias : public Module } irc::tokenstream ss(result); - std::vector<std::string> pars; + CommandBase::Params pars; std::string command, token; - ss.GetToken(command); - while (ss.GetToken(token) && (pars.size() <= MAXPARAMETERS)) + ss.GetMiddle(command); + while (ss.GetTrailing(token)) { pars.push_back(token); } - ServerInstance->Parser->CallHandler(command, pars, user); - } - virtual void OnRehash(User* user) - { - ReadAliases(); - } + active = true; + ServerInstance->Parser.CallHandler(command, pars, user); + active = false; + } - virtual void Prioritize() + void Prioritize() CXX11_OVERRIDE { // Prioritise after spanningtree so that channel aliases show the alias before the effects. Module* linkmod = ServerInstance->Modules->Find("m_spanningtree.so"); - ServerInstance->Modules->SetPriority(this, I_OnUserMessage, PRIORITY_AFTER, &linkmod); + ServerInstance->Modules->SetPriority(this, I_OnUserPostMessage, PRIORITY_AFTER, linkmod); } }; diff --git a/src/modules/m_allowinvite.cpp b/src/modules/m_allowinvite.cpp index 08a5f542a..41edbc855 100644 --- a/src/modules/m_allowinvite.cpp +++ b/src/modules/m_allowinvite.cpp @@ -19,36 +19,22 @@ #include "inspircd.h" -/* $ModDesc: Provides support for channel mode +A, allowing /invite freely on a channel and extban A to deny specific users it */ - -class AllowInvite : public SimpleChannelModeHandler -{ - public: - AllowInvite(Module* Creator) : SimpleChannelModeHandler(Creator, "allowinvite", 'A') { } -}; - class ModuleAllowInvite : public Module { - AllowInvite ni; + SimpleChannelModeHandler ni; public: - ModuleAllowInvite() : ni(this) + ModuleAllowInvite() + : ni(this, "allowinvite", 'A') { } - void init() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(ni); - Implementation eventlist[] = { I_OnUserPreInvite, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + tokens["EXTBAN"].push_back('A'); } - virtual void On005Numeric(std::string &output) - { - ServerInstance->AddExtBanChar('A'); - } - - virtual ModResult OnUserPreInvite(User* user,User* dest,Channel* channel, time_t timeout) + ModResult OnUserPreInvite(User* user,User* dest,Channel* channel, time_t timeout) CXX11_OVERRIDE { if (IS_LOCAL(user)) { @@ -56,10 +42,10 @@ class ModuleAllowInvite : public Module if (res == MOD_RES_DENY) { // Matching extban, explicitly deny /invite - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You are banned from using INVITE", user->nick.c_str(), channel->name.c_str()); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, channel->name, "You are banned from using INVITE"); return res; } - if (channel->IsModeSet('A') || res == MOD_RES_ALLOW) + if (channel->IsModeSet(ni) || res == MOD_RES_ALLOW) { // Explicitly allow /invite return MOD_RES_ALLOW; @@ -69,11 +55,7 @@ class ModuleAllowInvite : public Module return MOD_RES_PASSTHRU; } - virtual ~ModuleAllowInvite() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for channel mode +A, allowing /invite freely on a channel and extban A to deny specific users it",VF_VENDOR); } diff --git a/src/modules/m_alltime.cpp b/src/modules/m_alltime.cpp index 38ae4b254..486ad1c18 100644 --- a/src/modules/m_alltime.cpp +++ b/src/modules/m_alltime.cpp @@ -21,38 +21,32 @@ #include "inspircd.h" -/* $ModDesc: Display timestamps from all servers connected to the network */ - class CommandAlltime : public Command { public: CommandAlltime(Module* Creator) : Command(Creator, "ALLTIME", 0) { flags_needed = 'o'; - translation.push_back(TR_END); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - char fmtdate[64]; - time_t now = ServerInstance->Time(); - strftime(fmtdate, sizeof(fmtdate), "%Y-%m-%d %H:%M:%S", gmtime(&now)); + const std::string fmtdate = InspIRCd::TimeString(ServerInstance->Time(), "%Y-%m-%d %H:%M:%S", true); - std::string msg = ":" + ServerInstance->Config->ServerName + " NOTICE " + user->nick + " :System time is " + fmtdate + " (" + ConvToStr(ServerInstance->Time()) + ") on " + ServerInstance->Config->ServerName; + std::string msg = "System time is " + fmtdate + " (" + ConvToStr(ServerInstance->Time()) + ") on " + ServerInstance->Config->ServerName; - user->SendText(msg); + user->WriteRemoteNotice(msg); /* we want this routed out! */ return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_OPT_BCAST; } }; - class Modulealltime : public Module { CommandAlltime mycommand; @@ -62,16 +56,7 @@ class Modulealltime : public Module { } - void init() - { - ServerInstance->Modules->AddService(mycommand); - } - - virtual ~Modulealltime() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Display timestamps from all servers connected to the network", VF_OPTCOMMON | VF_VENDOR); } diff --git a/src/modules/m_anticaps.cpp b/src/modules/m_anticaps.cpp new file mode 100644 index 000000000..6cb9b940b --- /dev/null +++ b/src/modules/m_anticaps.cpp @@ -0,0 +1,304 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2017 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/exemption.h" + +enum AntiCapsMethod +{ + ACM_BAN, + ACM_BLOCK, + ACM_MUTE, + ACM_KICK, + ACM_KICK_BAN +}; + +class AntiCapsSettings +{ + public: + const AntiCapsMethod method; + const uint16_t minlen; + const uint8_t percent; + + AntiCapsSettings(const AntiCapsMethod& Method, const uint16_t& MinLen, const uint8_t& Percent) + : method(Method) + , minlen(MinLen) + , percent(Percent) + { + } +}; + +class AntiCapsMode : public ParamMode<AntiCapsMode, SimpleExtItem<AntiCapsSettings> > +{ + private: + bool ParseMethod(irc::sepstream& stream, AntiCapsMethod& method) + { + std::string methodstr; + if (!stream.GetToken(methodstr)) + return false; + + if (irc::equals(methodstr, "ban")) + method = ACM_BAN; + else if (irc::equals(methodstr, "block")) + method = ACM_BLOCK; + else if (irc::equals(methodstr, "mute")) + method = ACM_MUTE; + else if (irc::equals(methodstr, "kick")) + method = ACM_KICK; + else if (irc::equals(methodstr, "kickban")) + method = ACM_KICK_BAN; + else + return false; + + return true; + } + + bool ParseMinimumLength(irc::sepstream& stream, uint16_t& minlen) + { + std::string minlenstr; + if (!stream.GetToken(minlenstr)) + return false; + + uint16_t result = ConvToNum<uint16_t>(minlenstr); + if (result < 1 || result > ServerInstance->Config->Limits.MaxLine) + return false; + + minlen = result; + return true; + } + + bool ParsePercent(irc::sepstream& stream, uint8_t& percent) + { + std::string percentstr; + if (!stream.GetToken(percentstr)) + return false; + + uint8_t result = ConvToNum<uint8_t>(percentstr); + if (result < 1 || result > 100) + return false; + + percent = result; + return true; + } + + public: + AntiCapsMode(Module* Creator) + : ParamMode<AntiCapsMode, SimpleExtItem<AntiCapsSettings> >(Creator, "anticaps", 'B') + { + } + + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE + { + irc::sepstream stream(parameter, ':'); + AntiCapsMethod method; + uint16_t minlen; + uint8_t percent; + + // Attempt to parse the method. + if (!ParseMethod(stream, method) || !ParseMinimumLength(stream, minlen) || !ParsePercent(stream, percent)) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, "Invalid anticaps mode parameter. Syntax: <ban|block|mute|kick|kickban>:{minlen}:{percent}.")); + return MODEACTION_DENY; + } + + ext.set(channel, new AntiCapsSettings(method, minlen, percent)); + return MODEACTION_ALLOW; + } + + void SerializeParam(Channel* chan, const AntiCapsSettings* acs, std::string& out) + { + switch (acs->method) + { + case ACM_BAN: + out.append("ban"); + break; + case ACM_BLOCK: + out.append("block"); + break; + case ACM_MUTE: + out.append("mute"); + break; + case ACM_KICK: + out.append("kick"); + break; + case ACM_KICK_BAN: + out.append("kickban"); + break; + default: + out.append("unknown~"); + out.append(ConvToStr(acs->method)); + break; + } + out.push_back(':'); + out.append(ConvToStr(acs->minlen)); + out.push_back(':'); + out.append(ConvToStr(acs->percent)); + } +}; + +class ModuleAntiCaps : public Module +{ + private: + CheckExemption::EventProvider exemptionprov; + std::bitset<UCHAR_MAX> uppercase; + std::bitset<UCHAR_MAX> lowercase; + AntiCapsMode mode; + + void CreateBan(Channel* channel, User* user, bool mute) + { + std::string banmask(mute ? "m:" : ""); + banmask.append("*!*@"); + banmask.append(user->GetDisplayedHost()); + + Modes::ChangeList changelist; + changelist.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), banmask); + ServerInstance->Modes->Process(ServerInstance->FakeClient, channel, NULL, changelist); + } + + void InformUser(Channel* channel, User* user, const std::string& message) + { + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, channel, message + " and was blocked."); + } + + public: + ModuleAntiCaps() + : exemptionprov(this) + , mode(this) + { + } + + void ReadConfig(ConfigStatus&) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("anticaps"); + + uppercase.reset(); + const std::string upper = tag->getString("uppercase", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + for (std::string::const_iterator iter = upper.begin(); iter != upper.end(); ++iter) + uppercase.set(static_cast<unsigned char>(*iter)); + + lowercase.reset(); + const std::string lower = tag->getString("lowercase", "abcdefghijklmnopqrstuvwxyz"); + for (std::string::const_iterator iter = lower.begin(); iter != lower.end(); ++iter) + lowercase.set(static_cast<unsigned char>(*iter)); + } + + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE + { + // We only want to operate on messages from local users. + if (!IS_LOCAL(user)) + return MOD_RES_PASSTHRU; + + // The mode can only be applied to channels. + if (target.type != MessageTarget::TYPE_CHANNEL) + return MOD_RES_PASSTHRU; + + // We only act if the channel has the mode set. + Channel* channel = target.Get<Channel>(); + if (!channel->IsModeSet(&mode)) + return MOD_RES_PASSTHRU; + + // If the user is exempt from anticaps then we don't need + // to do anything else. + ModResult result = CheckExemption::Call(exemptionprov, user, channel, "anticaps"); + if (result == MOD_RES_ALLOW) + return MOD_RES_PASSTHRU; + + // If the message is a CTCP then we skip it unless it is + // an ACTION in which case we just check against the body. + std::string ctcpname; + std::string msgbody(details.text); + if (details.IsCTCP(ctcpname, msgbody)) + { + // If the CTCP is not an action then skip it. + if (!irc::equals(ctcpname, "ACTION")) + return MOD_RES_PASSTHRU; + } + + // Retrieve the anticaps config. This should never be + // null but its better to be safe than sorry. + AntiCapsSettings* config = mode.ext.get(channel); + if (!config) + return MOD_RES_PASSTHRU; + + // If the message is shorter than the minimum length then + // we don't need to do anything else. + size_t length = msgbody.length(); + if (length < config->minlen) + return MOD_RES_PASSTHRU; + + // Count the characters to see how many upper case and + // ignored (non upper or lower) characters there are. + size_t upper = 0; + for (std::string::const_iterator iter = msgbody.begin(); iter != msgbody.end(); ++iter) + { + unsigned char chr = static_cast<unsigned char>(*iter); + if (uppercase.test(chr)) + upper += 1; + else if (!lowercase.test(chr)) + length -= 1; + } + + // If the message was entirely symbols then the message + // can't contain any upper case letters. + if (length == 0) + return MOD_RES_PASSTHRU; + + // Calculate the percentage. + double percent = round((upper * 100) / length); + if (percent < config->percent) + return MOD_RES_PASSTHRU; + + const std::string message = InspIRCd::Format("Your message exceeded the %d%% upper case character threshold for %s", + config->percent, channel->name.c_str()); + + switch (config->method) + { + case ACM_BAN: + InformUser(channel, user, message); + CreateBan(channel, user, false); + break; + + case ACM_BLOCK: + InformUser(channel, user, message); + break; + + case ACM_MUTE: + InformUser(channel, user, message); + CreateBan(channel, user, true); + break; + + case ACM_KICK: + channel->KickUser(ServerInstance->FakeClient, user, message); + break; + + case ACM_KICK_BAN: + CreateBan(channel, user, false); + channel->KickUser(ServerInstance->FakeClient, user, message); + break; + } + return MOD_RES_DENY; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for punishing users that send capitalised messages.", VF_COMMON|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleAntiCaps) diff --git a/src/modules/m_auditorium.cpp b/src/modules/m_auditorium.cpp index 2a8edb9d4..8485f1d7a 100644 --- a/src/modules/m_auditorium.cpp +++ b/src/modules/m_auditorium.cpp @@ -21,56 +21,58 @@ #include "inspircd.h" +#include "modules/exemption.h" -/* $ModDesc: Allows for auditorium channels (+u) where nobody can see others joining and parting or the nick list */ - -class AuditoriumMode : public ModeHandler +class AuditoriumMode : public SimpleChannelModeHandler { public: - AuditoriumMode(Module* Creator) : ModeHandler(Creator, "auditorium", 'u', PARAM_NONE, MODETYPE_CHANNEL) + AuditoriumMode(Module* Creator) : SimpleChannelModeHandler(Creator, "auditorium", 'u') { - levelrequired = OP_VALUE; + ranktoset = ranktounset = OP_VALUE; } +}; - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - if (channel->IsModeSet(this) == adding) - return MODEACTION_DENY; - channel->SetMode(this, adding); - return MODEACTION_ALLOW; - } +class ModuleAuditorium; + +namespace +{ + +/** Hook handler for join client protocol events. + * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY). + * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module + * such as delayjoin or hostcycle generates a join. + */ +class JoinHook : public ClientProtocol::EventHook +{ + ModuleAuditorium* const parentmod; + bool active; + + public: + JoinHook(ModuleAuditorium* mod); + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE; + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE; }; +} + class ModuleAuditorium : public Module { - private: + CheckExemption::EventProvider exemptionprov; AuditoriumMode aum; bool OpsVisible; bool OpsCanSee; bool OperCanSee; - public: - ModuleAuditorium() : aum(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(aum); + JoinHook joinhook; - OnRehash(NULL); - - Implementation eventlist[] = { - I_OnUserJoin, I_OnUserPart, I_OnUserKick, - I_OnBuildNeighborList, I_OnNamesListItem, I_OnSendWhoLine, - I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ~ModuleAuditorium() + public: + ModuleAuditorium() + : exemptionprov(this) + , aum(this) + , joinhook(this) { } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("auditorium"); OpsVisible = tag->getBool("opvisible"); @@ -78,7 +80,7 @@ class ModuleAuditorium : public Module OperCanSee = tag->getBool("opercansee", true); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows for auditorium channels (+u) where nobody can see others joining and parting or the nick list", VF_VENDOR); } @@ -89,7 +91,7 @@ class ModuleAuditorium : public Module if (!memb->chan->IsModeSet(&aum)) return true; - ModResult res = ServerInstance->OnCheckExemption(memb->user, memb->chan, "auditorium-vis"); + ModResult res = CheckExemption::Call(exemptionprov, memb->user, memb->chan, "auditorium-vis"); return res.check(OpsVisible && memb->getRank() >= OP_VALUE); } @@ -105,26 +107,23 @@ class ModuleAuditorium : public Module return true; // Can you see the list by permission? - ModResult res = ServerInstance->OnCheckExemption(issuer,memb->chan,"auditorium-see"); + ModResult res = CheckExemption::Call(exemptionprov, issuer, memb->chan, "auditorium-see"); if (res.check(OpsCanSee && memb->chan->GetPrefixValue(issuer) >= OP_VALUE)) return true; return false; } - void OnNamesListItem(User* issuer, Membership* memb, std::string &prefixes, std::string &nick) + ModResult OnNamesListItem(User* issuer, Membership* memb, std::string& prefixes, std::string& nick) CXX11_OVERRIDE { - // Some module already hid this from being displayed, don't bother - if (nick.empty()) - return; - if (IsVisible(memb)) - return; + return MOD_RES_PASSTHRU; if (CanSee(issuer, memb)) - return; + return MOD_RES_PASSTHRU; - nick.clear(); + // Don't display this user in the NAMES list + return MOD_RES_DENY; } /** Build CUList for showing this join/part/kick */ @@ -133,43 +132,40 @@ class ModuleAuditorium : public Module if (IsVisible(memb)) return; - const UserMembList* users = memb->chan->GetUsers(); - for(UserMembCIter i = users->begin(); i != users->end(); i++) + const Channel::MemberMap& users = memb->chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) { if (IS_LOCAL(i->first) && !CanSee(i->first, memb)) excepts.insert(i->first); } } - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) + void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE { BuildExcept(memb, excepts); } - void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) + void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) CXX11_OVERRIDE { BuildExcept(memb, excepts); } - void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) + void OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) CXX11_OVERRIDE { - BuildExcept(memb, excepts); - } - - void OnBuildNeighborList(User* source, UserChanList &include, std::map<User*,bool> &exception) - { - UCListIter i = include.begin(); - while (i != include.end()) + for (IncludeChanList::iterator i = include.begin(); i != include.end(); ) { - Channel* c = *i++; - Membership* memb = c->GetUser(source); - if (!memb || IsVisible(memb)) + Membership* memb = *i; + if (IsVisible(memb)) + { + ++i; continue; + } + // this channel should not be considered when listing my neighbors - include.erase(c); + i = include.erase(i); // however, that might hide me from ops that can see me... - const UserMembList* users = c->GetUsers(); - for(UserMembCIter j = users->begin(); j != users->end(); j++) + const Channel::MemberMap& users = memb->chan->GetUsers(); + for(Channel::MemberMap::const_iterator j = users.begin(); j != users.end(); ++j) { if (IS_LOCAL(j->first) && CanSee(j->first, memb)) exception[j->first] = true; @@ -177,18 +173,37 @@ class ModuleAuditorium : public Module } } - void OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, std::string& line) + ModResult OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, Membership* memb, Numeric::Numeric& numeric) CXX11_OVERRIDE { - Channel* channel = ServerInstance->FindChan(params[0]); - if (!channel) - return; - Membership* memb = channel->GetUser(user); - if ((!memb) || (IsVisible(memb))) - return; + if (!memb) + return MOD_RES_PASSTHRU; + if (IsVisible(memb)) + return MOD_RES_PASSTHRU; if (CanSee(source, memb)) - return; - line.clear(); + return MOD_RES_PASSTHRU; + return MOD_RES_DENY; } }; +JoinHook::JoinHook(ModuleAuditorium* mod) + : ClientProtocol::EventHook(mod, "JOIN", 10) + , parentmod(mod) +{ +} + +void JoinHook::OnEventInit(const ClientProtocol::Event& ev) +{ + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + active = !parentmod->IsVisible(join.GetMember()); +} + +ModResult JoinHook::OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) +{ + if (!active) + return MOD_RES_PASSTHRU; + + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + return ((parentmod->CanSee(user, join.GetMember())) ? MOD_RES_PASSTHRU : MOD_RES_DENY); +} + MODULE_INIT(ModuleAuditorium) diff --git a/src/modules/m_autoop.cpp b/src/modules/m_autoop.cpp index 0c0e8f579..a09d0d9e4 100644 --- a/src/modules/m_autoop.cpp +++ b/src/modules/m_autoop.cpp @@ -19,9 +19,7 @@ #include "inspircd.h" -#include "u_listmode.h" - -/* $ModDesc: Provides support for the +w channel mode, autoop list */ +#include "listmode.h" /** Handles +w channel mode */ @@ -30,36 +28,31 @@ class AutoOpList : public ListModeBase public: AutoOpList(Module* Creator) : ListModeBase(Creator, "autoop", 'w', "End of Channel Access List", 910, 911, true) { - levelrequired = OP_VALUE; + ranktoset = ranktounset = OP_VALUE; tidy = false; } - ModeHandler* FindMode(const std::string& mid) + PrefixMode* FindMode(const std::string& mid) { if (mid.length() == 1) - return ServerInstance->Modes->FindMode(mid[0], MODETYPE_CHANNEL); - for(char c='A'; c <= 'z'; c++) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL); - if (mh && mh->name == mid) - return mh; - } - return NULL; + return ServerInstance->Modes->FindPrefixMode(mid[0]); + + ModeHandler* mh = ServerInstance->Modes->FindMode(mid, MODETYPE_CHANNEL); + return mh ? mh->IsPrefixMode() : NULL; } - ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) + ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) CXX11_OVERRIDE { std::string::size_type pos = parameter.find(':'); if (pos == 0 || pos == std::string::npos) return adding ? MOD_RES_DENY : MOD_RES_PASSTHRU; unsigned int mylevel = channel->GetPrefixValue(source); - std::string mid = parameter.substr(0, pos); - ModeHandler* mh = FindMode(mid); + std::string mid(parameter, 0, pos); + PrefixMode* mh = FindMode(mid); - if (adding && (!mh || !mh->GetPrefixRank())) + if (adding && !mh) { - source->WriteNumeric(415, "%s %s :Cannot find prefix mode '%s' for autoop", - source->nick.c_str(), mid.c_str(), mid.c_str()); + source->WriteNumeric(ERR_UNKNOWNMODE, mid, InspIRCd::Format("Cannot find prefix mode '%s' for autoop", mid.c_str())); return MOD_RES_DENY; } else if (!mh) @@ -68,10 +61,9 @@ class AutoOpList : public ListModeBase std::string dummy; if (mh->AccessCheck(source, channel, dummy, true) == MOD_RES_DENY) return MOD_RES_DENY; - if (mh->GetLevelRequired() > mylevel) + if (mh->GetLevelRequired(true) > mylevel) { - source->WriteNumeric(482, "%s %s :You must be able to set mode '%s' to include it in an autoop", - source->nick.c_str(), channel->name.c_str(), mid.c_str()); + source->WriteNumeric(ERR_CHANOPRIVSNEEDED, channel->name, InspIRCd::Format("You must be able to set mode '%s' to include it in an autoop", mid.c_str())); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; @@ -82,62 +74,42 @@ class ModuleAutoOp : public Module { AutoOpList mh; -public: + public: ModuleAutoOp() : mh(this) { } - void init() - { - ServerInstance->Modules->AddService(mh); - mh.DoImplements(this); - - Implementation list[] = { I_OnPostJoin, }; - ServerInstance->Modules->Attach(list, this, sizeof(list)/sizeof(Implementation)); - } - - void OnPostJoin(Membership *memb) + void OnPostJoin(Membership *memb) CXX11_OVERRIDE { if (!IS_LOCAL(memb->user)) return; - modelist* list = mh.extItem.get(memb->chan); + ListModeBase::ModeList* list = mh.GetList(memb->chan); if (list) { - std::string modeline("+"); - std::vector<std::string> modechange; - modechange.push_back(memb->chan->name); - for (modelist::iterator it = list->begin(); it != list->end(); it++) + Modes::ChangeList changelist; + for (ListModeBase::ModeList::iterator it = list->begin(); it != list->end(); it++) { std::string::size_type colon = it->mask.find(':'); if (colon == std::string::npos) continue; if (memb->chan->CheckBan(memb->user, it->mask.substr(colon+1))) { - ModeHandler* given = mh.FindMode(it->mask.substr(0, colon)); - if (given && given->GetPrefixRank()) - modeline.push_back(given->GetModeChar()); + PrefixMode* given = mh.FindMode(it->mask.substr(0, colon)); + if (given) + changelist.push_add(given, memb->user->nick); } } - modechange.push_back(modeline); - for(std::string::size_type i = modeline.length(); i > 1; --i) // we use "i > 1" instead of "i" so we skip the + - modechange.push_back(memb->user->nick); - if(modechange.size() >= 3) - ServerInstance->SendGlobalMode(modechange, ServerInstance->FakeClient); + ServerInstance->Modes->Process(ServerInstance->FakeClient, memb->chan, NULL, changelist); } } - void OnSyncChannel(Channel* chan, Module* proto, void* opaque) - { - mh.DoSyncChannel(chan, proto, opaque); - } - - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { mh.DoRehash(); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the +w channel mode", VF_VENDOR); } diff --git a/src/modules/m_banexception.cpp b/src/modules/m_banexception.cpp index 7531c5c12..3905cdf6a 100644 --- a/src/modules/m_banexception.cpp +++ b/src/modules/m_banexception.cpp @@ -22,10 +22,7 @@ #include "inspircd.h" -#include "u_listmode.h" - -/* $ModDesc: Provides support for the +e channel mode */ -/* $ModDep: ../../include/u_listmode.h */ +#include "listmode.h" /* Written by Om<om@inspircd.org>, April 2005. */ /* Rewritten to use the listmode utility by Om, December 2005 */ @@ -49,85 +46,63 @@ class ModuleBanException : public Module { BanException be; -public: + public: ModuleBanException() : be(this) { } - void init() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(be); - - be.DoImplements(this); - Implementation list[] = { I_OnRehash, I_On005Numeric, I_OnExtBanCheck, I_OnCheckChannelBan }; - ServerInstance->Modules->Attach(list, this, sizeof(list)/sizeof(Implementation)); + tokens["EXCEPTS"] = ConvToStr(be.GetModeChar()); } - void On005Numeric(std::string &output) + ModResult OnExtBanCheck(User *user, Channel *chan, char type) CXX11_OVERRIDE { - output.append(" EXCEPTS=e"); - } + ListModeBase::ModeList* list = be.GetList(chan); + if (!list) + return MOD_RES_PASSTHRU; - ModResult OnExtBanCheck(User *user, Channel *chan, char type) - { - if (chan != NULL) + for (ListModeBase::ModeList::iterator it = list->begin(); it != list->end(); it++) { - modelist *list = be.extItem.get(chan); - - if (!list) - return MOD_RES_PASSTHRU; + if (it->mask.length() <= 2 || it->mask[0] != type || it->mask[1] != ':') + continue; - for (modelist::iterator it = list->begin(); it != list->end(); it++) + if (chan->CheckBan(user, it->mask.substr(2))) { - if (it->mask.length() <= 2 || it->mask[0] != type || it->mask[1] != ':') - continue; - - if (chan->CheckBan(user, it->mask.substr(2))) - { - // They match an entry on the list, so let them pass this. - return MOD_RES_ALLOW; - } + // They match an entry on the list, so let them pass this. + return MOD_RES_ALLOW; } } return MOD_RES_PASSTHRU; } - ModResult OnCheckChannelBan(User* user, Channel* chan) + ModResult OnCheckChannelBan(User* user, Channel* chan) CXX11_OVERRIDE { - if (chan) + ListModeBase::ModeList* list = be.GetList(chan); + if (!list) { - modelist *list = be.extItem.get(chan); - - if (!list) - { - // No list, proceed normally - return MOD_RES_PASSTHRU; - } + // No list, proceed normally + return MOD_RES_PASSTHRU; + } - for (modelist::iterator it = list->begin(); it != list->end(); it++) + for (ListModeBase::ModeList::iterator it = list->begin(); it != list->end(); it++) + { + if (chan->CheckBan(user, it->mask)) { - if (chan->CheckBan(user, it->mask)) - { - // They match an entry on the list, so let them in. - return MOD_RES_ALLOW; - } + // They match an entry on the list, so let them in. + return MOD_RES_ALLOW; } } return MOD_RES_PASSTHRU; } - void OnSyncChannel(Channel* chan, Module* proto, void* opaque) - { - be.DoSyncChannel(chan, proto, opaque); - } - - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { be.DoRehash(); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the +e channel mode", VF_VENDOR); } diff --git a/src/modules/m_banredirect.cpp b/src/modules/m_banredirect.cpp index 1d35c2934..5ec75f13c 100644 --- a/src/modules/m_banredirect.cpp +++ b/src/modules/m_banredirect.cpp @@ -23,9 +23,7 @@ #include "inspircd.h" -#include "u_listmode.h" - -/* $ModDesc: Allows an extended ban (+b) syntax redirecting banned users to another channel */ +#include "listmode.h" /* Originally written by Om, January 2009 */ @@ -43,18 +41,20 @@ class BanRedirectEntry }; typedef std::vector<BanRedirectEntry> BanRedirectList; -typedef std::deque<std::string> StringDeque; class BanRedirect : public ModeWatcher { + ChanModeReference ban; public: SimpleExtItem<BanRedirectList> extItem; - BanRedirect(Module* parent) : ModeWatcher(parent, 'b', MODETYPE_CHANNEL), - extItem("banredirect", parent) + BanRedirect(Module* parent) + : ModeWatcher(parent, "ban", MODETYPE_CHANNEL) + , ban(parent, "ban") + , extItem("banredirect", ExtensionItem::EXT_CHANNEL, parent) { } - bool BeforeMode(User* source, User* dest, Channel* channel, std::string ¶m, bool adding, ModeType type) + bool BeforeMode(User* source, User* dest, Channel* channel, std::string& param, bool adding) CXX11_OVERRIDE { /* nick!ident@host -> nick!ident@host * nick!ident@host#chan -> nick!ident@host#chan @@ -63,14 +63,13 @@ class BanRedirect : public ModeWatcher * nick#chan -> nick!*@*#chan */ - if(channel && (type == MODETYPE_CHANNEL) && param.length()) + if ((channel) && !param.empty()) { BanRedirectList* redirects; std::string mask[4]; enum { NICK, IDENT, HOST, CHAN } current = NICK; std::string::iterator start_pos = param.begin(); - long maxbans = channel->GetMaxBans(); if (param.length() >= 2 && param[1] == ':') return true; @@ -78,9 +77,12 @@ class BanRedirect : public ModeWatcher if (param.find('#') == std::string::npos) return true; - if(adding && (channel->bans.size() > static_cast<unsigned>(maxbans))) + ListModeBase* banlm = static_cast<ListModeBase*>(*ban); + unsigned int maxbans = banlm->GetLimit(channel); + ListModeBase::ModeList* list = banlm->GetList(channel); + if ((list) && (adding) && (maxbans <= list->size())) { - source->WriteNumeric(478, "%s %s :Channel ban list for %s is full (maximum entries for this channel is %ld)", source->nick.c_str(), channel->name.c_str(), channel->name.c_str(), maxbans); + source->WriteNumeric(ERR_BANLISTFULL, channel->name, InspIRCd::Format("Channel ban list for %s is full (maximum entries for this channel is %u)", channel->name.c_str(), maxbans)); return false; } @@ -89,23 +91,25 @@ class BanRedirect : public ModeWatcher switch(*curr) { case '!': + if (current != NICK) + break; mask[current].assign(start_pos, curr); current = IDENT; start_pos = curr+1; break; case '@': + if (current != IDENT) + break; mask[current].assign(start_pos, curr); current = HOST; start_pos = curr+1; break; case '#': - /* bug #921: don't barf when redirecting to ## channels */ - if (current != CHAN) - { - mask[current].assign(start_pos, curr); - current = CHAN; - start_pos = curr; - } + if (current == CHAN) + break; + mask[current].assign(start_pos, curr); + current = CHAN; + start_pos = curr; break; } } @@ -144,27 +148,27 @@ class BanRedirect : public ModeWatcher { if (adding && IS_LOCAL(source)) { - if (!ServerInstance->IsChannel(mask[CHAN].c_str(), ServerInstance->Config->Limits.ChanMax)) + if (!ServerInstance->IsChannel(mask[CHAN])) { - source->WriteNumeric(403, "%s %s :Invalid channel name in redirection (%s)", source->nick.c_str(), channel->name.c_str(), mask[CHAN].c_str()); + source->WriteNumeric(ERR_NOSUCHCHANNEL, channel->name, InspIRCd::Format("Invalid channel name in redirection (%s)", mask[CHAN].c_str())); return false; } Channel *c = ServerInstance->FindChan(mask[CHAN]); if (!c) { - source->WriteNumeric(690, "%s :Target channel %s must exist to be set as a redirect.",source->nick.c_str(),mask[CHAN].c_str()); + source->WriteNumeric(690, InspIRCd::Format("Target channel %s must exist to be set as a redirect.", mask[CHAN].c_str())); return false; } else if (adding && c->GetPrefixValue(source) < OP_VALUE) { - source->WriteNumeric(690, "%s :You must be opped on %s to set it as a redirect.",source->nick.c_str(), mask[CHAN].c_str()); + source->WriteNumeric(690, InspIRCd::Format("You must be opped on %s to set it as a redirect.", mask[CHAN].c_str())); return false; } - if (assign(channel->name) == mask[CHAN]) + if (irc::equals(channel->name, mask[CHAN])) { - source->WriteNumeric(690, "%s %s :You cannot set a ban redirection to the channel the ban is on", source->nick.c_str(), channel->name.c_str()); + source->WriteNumeric(690, channel->name, "You cannot set a ban redirection to the channel the ban is on"); return false; } } @@ -183,7 +187,7 @@ class BanRedirect : public ModeWatcher for (BanRedirectList::iterator redir = redirects->begin(); redir != redirects->end(); ++redir) { // Mimic the functionality used when removing the mode - if ((irc::string(redir->targetchan.c_str()) == irc::string(mask[CHAN].c_str())) && (irc::string(redir->banmask.c_str()) == irc::string(param.c_str()))) + if (irc::equals(redir->targetchan, mask[CHAN]) && irc::equals(redir->banmask, param)) { // Make sure the +b handler will still set the right ban param.append(mask[CHAN]); @@ -210,8 +214,7 @@ class BanRedirect : public ModeWatcher for(BanRedirectList::iterator redir = redirects->begin(); redir != redirects->end(); redir++) { - /* Ugly as fuck */ - if((irc::string(redir->targetchan.c_str()) == irc::string(mask[CHAN].c_str())) && (irc::string(redir->banmask.c_str()) == irc::string(param.c_str()))) + if ((irc::equals(redir->targetchan, mask[CHAN])) && (irc::equals(redir->banmask, param))) { redirects->erase(redir); @@ -239,61 +242,42 @@ class ModuleBanRedirect : public Module { BanRedirect re; bool nofollow; + ChanModeReference limitmode; + ChanModeReference redirectmode; public: ModuleBanRedirect() - : re(this) + : re(this) + , nofollow(false) + , limitmode(this, "limit") + , redirectmode(this, "redirect") { - nofollow = false; - } - - - void init() - { - if(!ServerInstance->Modes->AddModeWatcher(&re)) - throw ModuleException("Could not add mode watcher"); - - ServerInstance->Modules->AddService(re.extItem); - Implementation list[] = { I_OnUserPreJoin }; - ServerInstance->Modules->Attach(list, this, sizeof(list)/sizeof(Implementation)); } - virtual void OnCleanup(int target_type, void* item) + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE { - if(target_type == TYPE_CHANNEL) + if (type == ExtensionItem::EXT_CHANNEL) { Channel* chan = static_cast<Channel*>(item); BanRedirectList* redirects = re.extItem.get(chan); if(redirects) { - irc::modestacker modestack(false); - StringDeque stackresult; - std::vector<std::string> mode_junk; - mode_junk.push_back(chan->name); + ModeHandler* ban = ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL); + Modes::ChangeList changelist; for(BanRedirectList::iterator i = redirects->begin(); i != redirects->end(); i++) - { - modestack.Push('b', i->targetchan.insert(0, i->banmask)); - } + changelist.push_remove(ban, i->targetchan.insert(0, i->banmask)); for(BanRedirectList::iterator i = redirects->begin(); i != redirects->end(); i++) - { - modestack.PushPlus(); - modestack.Push('b', i->banmask); - } + changelist.push_add(ban, i->banmask); - while(modestack.GetStackedLine(stackresult)) - { - mode_junk.insert(mode_junk.end(), stackresult.begin(), stackresult.end()); - ServerInstance->SendMode(mode_junk, ServerInstance->FakeClient); - mode_junk.erase(mode_junk.begin() + 1, mode_junk.end()); - } + ServerInstance->Modes->Process(ServerInstance->FakeClient, chan, NULL, changelist, ModeParser::MODE_LOCALONLY); } } } - virtual ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { if (chan) { @@ -337,19 +321,19 @@ class ModuleBanRedirect : public Module std::string destlimit; if (destchan) - destlimit = destchan->GetModeParameter('l'); + destlimit = destchan->GetModeParameter(limitmode); - if(destchan && ServerInstance->Modules->Find("m_redirect.so") && destchan->IsModeSet('L') && !destlimit.empty() && (destchan->GetUserCounter() >= atoi(destlimit.c_str()))) + if(destchan && destchan->IsModeSet(redirectmode) && !destlimit.empty() && (destchan->GetUserCounter() >= ConvToNum<size_t>(destlimit))) { - user->WriteNumeric(474, "%s %s :Cannot join channel (You are banned)", user->nick.c_str(), chan->name.c_str()); + user->WriteNumeric(ERR_BANNEDFROMCHAN, chan->name, "Cannot join channel (You are banned)"); return MOD_RES_DENY; } else { - user->WriteNumeric(474, "%s %s :Cannot join channel (You are banned)", user->nick.c_str(), chan->name.c_str()); - user->WriteNumeric(470, "%s %s %s :You are banned from this channel, so you are automatically transferred to the redirected channel.", user->nick.c_str(), chan->name.c_str(), redir->targetchan.c_str()); + user->WriteNumeric(ERR_BANNEDFROMCHAN, chan->name, "Cannot join channel (You are banned)"); + user->WriteNumeric(470, chan->name, redir->targetchan, "You are banned from this channel, so you are automatically transferred to the redirected channel."); nofollow = true; - Channel::JoinUser(user, redir->targetchan.c_str(), false, "", false, ServerInstance->Time()); + Channel::JoinUser(user, redir->targetchan); nofollow = false; return MOD_RES_DENY; } @@ -360,14 +344,7 @@ class ModuleBanRedirect : public Module return MOD_RES_PASSTHRU; } - virtual ~ModuleBanRedirect() - { - /* XXX is this the best place to do this? */ - if (!ServerInstance->Modes->DelModeWatcher(&re)) - ServerInstance->Logs->Log("m_banredirect.so", DEBUG, "Failed to delete modewatcher!"); - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows an extended ban (+b) syntax redirecting banned users to another channel", VF_COMMON|VF_VENDOR); } diff --git a/src/modules/m_bcrypt.cpp b/src/modules/m_bcrypt.cpp new file mode 100644 index 000000000..59ee1556c --- /dev/null +++ b/src/modules/m_bcrypt.cpp @@ -0,0 +1,102 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/// $CompilerFlags: -Ivendor_directory("bcrypt") + + +#include "inspircd.h" +#include "modules/hash.h" + +#include <crypt_blowfish.c> + +class BCryptProvider : public HashProvider +{ + private: + std::string Salt() + { + char entropy[16]; + for (unsigned int i = 0; i < sizeof(entropy); ++i) + entropy[i] = ServerInstance->GenRandomInt(0xFF); + + char salt[32]; + if (!_crypt_gensalt_blowfish_rn("$2a$", rounds, entropy, sizeof(entropy), salt, sizeof(salt))) + throw ModuleException("Could not generate salt - this should never happen"); + + return salt; + } + + public: + unsigned int rounds; + + std::string Generate(const std::string& data, const std::string& salt) + { + char hash[64]; + _crypt_blowfish_rn(data.c_str(), salt.c_str(), hash, sizeof(hash)); + return hash; + } + + std::string GenerateRaw(const std::string& data) CXX11_OVERRIDE + { + return Generate(data, Salt()); + } + + bool Compare(const std::string& input, const std::string& hash) CXX11_OVERRIDE + { + std::string ret = Generate(input, hash); + if (ret.empty()) + return false; + + if (ret == hash) + return true; + return false; + } + + std::string ToPrintable(const std::string& raw) CXX11_OVERRIDE + { + return raw; + } + + BCryptProvider(Module* parent) + : HashProvider(parent, "bcrypt", 60) + , rounds(10) + { + } +}; + +class ModuleBCrypt : public Module +{ + BCryptProvider bcrypt; + + public: + ModuleBCrypt() : bcrypt(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* conf = ServerInstance->Config->ConfValue("bcrypt"); + bcrypt.rounds = conf->getUInt("rounds", 10, 1); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Implements bcrypt hashing", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleBCrypt) diff --git a/src/modules/m_blockamsg.cpp b/src/modules/m_blockamsg.cpp index be861447f..c4af2fd7f 100644 --- a/src/modules/m_blockamsg.cpp +++ b/src/modules/m_blockamsg.cpp @@ -23,8 +23,6 @@ #include "inspircd.h" -/* $ModDesc: Attempt to block /amsg, at least some of the irritating mIRC scripts. */ - enum BlockAction { IBLOCK_KILL, IBLOCK_KILLOPERS, IBLOCK_NOTICE, IBLOCK_NOTICEOPERS, IBLOCK_SILENT }; /* IBLOCK_NOTICE - Send a notice to the user informing them of what happened. * IBLOCK_NOTICEOPERS - Send a notice to the user informing them and send an oper notice. @@ -37,64 +35,53 @@ enum BlockAction { IBLOCK_KILL, IBLOCK_KILLOPERS, IBLOCK_NOTICE, IBLOCK_NOTICEOP */ class BlockedMessage { -public: + public: std::string message; - irc::string target; + std::string target; time_t sent; - BlockedMessage(const std::string &msg, const irc::string &tgt, time_t when) - : message(msg), target(tgt), sent(when) + BlockedMessage(const std::string& msg, const std::string& tgt, time_t when) + : message(msg), target(tgt), sent(when) { } }; class ModuleBlockAmsg : public Module { - int ForgetDelay; + unsigned int ForgetDelay; BlockAction action; SimpleExtItem<BlockedMessage> blockamsg; public: - ModuleBlockAmsg() : blockamsg("blockamsg", this) - { - } - - void init() - { - this->OnRehash(NULL); - ServerInstance->Modules->AddService(blockamsg); - Implementation eventlist[] = { I_OnRehash, I_OnPreCommand }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleBlockAmsg() + ModuleBlockAmsg() + : blockamsg("blockamsg", ExtensionItem::EXT_USER, this) { } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Attempt to block /amsg, at least some of the irritating mIRC scripts.",VF_VENDOR); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("blockamsg"); - ForgetDelay = tag->getInt("delay", -1); + ForgetDelay = tag->getDuration("delay", 3); std::string act = tag->getString("action"); - if(act == "notice") + if (stdalgo::string::equalsci(act, "notice")) action = IBLOCK_NOTICE; - else if(act == "noticeopers") + else if (stdalgo::string::equalsci(act, "noticeopers")) action = IBLOCK_NOTICEOPERS; - else if(act == "silent") + else if (stdalgo::string::equalsci(act, "silent")) action = IBLOCK_SILENT; - else if(act == "kill") + else if (stdalgo::string::equalsci(act, "kill")) action = IBLOCK_KILL; else action = IBLOCK_KILLOPERS; } - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { // Don't do anything with unregistered users if (user->registered != REG_ALL) @@ -102,33 +89,24 @@ class ModuleBlockAmsg : public Module if ((validated) && (parameters.size() >= 2) && ((command == "PRIVMSG") || (command == "NOTICE"))) { - // parameters[0] should have the target(s) in it. - // I think it will be faster to first check if there are any commas, and if there are then try and parse it out. - // Most messages have a single target so... + // parameters[0] is the target list, count how many channels are there + unsigned int targets = 0; + // Is the first target a channel? + if (*parameters[0].c_str() == '#') + targets = 1; - int targets = 1; - int userchans = 0; - - if(*parameters[0].c_str() != '#') + for (const char* c = parameters[0].c_str(); *c; c++) { - // Decrement if the first target wasn't a channel. - targets--; - } - - for(const char* c = parameters[0].c_str(); *c; c++) - if((*c == ',') && *(c+1) && (*(c+1) == '#')) + if ((*c == ',') && (*(c+1) == '#')) targets++; + } /* targets should now contain the number of channel targets the msg/notice was pointed at. * If the msg/notice was a PM there should be no channel targets and 'targets' should = 0. * We don't want to block PMs so... */ - if(targets == 0) - { + if (targets == 0) return MOD_RES_PASSTHRU; - } - - userchans = user->chans.size(); // Check that this message wasn't already sent within a few seconds. BlockedMessage* m = blockamsg.get(user); @@ -138,30 +116,30 @@ class ModuleBlockAmsg : public Module // OR // The number of target channels is equal to the number of channels the sender is on..a little suspicious. // Check it's more than 1 too, or else users on one channel would have fun. - if((m && (m->message == parameters[1]) && (m->target != parameters[0]) && (ForgetDelay != -1) && (m->sent >= ServerInstance->Time()-ForgetDelay)) || ((targets > 1) && (targets == userchans))) + if ((m && (m->message == parameters[1]) && (!irc::equals(m->target, parameters[0])) && ForgetDelay && (m->sent >= ServerInstance->Time()-ForgetDelay)) || ((targets > 1) && (targets == user->chans.size()))) { // Block it... - if(action == IBLOCK_KILLOPERS || action == IBLOCK_NOTICEOPERS) + if (action == IBLOCK_KILLOPERS || action == IBLOCK_NOTICEOPERS) ServerInstance->SNO->WriteToSnoMask('a', "%s had an /amsg or /ame denied", user->nick.c_str()); - if(action == IBLOCK_KILL || action == IBLOCK_KILLOPERS) + if (action == IBLOCK_KILL || action == IBLOCK_KILLOPERS) ServerInstance->Users->QuitUser(user, "Attempted to global message (/amsg or /ame)"); - else if(action == IBLOCK_NOTICE || action == IBLOCK_NOTICEOPERS) - user->WriteServ( "NOTICE %s :Global message (/amsg or /ame) denied", user->nick.c_str()); + else if (action == IBLOCK_NOTICE || action == IBLOCK_NOTICEOPERS) + user->WriteNotice("Global message (/amsg or /ame) denied"); return MOD_RES_DENY; } - if(m) + if (m) { // If there's already a BlockedMessage allocated, use it. m->message = parameters[1]; - m->target = parameters[0].c_str(); + m->target = parameters[0]; m->sent = ServerInstance->Time(); } else { - m = new BlockedMessage(parameters[1], parameters[0].c_str(), ServerInstance->Time()); + m = new BlockedMessage(parameters[1], parameters[0], ServerInstance->Time()); blockamsg.set(user, m); } } @@ -169,5 +147,4 @@ class ModuleBlockAmsg : public Module } }; - MODULE_INIT(ModuleBlockAmsg) diff --git a/src/modules/m_blockcaps.cpp b/src/modules/m_blockcaps.cpp index 7146ee068..fa780427c 100644 --- a/src/modules/m_blockcaps.cpp +++ b/src/modules/m_blockcaps.cpp @@ -21,83 +21,79 @@ #include "inspircd.h" - -/* $ModDesc: Provides support to block all-CAPS channel messages and notices */ - - -/** Handles the +B channel mode - */ -class BlockCaps : public SimpleChannelModeHandler -{ - public: - BlockCaps(Module* Creator) : SimpleChannelModeHandler(Creator, "blockcaps", 'B') { } -}; +#include "modules/exemption.h" class ModuleBlockCAPS : public Module { - BlockCaps bc; - int percent; + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler bc; + unsigned int percent; unsigned int minlen; - char capsmap[256]; -public: + std::bitset<UCHAR_MAX> lowercase; + std::bitset<UCHAR_MAX> uppercase; - ModuleBlockCAPS() : bc(this) - { - } - - void init() - { - OnRehash(NULL); - ServerInstance->Modules->AddService(bc); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void On005Numeric(std::string &output) +public: + ModuleBlockCAPS() + : exemptionprov(this) + , bc(this, "blockcaps", 'B') { - ServerInstance->AddExtBanChar('B'); } - virtual void OnRehash(User* user) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ReadConf(); + tokens["EXTBAN"].push_back('B'); } - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (target_type == TYPE_CHANNEL) + if (target.type == MessageTarget::TYPE_CHANNEL) { - if ((!IS_LOCAL(user)) || (text.length() < minlen) || (text == "\1ACTION\1") || (text == "\1ACTION")) + if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; - Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"blockcaps"); + Channel* c = target.Get<Channel>(); + ModResult res = CheckExemption::Call(exemptionprov, user, c, "blockcaps"); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; - if (!c->GetExtBanStatus(user, 'B').check(!c->IsModeSet('B'))) + if (!c->GetExtBanStatus(user, 'B').check(!c->IsModeSet(bc))) { - int caps = 0; - const char* actstr = "\1ACTION "; - int act = 0; + // If the message is a CTCP then we skip it unless it is + // an ACTION in which case we just check against the body. + std::string ctcpname; + std::string message(details.text); + if (details.IsCTCP(ctcpname, message)) + { + // If the CTCP is not an action then skip it. + if (!irc::equals(ctcpname, "ACTION")) + return MOD_RES_PASSTHRU; + } - for (std::string::iterator i = text.begin(); i != text.end(); i++) + // If the message is shorter than the minimum length + // then we don't need to do anything else. + size_t length = message.length(); + if (length < minlen) + return MOD_RES_PASSTHRU; + + // Count the characters to see how many upper case and + // ignored (non upper or lower) characters there are. + size_t upper = 0; + for (std::string::const_iterator iter = message.begin(); iter != message.end(); ++iter) { - /* Smart fix for suggestion from Jobe, ignore CTCP ACTION (part of /ME) */ - if (*actstr && *i == *actstr++ && act != -1) - { - act++; - continue; - } - else - act = -1; - - caps += capsmap[(unsigned char)*i]; + unsigned char chr = static_cast<unsigned char>(*iter); + if (uppercase.test(chr)) + upper += 1; + else if (!lowercase.test(chr)) + length -= 1; } - if ( ((caps*100)/(int)text.length()) >= percent ) + + // Calculate the percentage which is upper case. If the + // message was entirely symbols then it can't contain + // any upper case letters. + if (length > 0 && round((upper * 100) / length) >= percent) { - user->WriteNumeric(ERR_CANNOTSENDTOCHAN, "%s %s :Your message cannot contain more than %d%% capital letters if it's longer than %d characters", user->nick.c_str(), c->name.c_str(), percent, minlen); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, c->name, InspIRCd::Format("Your message cannot contain %d%% or more capital letters if it's longer than %d characters", percent, minlen)); return MOD_RES_DENY; } } @@ -105,37 +101,24 @@ public: return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreMessage(user,dest,target_type,text,status,exempt_list); - } - - void ReadConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("blockcaps"); - percent = tag->getInt("percent", 100); - minlen = tag->getInt("minlen", 1); - std::string hmap = tag->getString("capsmap", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); - memset(capsmap, 0, sizeof(capsmap)); - for (std::string::iterator n = hmap.begin(); n != hmap.end(); n++) - capsmap[(unsigned char)*n] = 1; - if (percent < 1 || percent > 100) - { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "<blockcaps:percent> out of range, setting to default of 100."); - percent = 100; - } - if (minlen < 1 || minlen > MAXBUF-1) - { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "<blockcaps:minlen> out of range, setting to default of 1."); - minlen = 1; - } - } - - virtual ~ModuleBlockCAPS() - { + percent = tag->getUInt("percent", 100, 1, 100); + minlen = tag->getUInt("minlen", 1, 1, ServerInstance->Config->Limits.MaxLine); + + lowercase.reset(); + const std::string lower = tag->getString("lowercase", "abcdefghijklmnopqrstuvwxyz"); + for (std::string::const_iterator iter = lower.begin(); iter != lower.end(); ++iter) + lowercase.set(static_cast<unsigned char>(*iter)); + + uppercase.reset(); + const std::string upper = tag->getString("uppercase", tag->getString("capsmap", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + for (std::string::const_iterator iter = upper.begin(); iter != upper.end(); ++iter) + uppercase.set(static_cast<unsigned char>(*iter)); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support to block all-CAPS channel messages and notices", VF_VENDOR); } diff --git a/src/modules/m_blockcolor.cpp b/src/modules/m_blockcolor.cpp index 3cc01b4c0..43d0826dd 100644 --- a/src/modules/m_blockcolor.cpp +++ b/src/modules/m_blockcolor.cpp @@ -22,51 +22,38 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel mode +c to block color */ - -/** Handles the +c channel mode - */ -class BlockColor : public SimpleChannelModeHandler -{ - public: - BlockColor(Module* Creator) : SimpleChannelModeHandler(Creator, "blockcolor", 'c') { } -}; +#include "modules/exemption.h" class ModuleBlockColor : public Module { - BlockColor bc; + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler bc; public: - ModuleBlockColor() : bc(this) + ModuleBlockColor() + : exemptionprov(this) + , bc(this, "blockcolor", 'c') { } - void init() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(bc); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + tokens["EXTBAN"].push_back('c'); } - virtual void On005Numeric(std::string &output) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('c'); - } - - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) + if ((target.type == MessageTarget::TYPE_CHANNEL) && (IS_LOCAL(user))) { - Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"blockcolor"); + Channel* c = target.Get<Channel>(); + ModResult res = CheckExemption::Call(exemptionprov, user, c, "blockcolor"); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; - if (!c->GetExtBanStatus(user, 'c').check(!c->IsModeSet('c'))) + if (!c->GetExtBanStatus(user, 'c').check(!c->IsModeSet(bc))) { - for (std::string::iterator i = text.begin(); i != text.end(); i++) + for (std::string::iterator i = details.text.begin(); i != details.text.end(); i++) { switch (*i) { @@ -76,7 +63,7 @@ class ModuleBlockColor : public Module case 21: case 22: case 31: - user->WriteNumeric(404, "%s %s :Can't send colors to channel (+c set)",user->nick.c_str(), c->name.c_str()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, c->name, "Can't send colors to channel (+c set)"); return MOD_RES_DENY; break; } @@ -86,16 +73,7 @@ class ModuleBlockColor : public Module return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreMessage(user,dest,target_type,text,status,exempt_list); - } - - virtual ~ModuleBlockColor() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +c to block color",VF_VENDOR); } diff --git a/src/modules/m_botmode.cpp b/src/modules/m_botmode.cpp index b29c58240..1931666d1 100644 --- a/src/modules/m_botmode.cpp +++ b/src/modules/m_botmode.cpp @@ -20,51 +20,36 @@ #include "inspircd.h" +#include "modules/whois.h" -/* $ModDesc: Provides user mode +B to mark the user as a bot */ - -/** Handles user mode +B - */ -class BotMode : public SimpleUserModeHandler +enum { - public: - BotMode(Module* Creator) : SimpleUserModeHandler(Creator, "bot", 'B') { } + // From UnrealIRCd. + RPL_WHOISBOT = 335 }; -class ModuleBotMode : public Module +class ModuleBotMode : public Module, public Whois::EventListener { - BotMode bm; + SimpleUserModeHandler bm; public: ModuleBotMode() - : bm(this) - { - } - - void init() + : Whois::EventListener(this) + , bm(this, "bot", 'B') { - ServerInstance->Modules->AddService(bm); - Implementation eventlist[] = { I_OnWhois }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual ~ModuleBotMode() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides user mode +B to mark the user as a bot",VF_VENDOR); } - virtual void OnWhois(User* src, User* dst) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - if (dst->IsModeSet('B')) + if (whois.GetTarget()->IsModeSet(bm)) { - ServerInstance->SendWhoisLine(src, dst, 335, src->nick+" "+dst->nick+" :is a bot on "+ServerInstance->Config->Network); + whois.SendLine(RPL_WHOISBOT, "is a bot on " + ServerInstance->Config->Network); } } - }; - MODULE_INIT(ModuleBotMode) diff --git a/src/modules/m_callerid.cpp b/src/modules/m_callerid.cpp index 2df6d7af0..f50bdeedf 100644 --- a/src/modules/m_callerid.cpp +++ b/src/modules/m_callerid.cpp @@ -22,20 +22,33 @@ #include "inspircd.h" -/* $ModDesc: Implementation of callerid, usermode +g, /accept */ +enum +{ + RPL_ACCEPTLIST = 281, + RPL_ENDOFACCEPT = 282, + ERR_ACCEPTFULL = 456, + ERR_ACCEPTEXIST = 457, + ERR_ACCEPTNOT = 458, + ERR_TARGUMODEG = 716, + RPL_TARGNOTIFY = 717, + RPL_UMODEGMSG = 718 +}; class callerid_data { public: + typedef insp::flat_set<User*> UserSet; + typedef std::vector<callerid_data*> CallerIdDataSet; + time_t lastnotify; /** Users I accept messages from */ - std::set<User*> accepting; + UserSet accepting; /** Users who list me as accepted */ - std::list<callerid_data *> wholistsme; + CallerIdDataSet wholistsme; callerid_data() : lastnotify(0) { } @@ -43,7 +56,7 @@ class callerid_data { std::ostringstream oss; oss << lastnotify; - for (std::set<User*>::const_iterator i = accepting.begin(); i != accepting.end(); ++i) + for (UserSet::const_iterator i = accepting.begin(); i != accepting.end(); ++i) { User* u = *i; // Encode UIDs. @@ -56,21 +69,29 @@ class callerid_data struct CallerIDExtInfo : public ExtensionItem { CallerIDExtInfo(Module* parent) - : ExtensionItem("callerid_data", parent) + : ExtensionItem("callerid_data", ExtensionItem::EXT_USER, parent) { } - std::string serialize(SerializeFormat format, const Extensible* container, void* item) const + std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE { - callerid_data* dat = static_cast<callerid_data*>(item); - return dat->ToString(format); + std::string ret; + if (format != FORMAT_NETWORK) + { + callerid_data* dat = static_cast<callerid_data*>(item); + ret = dat->ToString(format); + } + return ret; } - void unserialize(SerializeFormat format, Extensible* container, const std::string& value) + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE { + if (format == FORMAT_NETWORK) + return; + void* old = get_raw(container); if (old) - this->free(old); + this->free(NULL, old); callerid_data* dat = new callerid_data; set_raw(container, dat); @@ -81,11 +102,8 @@ struct CallerIDExtInfo : public ExtensionItem while (s.GetToken(tok)) { - if (tok.empty()) - continue; - User *u = ServerInstance->FindNick(tok); - if ((u) && (u->registered == REG_ALL) && (!u->quitting) && (!IS_SERVER(u))) + if ((u) && (u->registered == REG_ALL) && (!u->quitting)) { if (dat->accepting.insert(u).second) { @@ -107,39 +125,52 @@ struct CallerIDExtInfo : public ExtensionItem return dat; } - void free(void* item) + void free(Extensible* container, void* item) CXX11_OVERRIDE { callerid_data* dat = static_cast<callerid_data*>(item); // We need to walk the list of users on our accept list, and remove ourselves from their wholistsme. - for (std::set<User *>::iterator it = dat->accepting.begin(); it != dat->accepting.end(); it++) + for (callerid_data::UserSet::iterator it = dat->accepting.begin(); it != dat->accepting.end(); ++it) { callerid_data *targ = this->get(*it, false); if (!targ) { - ServerInstance->Logs->Log("m_callerid", DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (1)"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (1)"); continue; // shouldn't happen, but oh well. } - std::list<callerid_data*>::iterator it2 = std::find(targ->wholistsme.begin(), targ->wholistsme.end(), dat); - if (it2 != targ->wholistsme.end()) - targ->wholistsme.erase(it2); - else - ServerInstance->Logs->Log("m_callerid", DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (2)"); + if (!stdalgo::vector::swaperase(targ->wholistsme, dat)) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (2)"); } delete dat; } }; -class User_g : public SimpleUserModeHandler -{ -public: - User_g(Module* Creator) : SimpleUserModeHandler(Creator, "callerid", 'g') { } -}; - class CommandAccept : public Command { + /** Pair: first is the target, second is true to add, false to remove + */ + typedef std::pair<User*, bool> ACCEPTAction; + + static ACCEPTAction GetTargetAndAction(std::string& tok, User* cmdfrom = NULL) + { + bool remove = (tok[0] == '-'); + if ((remove) || (tok[0] == '+')) + tok.erase(tok.begin()); + + User* target; + if (!cmdfrom || !IS_LOCAL(cmdfrom)) + target = ServerInstance->FindNick(tok); + else + target = ServerInstance->FindNickOnly(tok); + + if ((!target) || (target->registered != REG_ALL) || (target->quitting)) + target = NULL; + + return std::make_pair(target, !remove); + } + public: CallerIDExtInfo extInfo; unsigned int maxaccepts; @@ -148,42 +179,21 @@ public: { allow_empty_last_param = false; syntax = "*|(+|-)<nick>[,(+|-)<nick> ...]"; - TRANSLATE2(TR_CUSTOM, TR_END); + TRANSLATE1(TR_CUSTOM); } - virtual void EncodeParameter(std::string& parameter, int index) + void EncodeParameter(std::string& parameter, unsigned int index) CXX11_OVERRIDE { - if (index != 0) + // Send lists as-is (part of 2.0 compat) + if (parameter.find(',') != std::string::npos) return; - std::string out; - irc::commasepstream nicks(parameter); - std::string tok; - while (nicks.GetToken(tok)) - { - if (tok == "*") - { - continue; // Drop list requests, since remote servers ignore them anyway. - } - if (!out.empty()) - out.append(","); - bool dash = false; - if (tok[0] == '-') - { - dash = true; - tok.erase(0, 1); // Remove the dash. - } - else if (tok[0] == '+') - tok.erase(0, 1); - User* u = ServerInstance->FindNick(tok); - if ((!u) || (u->registered != REG_ALL) || (u->quitting) || (IS_SERVER(u))) - continue; + // Convert a [+|-]<nick> into a [-]<uuid> + ACCEPTAction action = GetTargetAndAction(parameter); + if (!action.first) + return; - if (dash) - out.append("-"); - out.append(u->uuid); - } - parameter = out; + parameter = (action.second ? "" : "-") + action.first->uuid; } /** Will take any number of nicks (up to MaxTargets), which can be seperated by commas. @@ -191,56 +201,60 @@ public: * /accept nick1,nick2,nick3,* * to add 3 nicks and then show your list */ - CmdResult Handle(const std::vector<std::string> ¶meters, User* user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (ServerInstance->Parser->LoopCall(user, this, parameters, 0)) + if (CommandParser::LoopCall(user, this, parameters, 0)) return CMD_SUCCESS; + /* Even if callerid mode is not set, we let them manage their ACCEPT list so that if they go +g they can * have a list already setup. */ - const std::string& tok = parameters[0]; - - if (tok == "*") + if (parameters[0] == "*") { - if (IS_LOCAL(user)) - ListAccept(user); + ListAccept(user); return CMD_SUCCESS; } - else if (tok[0] == '-') + + std::string tok = parameters[0]; + ACCEPTAction action = GetTargetAndAction(tok, user); + if (!action.first) { - User* whotoremove; - if (IS_LOCAL(user)) - whotoremove = ServerInstance->FindNickOnly(tok.substr(1)); - else - whotoremove = ServerInstance->FindNick(tok.substr(1)); - - if (whotoremove) - return (RemoveAccept(user, whotoremove) ? CMD_SUCCESS : CMD_FAILURE); - else - return CMD_FAILURE; + user->WriteNumeric(Numerics::NoSuchNick(tok)); + return CMD_FAILURE; } + + if ((!IS_LOCAL(user)) && (!IS_LOCAL(action.first))) + // Neither source nor target is local, forward the command to the server of target + return CMD_SUCCESS; + + // The second item in the pair is true if the first char is a '+' (or nothing), false if it's a '-' + if (action.second) + return (AddAccept(user, action.first) ? CMD_SUCCESS : CMD_FAILURE); else - { - const std::string target = (tok[0] == '+' ? tok.substr(1) : tok); - User* whotoadd; - if (IS_LOCAL(user)) - whotoadd = ServerInstance->FindNickOnly(target); - else - whotoadd = ServerInstance->FindNick(target); - - if ((whotoadd) && (whotoadd->registered == REG_ALL) && (!whotoadd->quitting) && (!IS_SERVER(whotoadd))) - return (AddAccept(user, whotoadd) ? CMD_SUCCESS : CMD_FAILURE); - else - { - user->WriteNumeric(401, "%s %s :No such nick/channel", user->nick.c_str(), tok.c_str()); - return CMD_FAILURE; - } - } + return (RemoveAccept(user, action.first) ? CMD_SUCCESS : CMD_FAILURE); } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - return ROUTE_BROADCAST; + // There is a list in parameters[0] in two cases: + // Either when the source is remote, this happens because 2.0 servers send comma seperated uuid lists, + // we don't split those but broadcast them, as before. + // + // Or if the source is local then LoopCall() runs OnPostCommand() after each entry in the list, + // meaning the linking module has sent an ACCEPT already for each entry in the list to the + // appropiate server and the ACCEPT with the list of nicks (this) doesn't need to be sent anywhere. + if ((!IS_LOCAL(user)) && (parameters[0].find(',') != std::string::npos)) + return ROUTE_BROADCAST; + + // Find the target + std::string targetstring = parameters[0]; + ACCEPTAction action = GetTargetAndAction(targetstring, user); + if (!action.first) + // Target is a "*" or source is local and the target is a list of nicks + return ROUTE_LOCALONLY; + + // Route to the server of the target + return ROUTE_UNICAST(action.first->server); } void ListAccept(User* user) @@ -248,10 +262,10 @@ public: callerid_data* dat = extInfo.get(user, false); if (dat) { - for (std::set<User*>::iterator i = dat->accepting.begin(); i != dat->accepting.end(); ++i) - user->WriteNumeric(281, "%s %s", user->nick.c_str(), (*i)->nick.c_str()); + for (callerid_data::UserSet::iterator i = dat->accepting.begin(); i != dat->accepting.end(); ++i) + user->WriteNumeric(RPL_ACCEPTLIST, (*i)->nick); } - user->WriteNumeric(282, "%s :End of ACCEPT list", user->nick.c_str()); + user->WriteNumeric(RPL_ENDOFACCEPT, "End of ACCEPT list"); } bool AddAccept(User* user, User* whotoadd) @@ -260,12 +274,12 @@ public: callerid_data* dat = extInfo.get(user, true); if (dat->accepting.size() >= maxaccepts) { - user->WriteNumeric(456, "%s :Accept list is full (limit is %d)", user->nick.c_str(), maxaccepts); + user->WriteNumeric(ERR_ACCEPTFULL, InspIRCd::Format("Accept list is full (limit is %d)", maxaccepts)); return false; } if (!dat->accepting.insert(whotoadd).second) { - user->WriteNumeric(457, "%s %s :is already on your accept list", user->nick.c_str(), whotoadd->nick.c_str()); + user->WriteNumeric(ERR_ACCEPTEXIST, whotoadd->nick, "is already on your accept list"); return false; } @@ -273,7 +287,7 @@ public: callerid_data *targ = extInfo.get(whotoadd, true); targ->wholistsme.push_back(dat); - user->WriteServ("NOTICE %s :%s is now on your accept list", user->nick.c_str(), whotoadd->nick.c_str()); + user->WriteNotice(whotoadd->nick + " is now on your accept list"); return true; } @@ -283,48 +297,39 @@ public: callerid_data* dat = extInfo.get(user, false); if (!dat) { - user->WriteNumeric(458, "%s %s :is not on your accept list", user->nick.c_str(), whotoremove->nick.c_str()); + user->WriteNumeric(ERR_ACCEPTNOT, whotoremove->nick, "is not on your accept list"); return false; } - std::set<User*>::iterator i = dat->accepting.find(whotoremove); - if (i == dat->accepting.end()) + if (!dat->accepting.erase(whotoremove)) { - user->WriteNumeric(458, "%s %s :is not on your accept list", user->nick.c_str(), whotoremove->nick.c_str()); + user->WriteNumeric(ERR_ACCEPTNOT, whotoremove->nick, "is not on your accept list"); return false; } - dat->accepting.erase(i); - // Look up their list to remove me. callerid_data *dat2 = extInfo.get(whotoremove, false); if (!dat2) { // How the fuck is this possible. - ServerInstance->Logs->Log("m_callerid", DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (3)"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (3)"); return false; } - std::list<callerid_data*>::iterator it = std::find(dat2->wholistsme.begin(), dat2->wholistsme.end(), dat); - if (it != dat2->wholistsme.end()) - // Found me! - dat2->wholistsme.erase(it); - else - ServerInstance->Logs->Log("m_callerid", DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (4)"); + if (!stdalgo::vector::swaperase(dat2->wholistsme, dat)) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (4)"); - user->WriteServ("NOTICE %s :%s is no longer on your accept list", user->nick.c_str(), whotoremove->nick.c_str()); + user->WriteNotice(whotoremove->nick + " is no longer on your accept list"); return true; } }; class ModuleCallerID : public Module { -private: CommandAccept cmd; - User_g myumode; + SimpleUserModeHandler myumode; // Configuration variables: - bool operoverride; // Operators can override callerid. bool tracknick; // Allow ACCEPT entries to update with nick changes. unsigned int notify_cooldown; // Seconds between notifications. @@ -339,74 +344,59 @@ private: return; // Iterate over the list of people who accept me, and remove all entries - for (std::list<callerid_data *>::iterator it = userdata->wholistsme.begin(); it != userdata->wholistsme.end(); it++) + for (callerid_data::CallerIdDataSet::iterator it = userdata->wholistsme.begin(); it != userdata->wholistsme.end(); ++it) { callerid_data *dat = *(it); // Find me on their callerid list - std::set<User *>::iterator it2 = dat->accepting.find(who); - - if (it2 != dat->accepting.end()) - dat->accepting.erase(it2); - else - ServerInstance->Logs->Log("m_callerid", DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (5)"); + if (!dat->accepting.erase(who)) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (5)"); } userdata->wholistsme.clear(); } public: - ModuleCallerID() : cmd(this), myumode(this) + ModuleCallerID() + : cmd(this) + , myumode(this, "callerid", 'g') { } - void init() - { - OnRehash(NULL); - - ServerInstance->Modules->AddService(myumode); - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(cmd.extInfo); - - Implementation eventlist[] = { I_OnRehash, I_OnUserPostNick, I_OnUserQuit, I_On005Numeric, I_OnUserPreNotice, I_OnUserPreMessage }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleCallerID() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implementation of callerid, usermode +g, /accept", VF_COMMON | VF_VENDOR); } - virtual void On005Numeric(std::string& output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output += " CALLERID=g"; + tokens["ACCEPT"] = ConvToStr(cmd.maxaccepts); + tokens["CALLERID"] = ConvToStr(myumode.GetModeChar()); } - ModResult PreText(User* user, User* dest, std::string& text) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (!dest->IsModeSet('g') || (user == dest)) + if (!IS_LOCAL(user) || target.type != MessageTarget::TYPE_USER) return MOD_RES_PASSTHRU; - if (operoverride && IS_OPER(user)) + User* dest = target.Get<User>(); + if (!dest->IsModeSet(myumode) || (user == dest)) return MOD_RES_PASSTHRU; - callerid_data* dat = cmd.extInfo.get(dest, true); - std::set<User*>::iterator i = dat->accepting.find(user); + if (user->HasPrivPermission("users/callerid-override")) + return MOD_RES_PASSTHRU; - if (i == dat->accepting.end()) + callerid_data* dat = cmd.extInfo.get(dest, true); + if (!dat->accepting.count(user)) { time_t now = ServerInstance->Time(); /* +g and *not* accepted */ - user->WriteNumeric(716, "%s %s :is in +g mode (server-side ignore).", user->nick.c_str(), dest->nick.c_str()); + user->WriteNumeric(ERR_TARGUMODEG, dest->nick, "is in +g mode (server-side ignore)."); if (now > (dat->lastnotify + (time_t)notify_cooldown)) { - user->WriteNumeric(717, "%s %s :has been informed that you messaged them.", user->nick.c_str(), dest->nick.c_str()); - dest->SendText(":%s 718 %s %s %s@%s :is messaging you, and you have umode +g. Use /ACCEPT +%s to allow.", - ServerInstance->Config->ServerName.c_str(), dest->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str(), user->nick.c_str()); + user->WriteNumeric(RPL_TARGNOTIFY, dest->nick, "has been informed that you messaged them."); + dest->WriteRemoteNumeric(RPL_UMODEGMSG, user->nick, InspIRCd::Format("%s@%s", user->ident.c_str(), user->GetDisplayedHost().c_str()), InspIRCd::Format("is messaging you, and you have umode +g. Use /ACCEPT +%s to allow.", + user->nick.c_str())); dat->lastnotify = now; } return MOD_RES_DENY; @@ -414,43 +404,30 @@ public: return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList &exempt_list) - { - if (IS_LOCAL(user) && target_type == TYPE_USER) - return PreText(user, (User*)dest, text); - - return MOD_RES_PASSTHRU; - } - - virtual ModResult OnUserPreNotice(User* user, void* dest, int target_type, std::string& text, char status, CUList &exempt_list) - { - if (IS_LOCAL(user) && target_type == TYPE_USER) - return PreText(user, (User*)dest, text); - - return MOD_RES_PASSTHRU; - } - - void OnUserPostNick(User* user, const std::string& oldnick) + void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE { if (!tracknick) RemoveFromAllAccepts(user); } - void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) + void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE { RemoveFromAllAccepts(user); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("callerid"); - cmd.maxaccepts = tag->getInt("maxaccepts", 16); - operoverride = tag->getBool("operoverride"); + cmd.maxaccepts = tag->getUInt("maxaccepts", 16); tracknick = tag->getBool("tracknick"); - notify_cooldown = tag->getInt("cooldown", 60); + notify_cooldown = tag->getDuration("cooldown", 60); + } + + void Prioritize() CXX11_OVERRIDE + { + // Want to be after modules like silence or services_account + ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST); } }; MODULE_INIT(ModuleCallerID) - - diff --git a/src/modules/m_cap.cpp b/src/modules/m_cap.cpp index ae9e824f4..922061757 100644 --- a/src/modules/m_cap.cpp +++ b/src/modules/m_cap.cpp @@ -1,8 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -19,151 +18,439 @@ #include "inspircd.h" -#include "m_cap.h" +#include "modules/reload.h" +#include "modules/cap.h" -/* $ModDesc: Provides the CAP negotiation mechanism seen in ratbox-derived ircds */ +enum +{ + // From IRCv3 capability-negotiation-3.1. + ERR_INVALIDCAPCMD = 410 +}; -/* -CAP LS -:alfred.staticbox.net CAP * LS :multi-prefix sasl -CAP REQ :multi-prefix -:alfred.staticbox.net CAP * ACK :multi-prefix -CAP CLEAR -:alfred.staticbox.net CAP * ACK :-multi-prefix -CAP REQ :multi-prefix -:alfred.staticbox.net CAP * ACK :multi-prefix -CAP LIST -:alfred.staticbox.net CAP * LIST :multi-prefix -CAP END -*/ - -/** Handle /CAP - */ -class CommandCAP : public Command +namespace Cap { - public: - LocalIntExt reghold; - CommandCAP (Module* mod) : Command(mod, "CAP", 1), - reghold("CAP_REGHOLD", mod) + class ManagerImpl; +} + +static Cap::ManagerImpl* managerimpl; + +class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener +{ + /** Stores the cap state of a module being reloaded + */ + struct CapModData { - works_before_reg = true; + struct Data + { + std::string name; + std::vector<std::string> users; + + Data(Capability* cap) + : name(cap->GetName()) + { + } + }; + std::vector<Data> caps; + }; + + typedef insp::flat_map<std::string, Capability*, irc::insensitive_swo> CapMap; + + ExtItem capext; + CapMap caps; + Events::ModuleEventProvider& evprov; + + static bool CanRequest(LocalUser* user, Ext usercaps, Capability* cap, bool adding) + { + const bool hascap = ((usercaps & cap->GetMask()) != 0); + if (hascap == adding) + return true; + + return cap->OnRequest(user, adding); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + Capability::Bit AllocateBit() const { - irc::string subcommand = parameters[0].c_str(); + Capability::Bit used = 0; + for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i) + { + Capability* cap = i->second; + used |= cap->GetMask(); + } - if (subcommand == "REQ") + for (unsigned int i = 0; i < MAX_CAPS; i++) { - if (parameters.size() < 2) - return CMD_FAILURE; + Capability::Bit bit = (1 << i); + if (!(used & bit)) + return bit; + } + throw ModuleException("Too many caps"); + } + + void OnReloadModuleSave(Module* mod, ReloadModule::CustomData& cd) CXX11_OVERRIDE + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "OnReloadModuleSave()"); + if (mod == creator) + return; - CapEvent Data(creator, user, CapEvent::CAPEVENT_REQ); + CapModData* capmoddata = new CapModData; + cd.add(this, capmoddata); - // tokenize the input into a nice list of requested caps - std::string cap_; - irc::spacesepstream cap_stream(parameters[1]); + for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i) + { + Capability* cap = i->second; + // Only save users of caps that belong to the module being reloaded + if (cap->creator != mod) + continue; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Module being reloaded implements cap %s, saving cap users", cap->GetName().c_str()); + capmoddata->caps.push_back(CapModData::Data(cap)); + CapModData::Data& capdata = capmoddata->caps.back(); - while (cap_stream.GetToken(cap_)) + // Populate list with uuids of users who are using the cap + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator j = list.begin(); j != list.end(); ++j) { - // Whilst the handling of extraneous spaces is not currently defined in the CAP specification - // every single other implementation ignores extraneous spaces. Lets copy them for - // compatibility purposes. - trim(cap_); - if (!cap_.empty()) - Data.wanted.push_back(cap_); + LocalUser* user = *j; + if (cap->get(user)) + capdata.users.push_back(user->uuid); } + } + } - reghold.set(user, 1); - Data.Send(); - - if (Data.wanted.empty()) + void OnReloadModuleRestore(Module* mod, void* data) CXX11_OVERRIDE + { + CapModData* capmoddata = static_cast<CapModData*>(data); + for (std::vector<CapModData::Data>::const_iterator i = capmoddata->caps.begin(); i != capmoddata->caps.end(); ++i) + { + const CapModData::Data& capdata = *i; + Capability* cap = ManagerImpl::Find(capdata.name); + if (!cap) { - user->WriteServ("CAP %s ACK :%s", user->nick.c_str(), parameters[1].c_str()); - return CMD_SUCCESS; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s is no longer available after reload", capdata.name.c_str()); + continue; } - // HACK: reset all of the caps which were enabled on this user because a cap request is atomic. - for (std::vector<std::pair<GenericCap*, int> >::iterator iter = Data.changed.begin(); iter != Data.changed.end(); ++iter) - iter->first->ext.set(user, iter->second); + // Set back the cap for all users who were using it before the reload + for (std::vector<std::string>::const_iterator j = capdata.users.begin(); j != capdata.users.end(); ++j) + { + const std::string& uuid = *j; + User* user = ServerInstance->FindUUID(uuid); + if (!user) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone when trying to restore cap %s", uuid.c_str(), capdata.name.c_str()); + continue; + } - user->WriteServ("CAP %s NAK :%s", user->nick.c_str(), parameters[1].c_str()); + cap->set(user, true); + } } - else if (subcommand == "END") + delete capmoddata; + } + + public: + ManagerImpl(Module* mod, Events::ModuleEventProvider& evprovref) + : Cap::Manager(mod) + , ReloadModule::EventListener(mod) + , capext(mod) + , evprov(evprovref) + { + managerimpl = this; + } + + ~ManagerImpl() + { + for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i) { - reghold.set(user, 0); + Capability* cap = i->second; + cap->Unregister(); } - else if ((subcommand == "LS") || (subcommand == "LIST")) - { - CapEvent Data(creator, user, subcommand == "LS" ? CapEvent::CAPEVENT_LS : CapEvent::CAPEVENT_LIST); + } + + void AddCap(Cap::Capability* cap) CXX11_OVERRIDE + { + // No-op if the cap is already registered. + // This allows modules to call SetActive() on a cap without checking if it's active first. + if (cap->IsRegistered()) + return; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Registering cap %s", cap->GetName().c_str()); + cap->bit = AllocateBit(); + cap->extitem = &capext; + caps.insert(std::make_pair(cap->GetName(), cap)); + ServerInstance->Modules.AddReferent("cap/" + cap->GetName(), cap); + + FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, true)); + } + + void DelCap(Cap::Capability* cap) CXX11_OVERRIDE + { + // No-op if the cap is not registered, see AddCap() above + if (!cap->IsRegistered()) + return; - reghold.set(user, 1); - Data.Send(); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unregistering cap %s", cap->GetName().c_str()); - std::string Result; - if (Data.wanted.size() > 0) - Result = irc::stringjoiner(" ", Data.wanted, 0, Data.wanted.size() - 1).GetJoined(); + // Fire the event first so modules can still see who is using the cap which is being unregistered + FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, false)); - user->WriteServ("CAP %s %s :%s", user->nick.c_str(), subcommand.c_str(), Result.c_str()); + // Turn off the cap for all users + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* user = *i; + cap->set(user, false); } - else if (subcommand == "CLEAR") + + ServerInstance->Modules.DelReferent(cap); + cap->Unregister(); + caps.erase(cap->GetName()); + } + + Capability* Find(const std::string& capname) const CXX11_OVERRIDE + { + CapMap::const_iterator it = caps.find(capname); + if (it != caps.end()) + return it->second; + return NULL; + } + + void NotifyValueChange(Capability* cap) CXX11_OVERRIDE + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s changed value", cap->GetName().c_str()); + FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapValueChange, (cap)); + } + + Protocol GetProtocol(LocalUser* user) const + { + return ((capext.get(user) & CAP_302_BIT) ? CAP_302 : CAP_LEGACY); + } + + void Set302Protocol(LocalUser* user) + { + capext.set(user, capext.get(user) | CAP_302_BIT); + } + + bool HandleReq(LocalUser* user, const std::string& reqlist) + { + Ext usercaps = capext.get(user); + irc::spacesepstream ss(reqlist); + for (std::string capname; ss.GetToken(capname); ) { - CapEvent Data(creator, user, CapEvent::CAPEVENT_CLEAR); + bool remove = (capname[0] == '-'); + if (remove) + capname.erase(capname.begin()); - reghold.set(user, 1); - Data.Send(); + Capability* cap = ManagerImpl::Find(capname); + if ((!cap) || (!CanRequest(user, usercaps, cap, !remove))) + return false; - std::string Result; - if (!Data.ack.empty()) - Result = irc::stringjoiner(" ", Data.ack, 0, Data.ack.size() - 1).GetJoined(); - user->WriteServ("CAP %s ACK :%s", user->nick.c_str(), Result.c_str()); + if (remove) + usercaps = cap->DelFromMask(usercaps); + else + usercaps = cap->AddToMask(usercaps); } - else + + capext.set(user, usercaps); + return true; + } + + void HandleList(std::string& out, LocalUser* user, bool show_all, bool show_values, bool minus_prefix = false) const + { + Ext show_caps = (show_all ? ~0 : capext.get(user)); + + for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i) { - user->WriteNumeric(ERR_INVALIDCAPSUBCOMMAND, "%s %s :Invalid CAP subcommand", user->nick.c_str(), subcommand.empty() ? "*" : subcommand.c_str()); - return CMD_FAILURE; + Capability* cap = i->second; + if (!(show_caps & cap->GetMask())) + continue; + + if ((show_all) && (!cap->OnList(user))) + continue; + + if (minus_prefix) + out.push_back('-'); + out.append(cap->GetName()); + + if (show_values) + { + const std::string* capvalue = cap->GetValue(user); + if ((capvalue) && (!capvalue->empty()) && (capvalue->find(' ') == std::string::npos)) + { + out.push_back('='); + out.append(*capvalue, 0, MAX_VALUE_LENGTH); + } + } + out.push_back(' '); } + } - return CMD_SUCCESS; + void HandleClear(LocalUser* user, std::string& result) + { + HandleList(result, user, false, false, true); + capext.unset(user); } }; -class ModuleCAP : public Module +Cap::ExtItem::ExtItem(Module* mod) + : LocalIntExt("caps", ExtensionItem::EXT_USER, mod) +{ +} + +std::string Cap::ExtItem::serialize(SerializeFormat format, const Extensible* container, void* item) const +{ + std::string ret; + // XXX: Cast away the const because IS_LOCAL() doesn't handle it + LocalUser* user = IS_LOCAL(const_cast<User*>(static_cast<const User*>(container))); + if ((format == FORMAT_NETWORK) || (!user)) + return ret; + + // List requested caps + managerimpl->HandleList(ret, user, false, false); + + // Serialize cap protocol version. If building a human-readable string append a new token, otherwise append only a single character indicating the version. + Protocol protocol = managerimpl->GetProtocol(user); + if (format == FORMAT_USER) + ret.append("capversion=3."); + else if (!ret.empty()) + ret.erase(ret.length()-1); + + if (protocol == CAP_302) + ret.push_back('2'); + else + ret.push_back('1'); + + return ret; +} + +void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, const std::string& value) +{ + if (format == FORMAT_NETWORK) + return; + + LocalUser* user = IS_LOCAL(static_cast<User*>(container)); + if (!user) + return; // Can't happen + + // Process the cap protocol version which is a single character at the end of the serialized string + const char verchar = *value.rbegin(); + if (verchar == '2') + managerimpl->Set302Protocol(user); + + // Remove the version indicator from the string passed to HandleReq + std::string caplist(value, 0, value.size()-1); + managerimpl->HandleReq(user, caplist); +} + +class CapMessage : public Cap::MessageBase { - CommandCAP cmd; public: - ModuleCAP() - : cmd(this) + CapMessage(LocalUser* user, const std::string& subcmd, const std::string& result) + : Cap::MessageBase(subcmd) { + SetUser(user); + PushParamRef(result); } +}; + +class CommandCap : public SplitCommand +{ + Events::ModuleEventProvider evprov; + Cap::ManagerImpl manager; + ClientProtocol::EventProvider protoevprov; - void init() + void DisplayResult(LocalUser* user, const std::string& subcmd, std::string& result) { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(cmd.reghold); + if (*result.rbegin() == ' ') + result.erase(result.end()-1); + DisplayResult2(user, subcmd, result); + } - Implementation eventlist[] = { I_OnCheckReady }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + void DisplayResult2(LocalUser* user, const std::string& subcmd, const std::string& result) + { + CapMessage msg(user, subcmd, result); + ClientProtocol::Event ev(protoevprov, msg); + user->Send(ev); } - ModResult OnCheckReady(LocalUser* user) + public: + LocalIntExt holdext; + + CommandCap(Module* mod) + : SplitCommand(mod, "CAP", 1) + , evprov(mod, "event/cap") + , manager(mod, evprov) + , protoevprov(mod, name) + , holdext("cap_hold", ExtensionItem::EXT_USER, mod) { - /* Users in CAP state get held until CAP END */ - if (cmd.reghold.get(user)) - return MOD_RES_DENY; + works_before_reg = true; + } + + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE + { + if (user->registered != REG_ALL) + holdext.set(user, 1); + + std::string subcommand(parameters[0].length(), ' '); + std::transform(parameters[0].begin(), parameters[0].end(), subcommand.begin(), ::toupper); + + if (subcommand == "REQ") + { + if (parameters.size() < 2) + return CMD_FAILURE; - return MOD_RES_PASSTHRU; + const std::string replysubcmd = (manager.HandleReq(user, parameters[1]) ? "ACK" : "NAK"); + DisplayResult2(user, replysubcmd, parameters[1]); + } + else if (subcommand == "END") + { + holdext.unset(user); + } + else if ((subcommand == "LS") || (subcommand == "LIST")) + { + const bool is_ls = (subcommand.length() == 2); + if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302")) + manager.Set302Protocol(user); + + std::string result; + // Show values only if supports v3.2 and doing LS + manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY))); + DisplayResult(user, subcommand, result); + } + else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY)) + { + std::string result; + manager.HandleClear(user, result); + DisplayResult(user, "ACK", result); + } + else + { + user->WriteNumeric(ERR_INVALIDCAPCMD, subcommand.empty() ? "*" : subcommand, "Invalid CAP subcommand"); + return CMD_FAILURE; + } + + return CMD_SUCCESS; } +}; - ~ModuleCAP() +class ModuleCap : public Module +{ + CommandCap cmd; + + public: + ModuleCap() + : cmd(this) { } - Version GetVersion() + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { - return Version("Client CAP extension support", VF_VENDOR); + return (cmd.holdext.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU); } -}; -MODULE_INIT(ModuleCAP) + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for CAP capability negotiation", VF_VENDOR); + } +}; +MODULE_INIT(ModuleCap) diff --git a/src/modules/m_cap.h b/src/modules/m_cap.h deleted file mode 100644 index 23cf8cf69..000000000 --- a/src/modules/m_cap.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#ifndef M_CAP_H -#define M_CAP_H - -class GenericCap; - -class CapEvent : public Event -{ - public: - enum CapEventType - { - CAPEVENT_REQ, - CAPEVENT_LS, - CAPEVENT_LIST, - CAPEVENT_CLEAR - }; - - CapEventType type; - std::vector<std::string> wanted; - std::vector<std::string> ack; - std::vector<std::pair<GenericCap*, int> > changed; // HACK: clean this up before 2.2 - User* user; - CapEvent(Module* sender, User* u, CapEventType capevtype) : Event(sender, "cap_request"), type(capevtype), user(u) {} -}; - -class GenericCap -{ - public: - LocalIntExt ext; - const std::string cap; - GenericCap(Module* parent, const std::string &Cap) : ext("cap_" + Cap, parent), cap(Cap) - { - ServerInstance->Modules->AddService(ext); - } - - void HandleEvent(Event& ev) - { - if (ev.id != "cap_request") - return; - - CapEvent *data = static_cast<CapEvent*>(&ev); - if (data->type == CapEvent::CAPEVENT_REQ) - { - for (std::vector<std::string>::iterator it = data->wanted.begin(); it != data->wanted.end(); ++it) - { - if (it->empty()) - continue; - bool enablecap = ((*it)[0] != '-'); - if (((enablecap) && (*it == cap)) || (*it == "-" + cap)) - { - // we can handle this, so ACK it, and remove it from the wanted list - data->ack.push_back(*it); - data->wanted.erase(it); - data->changed.push_back(std::make_pair(this, ext.set(data->user, enablecap ? 1 : 0))); - break; - } - } - } - else if (data->type == CapEvent::CAPEVENT_LS) - { - data->wanted.push_back(cap); - } - else if (data->type == CapEvent::CAPEVENT_LIST) - { - if (ext.get(data->user)) - data->wanted.push_back(cap); - } - else if (data->type == CapEvent::CAPEVENT_CLEAR) - { - data->ack.push_back("-" + cap); - ext.set(data->user, 0); - } - } -}; - -#endif diff --git a/src/modules/m_cban.cpp b/src/modules/m_cban.cpp index fb78e41b2..d1fb23620 100644 --- a/src/modules/m_cban.cpp +++ b/src/modules/m_cban.cpp @@ -22,48 +22,42 @@ #include "inspircd.h" #include "xline.h" +#include "modules/stats.h" -/* $ModDesc: Gives /cban, aka C:lines. Think Q:lines, for channels. */ +enum +{ + // InspIRCd-specific. + ERR_BADCHANNEL = 926 +}; /** Holds a CBAN item */ class CBan : public XLine { -public: - irc::string matchtext; +private: + std::string matchtext; +public: CBan(time_t s_time, long d, const std::string& src, const std::string& re, const std::string& ch) : XLine(s_time, d, src, re, "CBAN") - { - this->matchtext = ch.c_str(); - } - - ~CBan() + , matchtext(ch) { } // XXX I shouldn't have to define this - bool Matches(User *u) + bool Matches(User* u) CXX11_OVERRIDE { return false; } - bool Matches(const std::string &s) + bool Matches(const std::string& s) CXX11_OVERRIDE { - if (matchtext == s) - return true; - return false; - } - - void DisplayExpiry() - { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired CBan %s (set by %s %ld seconds ago)", - this->matchtext.c_str(), this->source.c_str(), (long int)(ServerInstance->Time() - this->set_time)); + return irc::equals(matchtext, s); } - const char* Displayable() + const std::string& Displayable() CXX11_OVERRIDE { - return matchtext.c_str(); + return matchtext; } }; @@ -76,12 +70,12 @@ class CBanFactory : public XLineFactory /** Generate a CBAN */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { return new CBan(set_time, duration, source, reason, xline_specific_mask); } - bool AutoApplyToUserList(XLine *x) + bool AutoApplyToUserList(XLine* x) CXX11_OVERRIDE { return false; // No, we apply to channels. } @@ -95,10 +89,9 @@ class CommandCBan : public Command CommandCBan(Module* Creator) : Command(Creator, "CBAN", 1, 3) { flags_needed = 'o'; this->syntax = "<channel> [<duration> :<reason>]"; - TRANSLATE4(TR_TEXT,TR_TEXT,TR_TEXT,TR_END); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { /* syntax: CBAN #channel time :reason goes here */ /* 'time' is a human-readable timestring, like 2d3h2s. */ @@ -111,14 +104,14 @@ class CommandCBan : public Command } else { - user->WriteServ("NOTICE %s :*** CBan %s not found in list, try /stats C.",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** CBan " + parameters[0] + " not found in list, try /stats C."); return CMD_FAILURE; } } else { // Adding - XXX todo make this respect <insane> tag perhaps.. - long duration = ServerInstance->Duration(parameters[1]); + unsigned long duration = InspIRCd::Duration(parameters[1]); const char *reason = (parameters.size() > 2) ? parameters[2].c_str() : "No reason supplied"; CBan* r = new CBan(ServerInstance->Time(), duration, user->nick.c_str(), reason, parameters[0].c_str()); @@ -131,21 +124,21 @@ class CommandCBan : public Command else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteGlobalSno('x', "%s added timed CBan for %s, expires on %s: %s", user->nick.c_str(), parameters[0].c_str(), timestr.c_str(), reason); } } else { delete r; - user->WriteServ("NOTICE %s :*** CBan for %s already exists", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** CBan for " + parameters[0] + " already exists"); return CMD_FAILURE; } } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { if (IS_LOCAL(user)) return ROUTE_LOCALONLY; // spanningtree will send ADDLINE @@ -154,61 +147,58 @@ class CommandCBan : public Command } }; -class ModuleCBan : public Module +class ModuleCBan : public Module, public Stats::EventListener { CommandCBan mycommand; CBanFactory f; public: - ModuleCBan() : mycommand(this) + ModuleCBan() + : Stats::EventListener(this) + , mycommand(this) { } - void init() + void init() CXX11_OVERRIDE { ServerInstance->XLines->RegisterFactory(&f); - - ServerInstance->Modules->AddService(mycommand); - Implementation eventlist[] = { I_OnUserPreJoin, I_OnStats }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual ~ModuleCBan() + ~ModuleCBan() { ServerInstance->XLines->DelAll("CBAN"); ServerInstance->XLines->UnregisterFactory(&f); } - virtual ModResult OnStats(char symbol, User* user, string_list &out) + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if (symbol != 'C') + if (stats.GetSymbol() != 'C') return MOD_RES_PASSTHRU; - ServerInstance->XLines->InvokeStats("CBAN", 210, user, out); + ServerInstance->XLines->InvokeStats("CBAN", 210, stats); return MOD_RES_DENY; } - virtual ModResult OnUserPreJoin(User *user, Channel *chan, const char *cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { XLine *rl = ServerInstance->XLines->MatchesLine("CBAN", cname); if (rl) { // Channel is banned. - user->WriteServ( "384 %s %s :Cannot join channel, CBANed (%s)", user->nick.c_str(), cname, rl->reason.c_str()); + user->WriteNumeric(ERR_BADCHANNEL, cname, InspIRCd::Format("Channel %s is CBANed: %s", cname.c_str(), rl->reason.c_str())); ServerInstance->SNO->WriteGlobalSno('a', "%s tried to join %s which is CBANed (%s)", - user->nick.c_str(), cname, rl->reason.c_str()); + user->nick.c_str(), cname.c_str(), rl->reason.c_str()); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Gives /cban, aka C:lines. Think Q:lines, for channels.", VF_COMMON | VF_VENDOR); } }; MODULE_INIT(ModuleCBan) - diff --git a/src/modules/m_censor.cpp b/src/modules/m_censor.cpp index 65b965df2..fedc0f713 100644 --- a/src/modules/m_censor.cpp +++ b/src/modules/m_censor.cpp @@ -20,72 +20,41 @@ */ -/* $ModDesc: Provides user and channel +G mode */ - -#define _CRT_SECURE_NO_DEPRECATE -#define _SCL_SECURE_NO_DEPRECATE - #include "inspircd.h" -#include <iostream> - -typedef std::map<irc::string,irc::string> censor_t; +#include "modules/exemption.h" -/** Handles usermode +G - */ -class CensorUser : public SimpleUserModeHandler -{ - public: - CensorUser(Module* Creator) : SimpleUserModeHandler(Creator, "u_censor", 'G') { } -}; - -/** Handles channel mode +G - */ -class CensorChannel : public SimpleChannelModeHandler -{ - public: - CensorChannel(Module* Creator) : SimpleChannelModeHandler(Creator, "censor", 'G') { } -}; +typedef insp::flat_map<std::string, std::string, irc::insensitive_swo> censor_t; class ModuleCensor : public Module { + CheckExemption::EventProvider exemptionprov; censor_t censors; - CensorUser cu; - CensorChannel cc; + SimpleUserModeHandler cu; + SimpleChannelModeHandler cc; public: - ModuleCensor() : cu(this), cc(this) { } - - void init() - { - /* Read the configuration file on startup. - */ - OnRehash(NULL); - ServerInstance->Modules->AddService(cu); - ServerInstance->Modules->AddService(cc); - Implementation eventlist[] = { I_OnRehash, I_OnUserPreMessage, I_OnUserPreNotice }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - - virtual ~ModuleCensor() + ModuleCensor() + : exemptionprov(this) + , cu(this, "u_censor", 'G') + , cc(this, "censor", 'G') { } // format of a config entry is <badword text="shit" replace="poo"> - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; bool active = false; - if (target_type == TYPE_USER) - active = ((User*)dest)->IsModeSet('G'); - else if (target_type == TYPE_CHANNEL) + if (target.type == MessageTarget::TYPE_USER) + active = target.Get<User>()->IsModeSet(cu); + else if (target.type == MessageTarget::TYPE_CHANNEL) { - active = ((Channel*)dest)->IsModeSet('G'); - Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"censor"); + Channel* c = target.Get<Channel>(); + active = c->IsModeSet(cc); + ModResult res = CheckExemption::Call(exemptionprov, user, c, "censor"); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; @@ -94,30 +63,25 @@ class ModuleCensor : public Module if (!active) return MOD_RES_PASSTHRU; - irc::string text2 = text.c_str(); for (censor_t::iterator index = censors.begin(); index != censors.end(); index++) { - if (text2.find(index->first) != irc::string::npos) + size_t censorpos; + while ((censorpos = irc::find(details.text, index->first)) != std::string::npos) { if (index->second.empty()) { - user->WriteNumeric(ERR_WORDFILTERED, "%s %s %s :Your message contained a censored word, and was blocked", user->nick.c_str(), ((target_type == TYPE_CHANNEL) ? ((Channel*)dest)->name.c_str() : ((User*)dest)->nick.c_str()), index->first.c_str()); + const std::string targname = target.type == MessageTarget::TYPE_CHANNEL ? target.Get<Channel>()->name : target.Get<User>()->nick; + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, targname, "Your message contained a censored word (" + index->first + "), and was blocked"); return MOD_RES_DENY; } - SearchAndReplace(text2, index->first, index->second); + details.text.replace(censorpos, index->first.size(), index->second); } } - text = text2.c_str(); return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreMessage(user,dest,target_type,text,status,exempt_list); - } - - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { /* * reload our config file on rehash - we must destroy and re-allocate the classes @@ -129,14 +93,16 @@ class ModuleCensor : public Module for (ConfigIter i = badwords.first; i != badwords.second; ++i) { ConfigTag* tag = i->second; - std::string str = tag->getString("text"); - irc::string pattern(str.c_str()); - str = tag->getString("replace"); - censors[pattern] = irc::string(str.c_str()); + const std::string text = tag->getString("text"); + if (text.empty()) + continue; + + const std::string replace = tag->getString("replace"); + censors[text] = replace; } } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides user and channel +G mode",VF_VENDOR); } diff --git a/src/modules/m_cgiirc.cpp b/src/modules/m_cgiirc.cpp index 13f1c1fb5..8d911a5b4 100644 --- a/src/modules/m_cgiirc.cpp +++ b/src/modules/m_cgiirc.cpp @@ -24,406 +24,365 @@ #include "inspircd.h" -#include "xline.h" +#include "modules/ssl.h" +#include "modules/whois.h" -/* $ModDesc: Change user's hosts connecting from known CGI:IRC hosts */ +enum +{ + // InspIRCd-specific. + RPL_WHOISGATEWAY = 350 +}; -enum CGItype { PASS, IDENT, PASSFIRST, IDENTFIRST, WEBIRC }; +// We need this method up here so that it can be accessed from anywhere +static void ChangeIP(LocalUser* user, const irc::sockets::sockaddrs& sa) +{ + // Set the users IP address and make sure they are in the right clone pool. + ServerInstance->Users->RemoveCloneCounts(user); + user->SetClientIP(sa); + ServerInstance->Users->AddClone(user); + if (user->quitting) + return; + + // Recheck the connect class. + user->MyClass = NULL; + user->SetClass(); + user->CheckClass(); + if (user->quitting) + return; + + // Check if this user matches any XLines. + user->CheckLines(true); + if (user->quitting) + return; +} + +// Encapsulates information about an ident host. +class IdentHost +{ + private: + std::string hostmask; + std::string newident; + public: + IdentHost(const std::string& mask, const std::string& ident) + : hostmask(mask) + , newident(ident) + { + } -/** Holds a CGI site's details - */ -class CGIhost + const std::string& GetIdent() const + { + return newident; + } + + bool Matches(LocalUser* user) const + { + if (!InspIRCd::Match(user->GetRealHost(), hostmask, ascii_case_insensitive_map)) + return false; + + return InspIRCd::MatchCIDR(user->GetIPString(), hostmask, ascii_case_insensitive_map); + } +}; + +// Encapsulates information about a WebIRC host. +class WebIRCHost { -public: + private: std::string hostmask; - CGItype type; + std::string fingerprint; std::string password; + std::string passhash; - CGIhost(const std::string &mask, CGItype t, const std::string &spassword) - : hostmask(mask), type(t), password(spassword) + public: + WebIRCHost(const std::string& mask, const std::string& fp, const std::string& pass, const std::string& hash) + : hostmask(mask) + , fingerprint(fp) + , password(pass) + , passhash(hash) { } + + bool Matches(LocalUser* user, const std::string& pass) const + { + // Did the user send a valid password? + if (!password.empty() && !ServerInstance->PassCompare(user, password, pass, passhash)) + return false; + + // Does the user have a valid fingerprint? + const std::string fp = SSLClientCert::GetFingerprint(&user->eh); + if (!fingerprint.empty() && fp != fingerprint) + return false; + + // Does the user's hostname match our hostmask? + if (InspIRCd::Match(user->GetRealHost(), hostmask, ascii_case_insensitive_map)) + return true; + + // Does the user's IP address match our hostmask? + return InspIRCd::MatchCIDR(user->GetIPString(), hostmask, ascii_case_insensitive_map); + } }; -typedef std::vector<CGIhost> CGIHostlist; /* * WEBIRC * This is used for the webirc method of CGIIRC auth, and is (really) the best way to do these things. - * Syntax: WEBIRC password client hostname ip - * Where password is a shared key, client is the name of the "client" and version (e.g. cgiirc), hostname + * Syntax: WEBIRC password gateway hostname ip + * Where password is a shared key, gateway is the name of the WebIRC gateway and version (e.g. cgiirc), hostname * is the resolved host of the client issuing the command and IP is the real IP of the client. * * How it works: * To tie in with the rest of cgiirc module, and to avoid race conditions, /webirc is only processed locally * and simply sets metadata on the user, which is later decoded on full connect to give something meaningful. */ -class CommandWebirc : public Command +class CommandWebIRC : public SplitCommand { public: + std::vector<WebIRCHost> hosts; bool notify; + StringExtItem gateway; StringExtItem realhost; StringExtItem realip; - LocalStringExt webirc_hostname; - LocalStringExt webirc_ip; - - CGIHostlist Hosts; - CommandWebirc(Module* Creator) - : Command(Creator, "WEBIRC", 4), - realhost("cgiirc_realhost", Creator), realip("cgiirc_realip", Creator), - webirc_hostname("cgiirc_webirc_hostname", Creator), webirc_ip("cgiirc_webirc_ip", Creator) - { - allow_empty_last_param = false; - works_before_reg = true; - this->syntax = "password client hostname ip"; - } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) - { - if(user->registered == REG_ALL) - return CMD_FAILURE; - - irc::sockets::sockaddrs ipaddr; - if (!irc::sockets::aptosa(parameters[3], 0, ipaddr)) - { - IS_LOCAL(user)->CommandFloodPenalty += 5000; - ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s tried to use WEBIRC but gave an invalid IP address.", user->GetFullRealHost().c_str()); - return CMD_FAILURE; - } - - for(CGIHostlist::iterator iter = Hosts.begin(); iter != Hosts.end(); iter++) - { - if(InspIRCd::Match(user->host, iter->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(user->GetIPString(), iter->hostmask, ascii_case_insensitive_map)) - { - if(iter->type == WEBIRC && parameters[0] == iter->password) - { - realhost.set(user, user->host); - realip.set(user, user->GetIPString()); - - // Check if we're happy with the provided hostname. If it's problematic then use the IP instead. - bool host_ok = (parameters[2].length() < 64) && (parameters[2].find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:-") == std::string::npos); - const std::string& newhost = (host_ok ? parameters[2] : parameters[3]); - - if (notify) - ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s detected as using CGI:IRC (%s), changing real host to %s from %s", user->nick.c_str(), user->host.c_str(), newhost.c_str(), user->host.c_str()); - - webirc_hostname.set(user, newhost); - webirc_ip.set(user, parameters[3]); - return CMD_SUCCESS; - } - } - } - - IS_LOCAL(user)->CommandFloodPenalty += 5000; - ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s tried to use WEBIRC, but didn't match any configured webirc blocks.", user->GetFullRealHost().c_str()); - return CMD_FAILURE; - } -}; - -/** Resolver for CGI:IRC hostnames encoded in ident/GECOS - */ -class CGIResolver : public Resolver -{ - std::string typ; - std::string theiruid; - LocalIntExt& waiting; - bool notify; - public: - CGIResolver(Module* me, bool NotifyOpers, const std::string &source, LocalUser* u, - const std::string &type, bool &cached, LocalIntExt& ext) - : Resolver(source, DNS_QUERY_PTR4, cached, me), typ(type), theiruid(u->uuid), - waiting(ext), notify(NotifyOpers) + CommandWebIRC(Module* Creator) + : SplitCommand(Creator, "WEBIRC", 4) + , gateway("cgiirc_gateway", ExtensionItem::EXT_USER, Creator) + , realhost("cgiirc_realhost", ExtensionItem::EXT_USER, Creator) + , realip("cgiirc_realip", ExtensionItem::EXT_USER, Creator) { + allow_empty_last_param = false; + works_before_reg = true; + this->syntax = "password gateway hostname ip"; } - virtual void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { - /* Check the user still exists */ - User* them = ServerInstance->FindUUID(theiruid); - if ((them) && (!them->quitting)) + if (user->registered == REG_ALL || realhost.get(user)) + return CMD_FAILURE; + + irc::sockets::sockaddrs ipaddr; + if (!irc::sockets::aptosa(parameters[3], user->client_sa.port(), ipaddr)) { - if (notify) - ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s detected as using CGI:IRC (%s), changing real host to %s from %s", them->nick.c_str(), them->host.c_str(), result.c_str(), typ.c_str()); - - if (result.length() > 64) - return; - them->host = result; - them->dhost = result; - them->InvalidateCache(); - them->CheckLines(true); + user->CommandFloodPenalty += 5000; + WriteLog("Connecting user %s (%s) tried to use WEBIRC but gave an invalid IP address.", + user->uuid.c_str(), user->GetIPString().c_str()); + return CMD_FAILURE; } - } - virtual void OnError(ResolverError e, const std::string &errormessage) - { - if (!notify) - return; - - User* them = ServerInstance->FindUUID(theiruid); - if ((them) && (!them->quitting)) + for (std::vector<WebIRCHost>::const_iterator iter = hosts.begin(); iter != hosts.end(); ++iter) { - ServerInstance->SNO->WriteToSnoMask('a', "Connecting user %s detected as using CGI:IRC (%s), but their host can't be resolved from their %s!", them->nick.c_str(), them->host.c_str(), typ.c_str()); + // If we don't match the host then skip to the next host. + if (!iter->Matches(user, parameters[0])) + continue; + + // The user matched a WebIRC block! + gateway.set(user, parameters[1]); + realhost.set(user, user->GetRealHost()); + realip.set(user, user->GetIPString()); + + WriteLog("Connecting user %s is using a WebIRC gateway; changing their IP from %s to %s.", + user->uuid.c_str(), user->GetIPString().c_str(), parameters[3].c_str()); + + // Set the IP address sent via WEBIRC. We ignore the hostname and lookup + // instead do our own DNS lookups because of unreliable gateways. + ChangeIP(user, ipaddr); + return CMD_SUCCESS; } + + user->CommandFloodPenalty += 5000; + WriteLog("Connecting user %s (%s) tried to use WEBIRC but didn't match any configured WebIRC hosts.", + user->uuid.c_str(), user->GetIPString().c_str()); + return CMD_FAILURE; } - virtual ~CGIResolver() + void WriteLog(const char* message, ...) CUSTOM_PRINTF(2, 3) { - User* them = ServerInstance->FindUUID(theiruid); - if (!them) - return; - int count = waiting.get(them); - if (count) - waiting.set(them, count - 1); + std::string buffer; + VAFORMAT(buffer, message, message); + + // If we are sending a snotice then the message will already be + // written to the logfile. + if (notify) + ServerInstance->SNO->WriteGlobalSno('w', buffer); + else + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, buffer); } }; -class ModuleCgiIRC : public Module +class ModuleCgiIRC : public Module, public Whois::EventListener { - CommandWebirc cmd; - LocalIntExt waiting; - - static void RecheckClass(LocalUser* user) - { - user->MyClass = NULL; - user->SetClass(); - user->CheckClass(); - } + private: + CommandWebIRC cmd; + std::vector<IdentHost> hosts; - static void ChangeIP(LocalUser* user, const std::string& newip) + static bool ParseIdent(LocalUser* user, irc::sockets::sockaddrs& out) { - ServerInstance->Users->RemoveCloneCounts(user); - const std::string oldip(user->GetIPString()); - user->SetClientIP(newip.c_str()); - user->InvalidateCache(); - if (user->host == oldip) - user->host = user->GetIPString(); - if (user->dhost == oldip) - user->dhost = user->GetIPString(); - ServerInstance->Users->AddLocalClone(user); - ServerInstance->Users->AddGlobalClone(user); - } - - void HandleIdentOrPass(LocalUser* user, const std::string& newip, bool was_pass) - { - cmd.realhost.set(user, user->host); - cmd.realip.set(user, user->GetIPString()); - user->host = user->dhost = user->GetIPString(); - ChangeIP(user, newip); - user->InvalidateCache(); - RecheckClass(user); - // Don't create the resolver if the core couldn't put the user in a connect class or when dns is disabled - if (user->quitting || ServerInstance->Config->NoUserDns) - return; - - try + const char* ident = NULL; + if (user->ident.length() == 8) + { + // The ident is an IPv4 address encoded in hexadecimal with two characters + // per address segment. + ident = user->ident.c_str(); + } + else if (user->ident.length() == 9 && user->ident[0] == '~') { - bool cached; - CGIResolver* r = new CGIResolver(this, cmd.notify, newip, user, (was_pass ? "PASS" : "IDENT"), cached, waiting); - waiting.set(user, waiting.get(user) + 1); - ServerInstance->AddResolver(r, cached); + // The same as above but m_ident got to this user before we did. Strip the + // ident prefix and continue as normal. + ident = user->ident.c_str() + 1; } - catch (...) + else { - if (cmd.notify) - ServerInstance->SNO->WriteToSnoMask('a', "Connecting user %s detected as using CGI:IRC (%s), but I could not resolve their hostname!", user->nick.c_str(), user->host.c_str()); + // The user either does not have an IPv4 in their ident or the gateway server + // is also running an identd. In the latter case there isn't really a lot we + // can do so we just assume that the client in question is not connecting via + // an ident gateway. + return false; } + + // Try to convert the IP address to a string. If this fails then the user + // does not have an IPv4 address in their ident. + errno = 0; + unsigned long address = strtoul(ident, NULL, 16); + if (errno) + return false; + + out.in4.sin_family = AF_INET; + out.in4.sin_addr.s_addr = htonl(address); + return true; } -public: - ModuleCgiIRC() : cmd(this), waiting("cgiirc-delay", this) + public: + ModuleCgiIRC() + : Whois::EventListener(this) + , cmd(this) { } - void init() + void init() CXX11_OVERRIDE { - OnRehash(NULL); - ServiceProvider* providerlist[] = { &cmd, &cmd.realhost, &cmd.realip, &cmd.webirc_hostname, &cmd.webirc_ip, &waiting }; - ServerInstance->Modules->AddServices(providerlist, sizeof(providerlist)/sizeof(ServiceProvider*)); - - Implementation eventlist[] = { I_OnRehash, I_OnUserRegister, I_OnCheckReady }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + ServerInstance->SNO->EnableSnomask('w', "CGIIRC"); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - cmd.Hosts.clear(); - - // Do we send an oper notice when a CGI:IRC has their host changed? - cmd.notify = ServerInstance->Config->ConfValue("cgiirc")->getBool("opernotice", true); + std::vector<IdentHost> identhosts; + std::vector<WebIRCHost> webirchosts; ConfigTagList tags = ServerInstance->Config->ConfTags("cgihost"); for (ConfigIter i = tags.first; i != tags.second; ++i) { ConfigTag* tag = i->second; - std::string hostmask = tag->getString("mask"); // An allowed CGI:IRC host - std::string type = tag->getString("type"); // What type of user-munging we do on this host. - std::string password = tag->getString("password"); - if(hostmask.length()) + // Ensure that we have the <cgihost:mask> parameter. + const std::string mask = tag->getString("mask"); + if (mask.empty()) + throw ModuleException("<cgihost:mask> is a mandatory field, at " + tag->getTagLocation()); + + // Determine what lookup type this host uses. + const std::string type = tag->getString("type"); + if (stdalgo::string::equalsci(type, "ident")) + { + // The IP address should be looked up from the hex IP address. + const std::string newident = tag->getString("newident", "gateway", ServerInstance->IsIdent); + identhosts.push_back(IdentHost(mask, newident)); + } + else if (stdalgo::string::equalsci(type, "webirc")) { - if (type == "webirc" && password.empty()) - { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc: Missing password in config: %s", hostmask.c_str()); - } - else - { - CGItype cgitype; - if (type == "pass") - cgitype = PASS; - else if (type == "ident") - cgitype = IDENT; - else if (type == "passfirst") - cgitype = PASSFIRST; - else if (type == "webirc") - cgitype = WEBIRC; - else - { - cgitype = PASS; - ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc.so: Invalid <cgihost:type> value in config: %s, setting it to \"pass\"", type.c_str()); - } - - cmd.Hosts.push_back(CGIhost(hostmask, cgitype, password)); - } + // The IP address will be received via the WEBIRC command. + const std::string fingerprint = tag->getString("fingerprint"); + const std::string password = tag->getString("password"); + + // WebIRC blocks require a password. + if (fingerprint.empty() && password.empty()) + throw ModuleException("When using <cgihost type=\"webirc\"> either the fingerprint or password field is required, at " + tag->getTagLocation()); + + webirchosts.push_back(WebIRCHost(mask, fingerprint, password, tag->getString("hash"))); } else { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc.so: Invalid <cgihost:mask> value in config: %s", hostmask.c_str()); - continue; + throw ModuleException(type + " is an invalid <cgihost:mask> type, at " + tag->getTagLocation()); } } + + // The host configuration was valid so we can apply it. + hosts.swap(identhosts); + cmd.hosts.swap(webirchosts); + + // Do we send an oper notice when a m_cgiirc client has their IP changed? + cmd.notify = ServerInstance->Config->ConfValue("cgiirc")->getBool("opernotice", true); } - ModResult OnCheckReady(LocalUser *user) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { - if (waiting.get(user)) - return MOD_RES_DENY; - - std::string *webirc_ip = cmd.webirc_ip.get(user); - if (!webirc_ip) + // If <connect:webirc> is not set then we have nothing to do. + const std::string webirc = myclass->config->getString("webirc"); + if (webirc.empty()) return MOD_RES_PASSTHRU; - std::string* webirc_hostname = cmd.webirc_hostname.get(user); - user->host = user->dhost = (webirc_hostname ? *webirc_hostname : user->GetIPString()); - - ChangeIP(user, *webirc_ip); - user->InvalidateCache(); - - RecheckClass(user); - if (user->quitting) - return MOD_RES_DENY; - - user->CheckLines(true); - if (user->quitting) + // If the user is not connecting via a WebIRC gateway then they + // cannot match this connect class. + const std::string* gateway = cmd.gateway.get(user); + if (!gateway) return MOD_RES_DENY; - cmd.webirc_hostname.unset(user); - cmd.webirc_ip.unset(user); - - return MOD_RES_PASSTHRU; + // If the gateway matches the <connect:webirc> constraint then + // allow the check to continue. Otherwise, reject it. + return InspIRCd::Match(*gateway, webirc) ? MOD_RES_PASSTHRU : MOD_RES_DENY; } - ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { - for(CGIHostlist::iterator iter = cmd.Hosts.begin(); iter != cmd.Hosts.end(); iter++) - { - if(InspIRCd::Match(user->host, iter->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(user->GetIPString(), iter->hostmask, ascii_case_insensitive_map)) - { - // Deal with it... - if(iter->type == PASS) - { - CheckPass(user); // We do nothing if it fails so... - user->CheckLines(true); - } - else if(iter->type == PASSFIRST && !CheckPass(user)) - { - // If the password lookup failed, try the ident - CheckIdent(user); // If this fails too, do nothing - user->CheckLines(true); - } - else if(iter->type == IDENT) - { - CheckIdent(user); // Nothing on failure. - user->CheckLines(true); - } - else if(iter->type == IDENTFIRST && !CheckIdent(user)) - { - // If the ident lookup fails, try the password. - CheckPass(user); - user->CheckLines(true); - } - else if(iter->type == WEBIRC) - { - // We don't need to do anything here - } - return MOD_RES_PASSTHRU; - } - } - return MOD_RES_PASSTHRU; - } + // There is no need to check for gateways if one is already being used. + if (cmd.realhost.get(user)) + return MOD_RES_PASSTHRU; - bool CheckPass(LocalUser* user) - { - if(IsValidHost(user->password)) + for (std::vector<IdentHost>::const_iterator iter = hosts.begin(); iter != hosts.end(); ++iter) { - HandleIdentOrPass(user, user->password, true); - user->password.clear(); - return true; - } - - return false; - } - - bool CheckIdent(LocalUser* user) - { - const char* ident; - in_addr newip; + // If we don't match the host then skip to the next host. + if (!iter->Matches(user)) + continue; - if (user->ident.length() == 8) - ident = user->ident.c_str(); - else if (user->ident.length() == 9 && user->ident[0] == '~') - ident = user->ident.c_str() + 1; - else - return false; + // We have matched an <cgihost> block! Try to parse the encoded IPv4 address + // out of the ident. + irc::sockets::sockaddrs address(user->client_sa); + if (!ParseIdent(user, address)) + return MOD_RES_PASSTHRU; - errno = 0; - unsigned long ipaddr = strtoul(ident, NULL, 16); - if (errno) - return false; - newip.s_addr = htonl(ipaddr); - std::string newipstr(inet_ntoa(newip)); + // Store the hostname and IP of the gateway for later use. + cmd.realhost.set(user, user->GetRealHost()); + cmd.realip.set(user, user->GetIPString()); - user->ident = "~cgiirc"; - HandleIdentOrPass(user, newipstr, false); + const std::string& newident = iter->GetIdent(); + cmd.WriteLog("Connecting user %s is using an ident gateway; changing their IP from %s to %s and their ident from %s to %s.", + user->uuid.c_str(), user->GetIPString().c_str(), address.addr().c_str(), user->ident.c_str(), newident.c_str()); - return true; + user->ChangeIdent(newident); + ChangeIP(user, address); + break; + } + return MOD_RES_PASSTHRU; } - bool IsValidHost(const std::string &host) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - if(!host.size() || host.size() > 64) - return false; - - for(unsigned int i = 0; i < host.size(); i++) - { - if( ((host[i] >= '0') && (host[i] <= '9')) || - ((host[i] >= 'A') && (host[i] <= 'Z')) || - ((host[i] >= 'a') && (host[i] <= 'z')) || - ((host[i] == '-') && (i > 0) && (i+1 < host.size()) && (host[i-1] != '.') && (host[i+1] != '.')) || - ((host[i] == '.') && (i > 0) && (i+1 < host.size())) ) + if (!whois.IsSelfWhois() && !whois.GetSource()->HasPrivPermission("users/auspex")) + return; - continue; - else - return false; - } + // If these fields are not set then the client is not using a gateway. + const std::string* realhost = cmd.realhost.get(whois.GetTarget()); + const std::string* realip = cmd.realip.get(whois.GetTarget()); + if (!realhost || !realip) + return; - return true; + const std::string* gateway = cmd.gateway.get(whois.GetTarget()); + if (gateway) + whois.SendLine(RPL_WHOISGATEWAY, *realhost, *realip, "is connected via the " + *gateway + " WebIRC gateway"); + else + whois.SendLine(RPL_WHOISGATEWAY, *realhost, *realip, "is connected via an ident gateway"); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Change user's hosts connecting from known CGI:IRC hosts",VF_VENDOR); + return Version("Enables forwarding the real IP address of a user from a gateway to the IRC server", VF_VENDOR); } - }; MODULE_INIT(ModuleCgiIRC) diff --git a/src/modules/m_chancreate.cpp b/src/modules/m_chancreate.cpp index 997a92648..6cf4af235 100644 --- a/src/modules/m_chancreate.cpp +++ b/src/modules/m_chancreate.cpp @@ -21,26 +21,20 @@ #include "inspircd.h" -/* $ModDesc: Provides snomasks 'j' and 'J', to which notices about newly created channels are sent */ - class ModuleChanCreate : public Module { - private: public: - void init() + void init() CXX11_OVERRIDE { ServerInstance->SNO->EnableSnomask('j', "CHANCREATE"); - Implementation eventlist[] = { I_OnUserJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides snomasks 'j' and 'J', to which notices about newly created channels are sent",VF_VENDOR); } - - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& except) + void OnUserJoin(Membership* memb, bool sync, bool created, CUList& except) CXX11_OVERRIDE { if ((created) && (IS_LOCAL(memb->user))) { diff --git a/src/modules/m_chanfilter.cpp b/src/modules/m_chanfilter.cpp index 651e659b5..903aab33a 100644 --- a/src/modules/m_chanfilter.cpp +++ b/src/modules/m_chanfilter.cpp @@ -23,97 +23,107 @@ */ -/* $ModDesc: Provides channel-specific censor lists (like mode +G but varies from channel to channel) */ - -#define _CRT_SECURE_NO_DEPRECATE -#define _SCL_SECURE_NO_DEPRECATE - #include "inspircd.h" -#include "u_listmode.h" +#include "listmode.h" +#include "modules/exemption.h" + +enum +{ + // InspIRCd-specific. + ERR_ALREADYCHANFILTERED = 937, + ERR_NOSUCHCHANFILTER = 938, + ERR_CHANFILTERFULL = 939 +}; /** Handles channel mode +g */ class ChanFilter : public ListModeBase { public: + unsigned long maxlen; + ChanFilter(Module* Creator) : ListModeBase(Creator, "filter", 'g', "End of channel spamfilter list", 941, 940, false, "chanfilter") { } - virtual bool ValidateParam(User* user, Channel* chan, std::string &word) + bool ValidateParam(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - if ((word.length() > 35) || (word.empty())) + if (word.length() > maxlen) { - user->WriteNumeric(935, "%s %s %s :word is too %s for censor list",user->nick.c_str(), chan->name.c_str(), word.c_str(), (word.empty() ? "short" : "long")); + user->WriteNumeric(Numerics::InvalidModeParameter(chan, this, word, "Word is too long for the spamfilter list")); return false; } return true; } - virtual bool TellListTooLong(User* user, Channel* chan, std::string &word) + void TellListTooLong(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - user->WriteNumeric(939, "%s %s %s :Channel spamfilter list is full", user->nick.c_str(), chan->name.c_str(), word.c_str()); - return true; + user->WriteNumeric(ERR_CHANFILTERFULL, chan->name, word, "Channel spamfilter list is full"); } - virtual void TellAlreadyOnList(User* user, Channel* chan, std::string &word) + void TellAlreadyOnList(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - user->WriteNumeric(937, "%s %s :The word %s is already on the spamfilter list",user->nick.c_str(), chan->name.c_str(), word.c_str()); + user->WriteNumeric(ERR_ALREADYCHANFILTERED, chan->name, InspIRCd::Format("The word %s is already on the spamfilter list", word.c_str())); } - virtual void TellNotSet(User* user, Channel* chan, std::string &word) + void TellNotSet(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - user->WriteNumeric(938, "%s %s :No such spamfilter word is set",user->nick.c_str(), chan->name.c_str()); + user->WriteNumeric(ERR_NOSUCHCHANFILTER, chan->name, "No such spamfilter word is set"); } }; class ModuleChanFilter : public Module { + CheckExemption::EventProvider exemptionprov; ChanFilter cf; bool hidemask; + bool notifyuser; public: ModuleChanFilter() - : cf(this) + : exemptionprov(this) + , cf(this) { } - void init() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(cf); - - cf.DoImplements(this); - Implementation eventlist[] = { I_OnRehash, I_OnUserPreMessage, I_OnUserPreNotice, I_OnSyncChannel }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - OnRehash(NULL); - } - - virtual void OnRehash(User* user) - { - hidemask = ServerInstance->Config->ConfValue("chanfilter")->getBool("hidemask"); + ConfigTag* tag = ServerInstance->Config->ConfValue("chanfilter"); + hidemask = tag->getBool("hidemask"); + cf.maxlen = tag->getUInt("maxlen", 35, 10, 100); + notifyuser = tag->getBool("notifyuser", true); cf.DoRehash(); } - virtual ModResult ProcessMessages(User* user,Channel* chan,std::string &text) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - ModResult res = ServerInstance->OnCheckExemption(user,chan,"filter"); + if (target.type != MessageTarget::TYPE_CHANNEL) + return MOD_RES_PASSTHRU; + + Channel* chan = target.Get<Channel>(); + ModResult res = CheckExemption::Call(exemptionprov, user, chan, "filter"); if (!IS_LOCAL(user) || res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; - modelist* list = cf.extItem.get(chan); + ListModeBase::ModeList* list = cf.GetList(chan); if (list) { - for (modelist::iterator i = list->begin(); i != list->end(); i++) + for (ListModeBase::ModeList::iterator i = list->begin(); i != list->end(); i++) { - if (InspIRCd::Match(text, i->mask)) + if (InspIRCd::Match(details.text, i->mask)) { + if (!notifyuser) + { + details.echo_original = true; + return MOD_RES_DENY; + } + if (hidemask) - user->WriteNumeric(404, "%s %s :Cannot send to channel (your message contained a censored word)",user->nick.c_str(), chan->name.c_str()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (your message contained a censored word)"); else - user->WriteNumeric(404, "%s %s %s :Cannot send to channel (your message contained a censored word)",user->nick.c_str(), chan->name.c_str(), i->mask.c_str()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (your message contained a censored word: " + i->mask + ")"); return MOD_RES_DENY; } } @@ -122,32 +132,14 @@ class ModuleChanFilter : public Module return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + Version GetVersion() CXX11_OVERRIDE { - if (target_type == TYPE_CHANNEL) - { - return ProcessMessages(user,(Channel*)dest,text); - } - return MOD_RES_PASSTHRU; - } - - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreMessage(user,dest,target_type,text,status,exempt_list); - } + // We don't send any link data if the length is 35 for compatibility with the 2.0 branch. + std::string maxfilterlen; + if (cf.maxlen != 35) + maxfilterlen.assign(ConvToStr(cf.maxlen)); - virtual void OnSyncChannel(Channel* chan, Module* proto, void* opaque) - { - cf.DoSyncChannel(chan, proto, opaque); - } - - virtual Version GetVersion() - { - return Version("Provides channel-specific censor lists (like mode +G but varies from channel to channel)", VF_VENDOR); - } - - virtual ~ModuleChanFilter() - { + return Version("Provides channel-specific censor lists (like mode +G but varies from channel to channel)", VF_VENDOR, maxfilterlen); } }; diff --git a/src/modules/m_chanhistory.cpp b/src/modules/m_chanhistory.cpp index 08f316578..fe4bd9477 100644 --- a/src/modules/m_chanhistory.cpp +++ b/src/modules/m_chanhistory.cpp @@ -18,24 +18,34 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel history for a given number of lines */ +#include "modules/ircv3_servertime.h" +#include "modules/ircv3_batch.h" struct HistoryItem { time_t ts; - std::string line; - HistoryItem(const std::string& Line) : ts(ServerInstance->Time()), line(Line) {} + std::string text; + std::string sourcemask; + + HistoryItem(User* source, const std::string& Text) + : ts(ServerInstance->Time()) + , text(Text) + , sourcemask(source->GetFullHost()) + { + } }; struct HistoryList { std::deque<HistoryItem> lines; unsigned int maxlen, maxtime; - HistoryList(unsigned int len, unsigned int time) : maxlen(len), maxtime(time) {} + std::string param; + + HistoryList(unsigned int len, unsigned int time, const std::string& oparam) + : maxlen(len), maxtime(time), param(oparam) { } }; -class HistoryMode : public ModeHandler +class HistoryMode : public ParamMode<HistoryMode, SimpleExtItem<HistoryList> > { bool IsValidDuration(const std::string& duration) { @@ -52,113 +62,114 @@ class HistoryMode : public ModeHandler } public: - SimpleExtItem<HistoryList> ext; unsigned int maxlines; - HistoryMode(Module* Creator) : ModeHandler(Creator, "history", 'H', PARAM_SETONLY, MODETYPE_CHANNEL), - ext("history", Creator) { } + HistoryMode(Module* Creator) + : ParamMode<HistoryMode, SimpleExtItem<HistoryList> >(Creator, "history", 'H') + { + } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE { - if (adding) + std::string::size_type colon = parameter.find(':'); + if (colon == std::string::npos) { - std::string::size_type colon = parameter.find(':'); - if (colon == std::string::npos) - return MODEACTION_DENY; - - std::string duration = parameter.substr(colon+1); - if ((IS_LOCAL(source)) && ((duration.length() > 10) || (!IsValidDuration(duration)))) - return MODEACTION_DENY; - - unsigned int len = ConvToInt(parameter.substr(0, colon)); - int time = ServerInstance->Duration(duration); - if (len == 0 || time < 0) - return MODEACTION_DENY; - if (len > maxlines && IS_LOCAL(source)) - return MODEACTION_DENY; - if (len > maxlines) - len = maxlines; - if (parameter == channel->GetModeParameter(this)) - return MODEACTION_DENY; - - HistoryList* history = ext.get(channel); - if (history) - { - // Shrink the list if the new line number limit is lower than the old one - if (len < history->lines.size()) - history->lines.erase(history->lines.begin(), history->lines.begin() + (history->lines.size() - len)); + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; + } - history->maxlen = len; - history->maxtime = time; - } - else - { - ext.set(channel, new HistoryList(len, time)); - } - channel->SetModeParam('H', parameter); + std::string duration(parameter, colon+1); + if ((IS_LOCAL(source)) && ((duration.length() > 10) || (!IsValidDuration(duration)))) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; + } + + unsigned int len = ConvToInt(parameter.substr(0, colon)); + unsigned int time = InspIRCd::Duration(duration); + if (len == 0 || (len > maxlines && IS_LOCAL(source))) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; + } + if (len > maxlines) + len = maxlines; + + HistoryList* history = ext.get(channel); + if (history) + { + // Shrink the list if the new line number limit is lower than the old one + if (len < history->lines.size()) + history->lines.erase(history->lines.begin(), history->lines.begin() + (history->lines.size() - len)); + + history->maxlen = len; + history->maxtime = time; + history->param = parameter; } else { - if (!channel->IsModeSet('H')) - return MODEACTION_DENY; - ext.unset(channel); - channel->SetModeParam('H', ""); + ext.set(channel, new HistoryList(len, time, parameter)); } return MODEACTION_ALLOW; } + + void SerializeParam(Channel* chan, const HistoryList* history, std::string& out) + { + out.append(history->param); + } }; class ModuleChanHistory : public Module { HistoryMode m; bool sendnotice; + UserModeReference botmode; bool dobots; - public: - ModuleChanHistory() : m(this) - { - } + IRCv3::Batch::CapReference batchcap; + IRCv3::Batch::API batchmanager; + IRCv3::Batch::Batch batch; + IRCv3::ServerTime::API servertimemanager; - void init() + public: + ModuleChanHistory() + : m(this) + , botmode(this, "bot") + , batchcap(this) + , batchmanager(this) + , batch("chathistory") + , servertimemanager(this) { - ServerInstance->Modules->AddService(m); - ServerInstance->Modules->AddService(m.ext); - - Implementation eventlist[] = { I_OnPostJoin, I_OnUserMessage, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); } - void OnRehash(User*) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("chanhistory"); - m.maxlines = tag->getInt("maxlines", 50); + m.maxlines = tag->getUInt("maxlines", 50, 1); sendnotice = tag->getBool("notice", true); dobots = tag->getBool("bots", true); } - void OnUserMessage(User* user,void* dest,int target_type, const std::string &text, char status, const CUList&) + void OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE { - if (target_type == TYPE_CHANNEL && status == 0) + if ((target.type == MessageTarget::TYPE_CHANNEL) && (target.status == 0) && (details.type == MSG_PRIVMSG)) { - Channel* c = (Channel*)dest; + Channel* c = target.Get<Channel>(); HistoryList* list = m.ext.get(c); if (list) { - char buf[MAXBUF]; - snprintf(buf, MAXBUF, ":%s PRIVMSG %s :%s", - user->GetFullHost().c_str(), c->name.c_str(), text.c_str()); - list->lines.push_back(HistoryItem(buf)); + list->lines.push_back(HistoryItem(user, details.text)); if (list->lines.size() > list->maxlen) list->lines.pop_front(); } } } - void OnPostJoin(Membership* memb) + void OnPostJoin(Membership* memb) CXX11_OVERRIDE { - if (IS_REMOTE(memb->user)) + LocalUser* localuser = IS_LOCAL(memb->user); + if (!localuser) return; - if (!dobots && ServerInstance->Modules->Find("m_botmode.so") && memb->user->IsModeSet('B')) + if (memb->user->IsModeSet(botmode) && !dobots) return; HistoryList* list = m.ext.get(memb->chan); @@ -168,20 +179,38 @@ class ModuleChanHistory : public Module if (list->maxtime) mintime = ServerInstance->Time() - list->maxtime; - if (sendnotice) + if ((sendnotice) && (!batchcap.get(localuser))) + { + std::string message("Replaying up to " + ConvToStr(list->maxlen) + " lines of pre-join history"); + if (list->maxtime > 0) + message.append(" spanning up to " + ConvToStr(list->maxtime) + " seconds"); + memb->WriteNotice(message); + } + + if (batchmanager) { - memb->user->WriteServ("NOTICE %s :Replaying up to %d lines of pre-join history spanning up to %d seconds", - memb->chan->name.c_str(), list->maxlen, list->maxtime); + batchmanager->Start(batch); + batch.GetBatchStartMessage().PushParamRef(memb->chan->name); } for(std::deque<HistoryItem>::iterator i = list->lines.begin(); i != list->lines.end(); ++i) { - if (i->ts >= mintime) - memb->user->Write(i->line); + const HistoryItem& item = *i; + if (item.ts >= mintime) + { + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, item.sourcemask, memb->chan, item.text); + if (servertimemanager) + servertimemanager->Set(msg, item.ts); + batch.AddToBatch(msg); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); + } } + + if (batchmanager) + batchmanager->End(batch); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel history replayed on join", VF_VENDOR); } diff --git a/src/modules/m_chanlog.cpp b/src/modules/m_chanlog.cpp index 6dbc0e7a8..85e7ca2eb 100644 --- a/src/modules/m_chanlog.cpp +++ b/src/modules/m_chanlog.cpp @@ -20,31 +20,16 @@ #include "inspircd.h" -/* $ModDesc: Logs snomask output to channel(s). */ - class ModuleChanLog : public Module { - private: /* * Multimap so people can redirect a snomask to multiple channels. */ - typedef std::multimap<char, std::string> ChanLogTargets; + typedef insp::flat_multimap<char, std::string> ChanLogTargets; ChanLogTargets logstreams; public: - void init() - { - Implementation eventlist[] = { I_OnRehash, I_OnSendSnotice }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - OnRehash(NULL); - } - - virtual ~ModuleChanLog() - { - } - - virtual void OnRehash(User *user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { std::string snomasks; std::string channel; @@ -59,100 +44,45 @@ class ModuleChanLog : public Module if (channel.empty() || snomasks.empty()) { - ServerInstance->Logs->Log("m_chanlog", DEFAULT, "Malformed chanlog tag, ignoring"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Malformed chanlog tag, ignoring"); continue; } for (std::string::const_iterator it = snomasks.begin(); it != snomasks.end(); it++) { logstreams.insert(std::make_pair(*it, channel)); - ServerInstance->Logs->Log("m_chanlog", DEFAULT, "Logging %c to %s", *it, channel.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Logging %c to %s", *it, channel.c_str()); } } } - virtual ModResult OnSendSnotice(char &sno, std::string &desc, const std::string &msg) + ModResult OnSendSnotice(char &sno, std::string &desc, const std::string &msg) CXX11_OVERRIDE { std::pair<ChanLogTargets::const_iterator, ChanLogTargets::const_iterator> itpair = logstreams.equal_range(sno); if (itpair.first == itpair.second) return MOD_RES_PASSTHRU; - char buf[MAXBUF]; - snprintf(buf, MAXBUF, "\2%s\2: %s", desc.c_str(), msg.c_str()); + const std::string snotice = "\2" + desc + "\2: " + msg; for (ChanLogTargets::const_iterator it = itpair.first; it != itpair.second; ++it) { Channel *c = ServerInstance->FindChan(it->second); if (c) { - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "PRIVMSG %s :%s", c->name.c_str(), buf); - ServerInstance->PI->SendChannelPrivmsg(c, 0, buf); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->Config->ServerName, c, snotice); + c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg); + ServerInstance->PI->SendMessage(c, 0, snotice); } } return MOD_RES_PASSTHRU; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Logs snomask output to channel(s).", VF_VENDOR); } }; - MODULE_INIT(ModuleChanLog) - - - - - - - - - -/* - * This is for the "old" chanlog module which intercepted messages going to the logfile.. - * I don't consider it all that useful, and it's quite dangerous if setup incorrectly, so - * this is defined out but left intact in case someone wants to develop it further someday. - * - * -- w00t (aug 23rd, 2008) - */ -#define OLD_CHANLOG 0 - -#if OLD_CHANLOG -class ChannelLogStream : public LogStream -{ - private: - std::string channel; - - public: - ChannelLogStream(int loglevel, const std::string &chan) : LogStream(loglevel), channel(chan) - { - } - - virtual void OnLog(int loglevel, const std::string &type, const std::string &msg) - { - Channel *c = ServerInstance->FindChan(channel); - static bool Logging = false; - - if (loglevel < this->loglvl) - return; - - if (Logging) - return; - - if (c) - { - Logging = true; // this avoids (rare chance) loops with logging server IO on networks - char buf[MAXBUF]; - snprintf(buf, MAXBUF, "\2%s\2: %s", type.c_str(), msg.c_str()); - - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "PRIVMSG %s :%s", c->name.c_str(), buf); - ServerInstance->PI->SendChannelPrivmsg(c, 0, buf); - Logging = false; - } - } -}; -#endif - diff --git a/src/modules/m_channames.cpp b/src/modules/m_channames.cpp index 325e8fee1..d0d122b43 100644 --- a/src/modules/m_channames.cpp +++ b/src/modules/m_channames.cpp @@ -19,83 +19,77 @@ #include "inspircd.h" -/* $ModDesc: Implements config tags which allow changing characters allowed in channel names */ - static std::bitset<256> allowedmap; -class NewIsChannelHandler : public HandlerBase2<bool, const char*, size_t> +class NewIsChannelHandler { public: - NewIsChannelHandler() { } - virtual ~NewIsChannelHandler() { } - virtual bool Call(const char*, size_t); + static bool Call(const std::string&); }; -bool NewIsChannelHandler::Call(const char* c, size_t max) +bool NewIsChannelHandler::Call(const std::string& channame) { - /* check for no name - don't check for !*chname, as if it is empty, it won't be '#'! */ - if (!c || *c++ != '#') + if (channame.empty() || channame.length() > ServerInstance->Config->Limits.ChanMax || channame[0] != '#') + return false; + + for (std::string::const_iterator c = channame.begin(); c != channame.end(); ++c) + { + unsigned int i = *c & 0xFF; + if (!allowedmap[i]) return false; + } - while (*c && --max) - { - unsigned int i = *c++ & 0xFF; - if (!allowedmap[i]) - return false; - } - // a name of exactly max length will have max = 1 here; the null does not trigger --max - return max; + return true; } class ModuleChannelNames : public Module { - private: - NewIsChannelHandler myhandler; - caller2<bool, const char*, size_t> rememberer; + TR1NS::function<bool(const std::string&)> rememberer; bool badchan; + ChanModeReference permchannelmode; public: - ModuleChannelNames() : rememberer(ServerInstance->IsChannel), badchan(false) + ModuleChannelNames() + : rememberer(ServerInstance->IsChannel) + , badchan(false) + , permchannelmode(this, "permanent") { } - void init() + void init() CXX11_OVERRIDE { - ServerInstance->IsChannel = &myhandler; - Implementation eventlist[] = { I_OnRehash, I_OnUserKick }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); + ServerInstance->IsChannel = NewIsChannelHandler::Call; } void ValidateChans() { + Modes::ChangeList removepermchan; + badchan = true; - std::vector<Channel*> chanvec; - for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); ++i) - { - if (!ServerInstance->IsChannel(i->second->name.c_str(), MAXBUF)) - chanvec.push_back(i->second); - } - std::vector<Channel*>::reverse_iterator c2 = chanvec.rbegin(); - while (c2 != chanvec.rend()) + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ) { - Channel* c = *c2++; - if (c->IsModeSet('P') && c->GetUserCounter()) - { - std::vector<std::string> modes; - modes.push_back(c->name); - modes.push_back("-P"); + Channel* c = i->second; + // Move iterator before we begin kicking + ++i; + if (ServerInstance->IsChannel(c->name)) + continue; // The name of this channel is still valid - ServerInstance->SendGlobalMode(modes, ServerInstance->FakeClient); + if (c->IsModeSet(permchannelmode) && c->GetUserCounter()) + { + removepermchan.clear(); + removepermchan.push_remove(*permchannelmode); + ServerInstance->Modes->Process(ServerInstance->FakeClient, c, NULL, removepermchan); } - const UserMembList* users = c->GetUsers(); - for(UserMembCIter j = users->begin(); j != users->end(); ) + + Channel::MemberMap& users = c->userlist; + for (Channel::MemberMap::iterator j = users.begin(); j != users.end(); ) { if (IS_LOCAL(j->first)) { // KickUser invalidates the iterator - UserMembCIter it = j++; - c->KickUser(ServerInstance->FakeClient, it->first, "Channel name no longer valid"); + Channel::MemberMap::iterator it = j++; + c->KickUser(ServerInstance->FakeClient, it, "Channel name no longer valid"); } else ++j; @@ -104,7 +98,7 @@ class ModuleChannelNames : public Module badchan = false; } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("channames"); std::string denyToken = tag->getString("denyrange"); @@ -134,24 +128,25 @@ class ModuleChannelNames : public Module ValidateChans(); } - virtual void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& except_list) + void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& except_list) CXX11_OVERRIDE { if (badchan) { - const UserMembList* users = memb->chan->GetUsers(); - for(UserMembCIter i = users->begin(); i != users->end(); i++) + const Channel::MemberMap& users = memb->chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) if (i->first != memb->user) except_list.insert(i->first); } } - virtual ~ModuleChannelNames() + CullResult cull() CXX11_OVERRIDE { ServerInstance->IsChannel = rememberer; ValidateChans(); + return Module::cull(); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements config tags which allow changing characters allowed in channel names", VF_VENDOR); } diff --git a/src/modules/m_channelban.cpp b/src/modules/m_channelban.cpp index 6eec486ea..ffb43eef1 100644 --- a/src/modules/m_channelban.cpp +++ b/src/modules/m_channelban.cpp @@ -20,50 +20,31 @@ #include "inspircd.h" -/* $ModDesc: Implements extban +b j: - matching channel bans */ - class ModuleBadChannelExtban : public Module { - private: public: - void init() - { - Implementation eventlist[] = { I_OnCheckBan, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ~ModuleBadChannelExtban() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Extban 'j' - channel status/join ban", VF_OPTCOMMON|VF_VENDOR); } - ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) + ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) CXX11_OVERRIDE { if ((mask.length() > 2) && (mask[0] == 'j') && (mask[1] == ':')) { - std::string rm = mask.substr(2); + std::string rm(mask, 2); char status = 0; - ModeHandler* mh = ServerInstance->Modes->FindPrefix(rm[0]); + const PrefixMode* const mh = ServerInstance->Modes->FindPrefix(rm[0]); if (mh) { - rm = mask.substr(3); + rm.assign(mask, 3, std::string::npos); status = mh->GetModeChar(); } - for (UCListIter i = user->chans.begin(); i != user->chans.end(); i++) + for (User::ChanList::iterator i = user->chans.begin(); i != user->chans.end(); i++) { - if (InspIRCd::Match((**i).name, rm)) + if (InspIRCd::Match((*i)->chan->name, rm)) { - if (status) - { - Membership* memb = (**i).GetUser(user); - if (memb && memb->hasMode(status)) - return MOD_RES_DENY; - } - else + if ((!status) || ((*i)->HasMode(mh))) return MOD_RES_DENY; } } @@ -71,12 +52,10 @@ class ModuleBadChannelExtban : public Module return MOD_RES_PASSTHRU; } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('j'); + tokens["EXTBAN"].push_back('j'); } }; - MODULE_INIT(ModuleBadChannelExtban) - diff --git a/src/modules/m_chanprotect.cpp b/src/modules/m_chanprotect.cpp deleted file mode 100644 index affd0c8d6..000000000 --- a/src/modules/m_chanprotect.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2006-2009 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> - * Copyright (C) 2004-2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -/* $ModDesc: Provides channel modes +a and +q */ - -#define PROTECT_VALUE 40000 -#define FOUNDER_VALUE 50000 - -struct ChanProtectSettings -{ - bool DeprivSelf; - bool DeprivOthers; - bool FirstInGetsFounder; - bool booting; - ChanProtectSettings() : booting(true) {} -}; - -static ChanProtectSettings settings; - -/** Handles basic operation of +qa channel modes - */ -class FounderProtectBase -{ - private: - const std::string type; - const char mode; - const int list; - const int end; - public: - FounderProtectBase(char Mode, const std::string &mtype, int l, int e) : - type(mtype), mode(Mode), list(l), end(e) - { - } - - void RemoveMode(Channel* channel, irc::modestacker* stack) - { - const UserMembList* cl = channel->GetUsers(); - std::vector<std::string> mode_junk; - mode_junk.push_back(channel->name); - irc::modestacker modestack(false); - std::deque<std::string> stackresult; - - for (UserMembCIter i = cl->begin(); i != cl->end(); i++) - { - if (i->second->hasMode(mode)) - { - if (stack) - stack->Push(mode, i->first->nick); - else - modestack.Push(mode, i->first->nick); - } - } - - if (stack) - return; - - while (modestack.GetStackedLine(stackresult)) - { - mode_junk.insert(mode_junk.end(), stackresult.begin(), stackresult.end()); - ServerInstance->SendMode(mode_junk, ServerInstance->FakeClient); - mode_junk.erase(mode_junk.begin() + 1, mode_junk.end()); - } - } - - void DisplayList(User* user, Channel* channel) - { - const UserMembList* cl = channel->GetUsers(); - for (UserMembCIter i = cl->begin(); i != cl->end(); ++i) - { - if (i->second->hasMode(mode)) - { - user->WriteServ("%d %s %s %s", list, user->nick.c_str(), channel->name.c_str(), i->first->nick.c_str()); - } - } - user->WriteServ("%d %s %s :End of channel %s list", end, user->nick.c_str(), channel->name.c_str(), type.c_str()); - } - - bool CanRemoveOthers(User* u1, Channel* c) - { - Membership* m1 = c->GetUser(u1); - return (settings.DeprivOthers && m1 && m1->hasMode(mode)); - } -}; - -/** Abstraction of FounderProtectBase for channel mode +q - */ -class ChanFounder : public ModeHandler, public FounderProtectBase -{ - public: - ChanFounder(Module* Creator) - : ModeHandler(Creator, "founder", 'q', PARAM_ALWAYS, MODETYPE_CHANNEL), - FounderProtectBase('q', "founder", 386, 387) - { - ModeHandler::list = true; - levelrequired = FOUNDER_VALUE; - m_paramtype = TR_NICK; - } - - void setPrefix(int pfx) - { - prefix = pfx; - } - - unsigned int GetPrefixRank() - { - return FOUNDER_VALUE; - } - - void RemoveMode(Channel* channel, irc::modestacker* stack) - { - FounderProtectBase::RemoveMode(channel, stack); - } - - void RemoveMode(User* user, irc::modestacker* stack) - { - } - - ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) - { - User* theuser = ServerInstance->FindNick(parameter); - // remove own privs? - if (source == theuser && !adding && settings.DeprivSelf) - return MOD_RES_ALLOW; - - if (!adding && FounderProtectBase::CanRemoveOthers(source, channel)) - { - return MOD_RES_PASSTHRU; - } - else - { - source->WriteNumeric(468, "%s %s :Only servers may set channel mode +q", source->nick.c_str(), channel->name.c_str()); - return MOD_RES_DENY; - } - } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - return MODEACTION_ALLOW; - } - - void DisplayList(User* user, Channel* channel) - { - FounderProtectBase::DisplayList(user,channel); - } -}; - -/** Abstraction of FounderProtectBase for channel mode +a - */ -class ChanProtect : public ModeHandler, public FounderProtectBase -{ - public: - ChanProtect(Module* Creator) - : ModeHandler(Creator, "admin", 'a', PARAM_ALWAYS, MODETYPE_CHANNEL), - FounderProtectBase('a',"protected user", 388, 389) - { - ModeHandler::list = true; - levelrequired = PROTECT_VALUE; - m_paramtype = TR_NICK; - } - - void setPrefix(int pfx) - { - prefix = pfx; - } - - - unsigned int GetPrefixRank() - { - return PROTECT_VALUE; - } - - void RemoveMode(Channel* channel, irc::modestacker* stack) - { - FounderProtectBase::RemoveMode(channel, stack); - } - - void RemoveMode(User* user, irc::modestacker* stack) - { - } - - ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) - { - User* theuser = ServerInstance->FindNick(parameter); - // source has +q - if (channel->GetPrefixValue(source) > PROTECT_VALUE) - return MOD_RES_ALLOW; - - // removing own privs? - if (source == theuser && !adding && settings.DeprivSelf) - return MOD_RES_ALLOW; - - if (!adding && FounderProtectBase::CanRemoveOthers(source, channel)) - { - return MOD_RES_PASSTHRU; - } - else - { - source->WriteNumeric(482, "%s %s :You are not a channel founder", source->nick.c_str(), channel->name.c_str()); - return MOD_RES_DENY; - } - } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - return MODEACTION_ALLOW; - } - - void DisplayList(User* user, Channel* channel) - { - FounderProtectBase::DisplayList(user, channel); - } - -}; - -class ModuleChanProtect : public Module -{ - ChanProtect cp; - ChanFounder cf; - public: - ModuleChanProtect() : cp(this), cf(this) - { - } - - void init() - { - /* Load config stuff */ - LoadSettings(); - settings.booting = false; - - ServerInstance->Modules->AddService(cf); - ServerInstance->Modules->AddService(cp); - - Implementation eventlist[] = { I_OnUserPreJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void LoadSettings() - { - ConfigTag* tag = ServerInstance->Config->ConfValue("chanprotect"); - - settings.FirstInGetsFounder = tag->getBool("noservices"); - - std::string qpre = tag->getString("qprefix"); - char QPrefix = qpre.empty() ? 0 : qpre[0]; - - std::string apre = tag->getString("aprefix"); - char APrefix = apre.empty() ? 0 : apre[0]; - - if ((APrefix && QPrefix) && APrefix == QPrefix) - throw ModuleException("What the smeg, why are both your +q and +a prefixes the same character?"); - - if (settings.booting) - { - if (APrefix && ServerInstance->Modes->FindPrefix(APrefix) && ServerInstance->Modes->FindPrefix(APrefix) != &cp) - throw ModuleException("Looks like the +a prefix you picked for m_chanprotect is already in use. Pick another."); - - if (QPrefix && ServerInstance->Modes->FindPrefix(QPrefix) && ServerInstance->Modes->FindPrefix(QPrefix) != &cf) - throw ModuleException("Looks like the +q prefix you picked for m_chanprotect is already in use. Pick another."); - - cp.setPrefix(APrefix); - cf.setPrefix(QPrefix); - } - settings.DeprivSelf = tag->getBool("deprotectself", true); - settings.DeprivOthers = tag->getBool("deprotectothers", true); - } - - ModResult OnUserPreJoin(User *user, Channel *chan, const char *cname, std::string &privs, const std::string &keygiven) - { - // if the user is the first user into the channel, mark them as the founder, but only if - // the config option for it is set - - if (settings.FirstInGetsFounder && !chan) - privs += 'q'; - - return MOD_RES_PASSTHRU; - } - - Version GetVersion() - { - return Version("Founder and Protect modes (+qa)", VF_VENDOR); - } -}; - -MODULE_INIT(ModuleChanProtect) diff --git a/src/modules/m_check.cpp b/src/modules/m_check.cpp index 5063368b8..f820f2bd0 100644 --- a/src/modules/m_check.cpp +++ b/src/modules/m_check.cpp @@ -20,16 +20,112 @@ */ -/* $ModDesc: Provides the /CHECK command to retrieve information on a user, channel, hostname or IP address */ - #include "inspircd.h" +#include "listmode.h" + +enum +{ + RPL_CHECK = 802 +}; + +class CheckContext +{ + User* const user; + const std::string& target; + + public: + CheckContext(User* u, const std::string& targetstr) + : user(u) + , target(targetstr) + { + Write("START", target); + } + + ~CheckContext() + { + Write("END", target); + } + + void Write(const std::string& type, const std::string& text) + { + user->WriteRemoteNumeric(RPL_CHECK, type, text); + } + + User* GetUser() const { return user; } + + void DumpListMode(const ListModeBase::ModeList* list) + { + if (!list) + return; + + CheckContext::List modelist(*this, "modelist"); + for (ListModeBase::ModeList::const_iterator i = list->begin(); i != list->end(); ++i) + modelist.Add(i->mask); + + modelist.Flush(); + } + + void DumpExt(Extensible* ext) + { + CheckContext::List extlist(*this, "metadata"); + for(Extensible::ExtensibleStore::const_iterator i = ext->GetExtList().begin(); i != ext->GetExtList().end(); ++i) + { + ExtensionItem* item = i->first; + std::string value = item->serialize(FORMAT_USER, ext, i->second); + if (!value.empty()) + Write("meta:" + item->name, value); + else if (!item->name.empty()) + extlist.Add(item->name); + } + + extlist.Flush(); + } + + class List : public Numeric::GenericBuilder<' ', false, Numeric::WriteRemoteNumericSink> + { + public: + List(CheckContext& context, const char* checktype) + : Numeric::GenericBuilder<' ', false, Numeric::WriteRemoteNumericSink>(Numeric::WriteRemoteNumericSink(context.GetUser()), RPL_CHECK, false, (IS_LOCAL(context.GetUser()) ? context.GetUser()->nick.length() : ServerInstance->Config->Limits.NickMax) + strlen(checktype) + 1) + { + GetNumeric().push(checktype).push(std::string()); + } + }; +}; /** Handle /CHECK */ class CommandCheck : public Command { + UserModeReference snomaskmode; + + std::string GetSnomasks(User* user) + { + std::string ret; + if (snomaskmode) + ret = snomaskmode->GetUserParameter(user); + + if (ret.empty()) + ret = "+"; + return ret; + } + + static std::string GetAllowedOperOnlyModes(LocalUser* user, ModeType modetype) + { + std::string ret; + const ModeParser::ModeHandlerMap& modes = ServerInstance->Modes.GetModes(modetype); + for (ModeParser::ModeHandlerMap::const_iterator i = modes.begin(); i != modes.end(); ++i) + { + const ModeHandler* const mh = i->second; + if ((mh->NeedsOper()) && (user->HasModePermission(mh))) + ret.push_back(mh->GetModeChar()); + } + return ret; + } + public: - CommandCheck(Module* parent) : Command(parent,"CHECK", 1) + CommandCheck(Module* parent) + : Command(parent,"CHECK", 1) + , snomaskmode(parent, "snomask") { flags_needed = 'o'; syntax = "<nickname>|<ip>|<hostmask>|<channel> <server>"; } @@ -44,176 +140,137 @@ class CommandCheck : public Command return ret; } - void dumpExt(User* user, const std::string& checkstr, Extensible* ext) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - std::stringstream dumpkeys; - for(Extensible::ExtensibleStore::const_iterator i = ext->GetExtList().begin(); i != ext->GetExtList().end(); i++) - { - ExtensionItem* item = i->first; - std::string value = item->serialize(FORMAT_USER, ext, i->second); - if (!value.empty()) - user->SendText(checkstr + " meta:" + item->name + " " + value); - else if (!item->name.empty()) - dumpkeys << " " << item->name; - } - if (!dumpkeys.str().empty()) - user->SendText(checkstr + " metadata", dumpkeys); - } - - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) - { - if (parameters.size() > 1 && parameters[1] != ServerInstance->Config->ServerName.c_str()) + if (parameters.size() > 1 && parameters[1] != ServerInstance->Config->ServerName) return CMD_SUCCESS; User *targuser; Channel *targchan; - std::string checkstr; std::string chliststr; - checkstr = ":" + ServerInstance->Config->ServerName + " 304 " + user->nick + " :CHECK"; - targuser = ServerInstance->FindNick(parameters[0]); targchan = ServerInstance->FindChan(parameters[0]); /* * Syntax of a /check reply: - * :server.name 304 target :CHECK START <target> - * :server.name 304 target :CHECK <field> <value> - * :server.name 304 target :CHECK END + * :server.name 802 target START <target> + * :server.name 802 target <field> :<value> + * :server.name 802 target END <target> */ - user->SendText(checkstr + " START " + parameters[0]); + // Constructor sends START, destructor sends END + CheckContext context(user, parameters[0]); if (targuser) { LocalUser* loctarg = IS_LOCAL(targuser); /* /check on a user */ - user->SendText(checkstr + " nuh " + targuser->GetFullHost()); - user->SendText(checkstr + " realnuh " + targuser->GetFullRealHost()); - user->SendText(checkstr + " realname " + targuser->fullname); - user->SendText(checkstr + " modes +" + targuser->FormatModes()); - user->SendText(checkstr + " snomasks +" + targuser->FormatNoticeMasks()); - user->SendText(checkstr + " server " + targuser->server); - user->SendText(checkstr + " uid " + targuser->uuid); - user->SendText(checkstr + " signon " + timestring(targuser->signon)); - user->SendText(checkstr + " nickts " + timestring(targuser->age)); + context.Write("nuh", targuser->GetFullHost()); + context.Write("realnuh", targuser->GetFullRealHost()); + context.Write("realname", targuser->GetRealName()); + context.Write("modes", targuser->GetModeLetters()); + context.Write("snomasks", GetSnomasks(targuser)); + context.Write("server", targuser->server->GetName()); + context.Write("uid", targuser->uuid); + context.Write("signon", timestring(targuser->signon)); + context.Write("nickts", timestring(targuser->age)); if (loctarg) - user->SendText(checkstr + " lastmsg " + timestring(targuser->idle_lastmsg)); + context.Write("lastmsg", timestring(loctarg->idle_lastmsg)); - if (IS_AWAY(targuser)) + if (targuser->IsAway()) { /* user is away */ - user->SendText(checkstr + " awaytime " + timestring(targuser->awaytime)); - user->SendText(checkstr + " awaymsg " + targuser->awaymsg); + context.Write("awaytime", timestring(targuser->awaytime)); + context.Write("awaymsg", targuser->awaymsg); } - if (IS_OPER(targuser)) + if (targuser->IsOper()) { OperInfo* oper = targuser->oper; /* user is an oper of type ____ */ - user->SendText(checkstr + " opertype " + oper->NameStr()); + context.Write("opertype", oper->name); if (loctarg) { - std::string umodes; - std::string cmodes; - for(char c='A'; c <= 'z'; c++) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_USER); - if (mh && mh->NeedsOper() && loctarg->HasModePermission(c, MODETYPE_USER)) - umodes.push_back(c); - mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL); - if (mh && mh->NeedsOper() && loctarg->HasModePermission(c, MODETYPE_CHANNEL)) - cmodes.push_back(c); - } - user->SendText(checkstr + " modeperms user=" + umodes + " channel=" + cmodes); - std::string opcmds; - for(std::set<std::string>::iterator i = oper->AllowedOperCommands.begin(); i != oper->AllowedOperCommands.end(); i++) - { - opcmds.push_back(' '); - opcmds.append(*i); - } - std::stringstream opcmddump(opcmds); - user->SendText(checkstr + " commandperms", opcmddump); - std::string privs; - for(std::set<std::string>::iterator i = oper->AllowedPrivs.begin(); i != oper->AllowedPrivs.end(); i++) - { - privs.push_back(' '); - privs.append(*i); - } - std::stringstream privdump(privs); - user->SendText(checkstr + " permissions", privdump); + std::string umodes = GetAllowedOperOnlyModes(loctarg, MODETYPE_USER); + std::string cmodes = GetAllowedOperOnlyModes(loctarg, MODETYPE_CHANNEL); + context.Write("modeperms", "user=" + umodes + " channel=" + cmodes); + + CheckContext::List opcmdlist(context, "commandperms"); + opcmdlist.Add(oper->AllowedOperCommands.ToString()); + opcmdlist.Flush(); + CheckContext::List privlist(context, "permissions"); + opcmdlist.Add(oper->AllowedPrivs.ToString()); + privlist.Flush(); } } if (loctarg) { - user->SendText(checkstr + " clientaddr " + irc::sockets::satouser(loctarg->client_sa)); - user->SendText(checkstr + " serveraddr " + irc::sockets::satouser(loctarg->server_sa)); + context.Write("clientaddr", loctarg->client_sa.str()); + context.Write("serveraddr", loctarg->server_sa.str()); std::string classname = loctarg->GetClass()->name; if (!classname.empty()) - user->SendText(checkstr + " connectclass " + classname); + context.Write("connectclass", classname); } else - user->SendText(checkstr + " onip " + targuser->GetIPString()); + context.Write("onip", targuser->GetIPString()); - for (UCListIter i = targuser->chans.begin(); i != targuser->chans.end(); i++) + CheckContext::List chanlist(context, "onchans"); + for (User::ChanList::iterator i = targuser->chans.begin(); i != targuser->chans.end(); i++) { - Channel* c = *i; - chliststr.append(c->GetPrefixChar(targuser)).append(c->name).append(" "); + Membership* memb = *i; + Channel* c = memb->chan; + char prefix = memb->GetPrefixChar(); + if (prefix) + chliststr.push_back(prefix); + chliststr.append(c->name); + chanlist.Add(chliststr); + chliststr.clear(); } - std::stringstream dump(chliststr); + chanlist.Flush(); - user->SendText(checkstr + " onchans", dump); - - dumpExt(user, checkstr, targuser); + context.DumpExt(targuser); } else if (targchan) { /* /check on a channel */ - user->SendText(checkstr + " timestamp " + timestring(targchan->age)); + context.Write("timestamp", timestring(targchan->age)); if (!targchan->topic.empty()) { /* there is a topic, assume topic related information exists */ - user->SendText(checkstr + " topic " + targchan->topic); - user->SendText(checkstr + " topic_setby " + targchan->setby); - user->SendText(checkstr + " topic_setat " + timestring(targchan->topicset)); + context.Write("topic", targchan->topic); + context.Write("topic_setby", targchan->setby); + context.Write("topic_setat", timestring(targchan->topicset)); } - user->SendText(checkstr + " modes " + targchan->ChanModes(true)); - user->SendText(checkstr + " membercount " + ConvToStr(targchan->GetUserCounter())); + context.Write("modes", targchan->ChanModes(true)); + context.Write("membercount", ConvToStr(targchan->GetUserCounter())); /* now the ugly bit, spool current members of a channel. :| */ - const UserMembList *ulist= targchan->GetUsers(); + const Channel::MemberMap& ulist = targchan->GetUsers(); /* note that unlike /names, we do NOT check +i vs in the channel */ - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { - char tmpbuf[MAXBUF]; /* - * Unlike Asuka, I define a clone as coming from the same host. --w00t - */ - snprintf(tmpbuf, MAXBUF, "%-3lu %s%s (%s@%s) %s ", ServerInstance->Users->GlobalCloneCount(i->first), targchan->GetAllPrefixChars(i->first), i->first->nick.c_str(), i->first->ident.c_str(), i->first->dhost.c_str(), i->first->fullname.c_str()); - user->SendText(checkstr + " member " + tmpbuf); + * Unlike Asuka, I define a clone as coming from the same host. --w00t + */ + const UserManager::CloneCounts& clonecount = ServerInstance->Users->GetCloneCounts(i->first); + context.Write("member", InspIRCd::Format("%-3u %s%s (%s@%s) %s ", clonecount.global, + i->second->GetAllPrefixChars().c_str(), i->first->nick.c_str(), + i->first->ident.c_str(), i->first->GetDisplayedHost().c_str(), i->first->GetRealName().c_str())); } - irc::modestacker modestack(true); - for(BanList::iterator b = targchan->bans.begin(); b != targchan->bans.end(); ++b) - { - modestack.Push('b', b->data); - } - std::vector<std::string> stackresult; - std::vector<TranslateType> dummy; - while (modestack.GetStackedLine(stackresult)) - { - creator->ProtoSendMode(user, TYPE_CHANNEL, targchan, stackresult, dummy); - stackresult.clear(); - } - FOREACH_MOD(I_OnSyncChannel,OnSyncChannel(targchan,creator,user)); - dumpExt(user, checkstr, targchan); + const ModeParser::ListModeList& listmodes = ServerInstance->Modes->GetListModes(); + for (ModeParser::ListModeList::const_iterator i = listmodes.begin(); i != listmodes.end(); ++i) + context.DumpListMode((*i)->GetList(targchan)); + + context.DumpExt(targchan); } else { @@ -221,73 +278,46 @@ class CommandCheck : public Command long x = 0; /* hostname or other */ - for (user_hash::const_iterator a = ServerInstance->Users->clientlist->begin(); a != ServerInstance->Users->clientlist->end(); a++) + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator a = users.begin(); a != users.end(); ++a) { - if (InspIRCd::Match(a->second->host, parameters[0], ascii_case_insensitive_map) || InspIRCd::Match(a->second->dhost, parameters[0], ascii_case_insensitive_map)) + if (InspIRCd::Match(a->second->GetRealHost(), parameters[0], ascii_case_insensitive_map) || InspIRCd::Match(a->second->GetDisplayedHost(), parameters[0], ascii_case_insensitive_map)) { /* host or vhost matches mask */ - user->SendText(checkstr + " match " + ConvToStr(++x) + " " + a->second->GetFullRealHost() + " " + a->second->GetIPString() + " " + a->second->fullname); + context.Write("match", ConvToStr(++x) + " " + a->second->GetFullRealHost() + " " + a->second->GetIPString() + " " + a->second->GetRealName()); } /* IP address */ else if (InspIRCd::MatchCIDR(a->second->GetIPString(), parameters[0])) { /* same IP. */ - user->SendText(checkstr + " match " + ConvToStr(++x) + " " + a->second->GetFullRealHost() + " " + a->second->GetIPString() + " " + a->second->fullname); + context.Write("match", ConvToStr(++x) + " " + a->second->GetFullRealHost() + " " + a->second->GetIPString() + " " + a->second->GetRealName()); } } - user->SendText(checkstr + " matches " + ConvToStr(x)); + context.Write("matches", ConvToStr(x)); } - user->SendText(checkstr + " END " + parameters[0]); - + // END is sent by the CheckContext destructor return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - if (parameters.size() > 1) + if ((parameters.size() > 1) && (parameters[1].find('.') != std::string::npos)) return ROUTE_OPT_UCAST(parameters[1]); return ROUTE_LOCALONLY; } }; - class ModuleCheck : public Module { - private: CommandCheck mycommand; public: ModuleCheck() : mycommand(this) { } - void init() - { - ServerInstance->Modules->AddService(mycommand); - } - - ~ModuleCheck() - { - } - - void ProtoSendMode(void* uv, TargetTypeFlags, void*, const std::vector<std::string>& result, const std::vector<TranslateType>&) - { - User* user = (User*)uv; - std::string checkstr(":"); - checkstr.append(ServerInstance->Config->ServerName); - checkstr.append(" 304 "); - checkstr.append(user->nick); - checkstr.append(" :CHECK modelist"); - for(unsigned int i=0; i < result.size(); i++) - { - checkstr.append(" "); - checkstr.append(result[i]); - } - user->SendText(checkstr); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("CHECK command, view user, channel, IP address or hostname information", VF_VENDOR|VF_OPTCOMMON); } diff --git a/src/modules/m_chghost.cpp b/src/modules/m_chghost.cpp index 6aaed7831..6e498a8be 100644 --- a/src/modules/m_chghost.cpp +++ b/src/modules/m_chghost.cpp @@ -21,38 +21,35 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the CHGHOST command */ - /** Handle /CHGHOST */ class CommandChghost : public Command { - private: - char* hostmap; public: - CommandChghost(Module* Creator, char* hmap) : Command(Creator,"CHGHOST", 2), hostmap(hmap) + std::bitset<UCHAR_MAX> hostmap; + + CommandChghost(Module* Creator) + : Command(Creator,"CHGHOST", 2) { allow_empty_last_param = false; flags_needed = 'o'; syntax = "<nick> <newhost>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - const char* x = parameters[1].c_str(); - - if (parameters[1].length() > 63) + if (parameters[1].length() > ServerInstance->Config->Limits.MaxHost) { - user->WriteServ("NOTICE %s :*** CHGHOST: Host too long", user->nick.c_str()); + user->WriteNotice("*** CHGHOST: Host too long"); return CMD_FAILURE; } - for (; *x; x++) + for (std::string::const_iterator x = parameters[1].begin(); x != parameters[1].end(); x++) { - if (!hostmap[(unsigned char)*x]) + if (!hostmap.test(static_cast<unsigned char>(*x))) { - user->WriteServ("NOTICE "+user->nick+" :*** CHGHOST: Invalid characters in hostname"); + user->WriteNotice("*** CHGHOST: Invalid characters in hostname"); return CMD_FAILURE; } } @@ -60,30 +57,27 @@ class CommandChghost : public Command User* dest = ServerInstance->FindNick(parameters[0]); // Allow services to change the host of unregistered users - if ((!dest) || ((dest->registered != REG_ALL) && (!ServerInstance->ULine(user->server)))) + if ((!dest) || ((dest->registered != REG_ALL) && (!user->server->IsULine()))) { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); return CMD_FAILURE; } if (IS_LOCAL(dest)) { - if ((dest->ChangeDisplayedHost(parameters[1].c_str())) && (!ServerInstance->ULine(user->server))) + if ((dest->ChangeDisplayedHost(parameters[1])) && (!user->server->IsULine())) { // fix by brain - ulines set hosts silently - ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used CHGHOST to make the displayed host of "+dest->nick+" become "+dest->dhost); + ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used CHGHOST to make the displayed host of "+dest->nick+" become "+dest->GetDisplayedHost()); } } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -91,38 +85,26 @@ class CommandChghost : public Command class ModuleChgHost : public Module { CommandChghost cmd; - char hostmap[256]; - public: - ModuleChgHost() : cmd(this, hostmap) - { - } - void init() + public: + ModuleChgHost() + : cmd(this) { - OnRehash(NULL); - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { std::string hmap = ServerInstance->Config->ConfValue("hostname")->getString("charmap", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_/0123456789"); - memset(hostmap, 0, sizeof(hostmap)); + cmd.hostmap.reset(); for (std::string::iterator n = hmap.begin(); n != hmap.end(); n++) - hostmap[(unsigned char)*n] = 1; - } - - ~ModuleChgHost() - { + cmd.hostmap.set(static_cast<unsigned char>(*n)); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the CHGHOST command", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleChgHost) diff --git a/src/modules/m_chgident.cpp b/src/modules/m_chgident.cpp index 2112e45a3..9a2d3b2ea 100644 --- a/src/modules/m_chgident.cpp +++ b/src/modules/m_chgident.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the CHGIDENT command */ - /** Handle /CHGIDENT */ class CommandChgident : public Command @@ -34,52 +32,48 @@ class CommandChgident : public Command allow_empty_last_param = false; flags_needed = 'o'; syntax = "<nick> <newident>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* dest = ServerInstance->FindNick(parameters[0]); if ((!dest) || (dest->registered != REG_ALL)) { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); return CMD_FAILURE; } if (parameters[1].length() > ServerInstance->Config->Limits.IdentMax) { - user->WriteServ("NOTICE %s :*** CHGIDENT: Ident is too long", user->nick.c_str()); + user->WriteNotice("*** CHGIDENT: Ident is too long"); return CMD_FAILURE; } - if (!ServerInstance->IsIdent(parameters[1].c_str())) + if (!ServerInstance->IsIdent(parameters[1])) { - user->WriteServ("NOTICE %s :*** CHGIDENT: Invalid characters in ident", user->nick.c_str()); + user->WriteNotice("*** CHGIDENT: Invalid characters in ident"); return CMD_FAILURE; } if (IS_LOCAL(dest)) { - dest->ChangeIdent(parameters[1].c_str()); + dest->ChangeIdent(parameters[1]); - if (!ServerInstance->ULine(user->server)) + if (!user->server->IsULine()) ServerInstance->SNO->WriteGlobalSno('a', "%s used CHGIDENT to change %s's ident to '%s'", user->nick.c_str(), dest->nick.c_str(), dest->ident.c_str()); } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; - class ModuleChgIdent : public Module { CommandChgident cmd; @@ -89,21 +83,10 @@ public: { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleChgIdent() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the CHGIDENT command", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleChgIdent) - diff --git a/src/modules/m_chgname.cpp b/src/modules/m_chgname.cpp index 73ae3d487..aedd75d94 100644 --- a/src/modules/m_chgname.cpp +++ b/src/modules/m_chgname.cpp @@ -20,8 +20,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the CHGNAME command */ - /** Handle /CHGNAME */ class CommandChgname : public Command @@ -31,51 +29,47 @@ class CommandChgname : public Command { allow_empty_last_param = false; flags_needed = 'o'; - syntax = "<nick> <newname>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + syntax = "<nick> <new real name>"; + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* dest = ServerInstance->FindNick(parameters[0]); if ((!dest) || (dest->registered != REG_ALL)) { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); return CMD_FAILURE; } if (parameters[1].empty()) { - user->WriteServ("NOTICE %s :*** CHGNAME: GECOS must be specified", user->nick.c_str()); + user->WriteNotice("*** CHGNAME: Real name must be specified"); return CMD_FAILURE; } - if (parameters[1].length() > ServerInstance->Config->Limits.MaxGecos) + if (parameters[1].length() > ServerInstance->Config->Limits.MaxReal) { - user->WriteServ("NOTICE %s :*** CHGNAME: GECOS too long", user->nick.c_str()); + user->WriteNotice("*** CHGNAME: Real name is too long"); return CMD_FAILURE; } if (IS_LOCAL(dest)) { - dest->ChangeName(parameters[1].c_str()); - ServerInstance->SNO->WriteGlobalSno('a', "%s used CHGNAME to change %s's GECOS to '%s'", user->nick.c_str(), dest->nick.c_str(), dest->fullname.c_str()); + dest->ChangeRealName(parameters[1]); + ServerInstance->SNO->WriteGlobalSno('a', "%s used CHGNAME to change %s's real name to '%s'", user->nick.c_str(), dest->nick.c_str(), dest->GetRealName().c_str()); } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; - class ModuleChgName : public Module { CommandChgname cmd; @@ -85,20 +79,10 @@ public: { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleChgName() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the CHGNAME command", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleChgName) diff --git a/src/modules/m_classban.cpp b/src/modules/m_classban.cpp new file mode 100644 index 000000000..066834079 --- /dev/null +++ b/src/modules/m_classban.cpp @@ -0,0 +1,47 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Johanna Abrahamsson <johanna-a@mjao.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +class ModuleClassBan : public Module +{ + public: + ModResult OnCheckBan(User* user, Channel* c, const std::string& mask) CXX11_OVERRIDE + { + LocalUser* localUser = IS_LOCAL(user); + if ((localUser) && (mask.length() > 2) && (mask[0] == 'n') && (mask[1] == ':')) + { + if (InspIRCd::Match(localUser->GetClass()->name, mask.substr(2))) + return MOD_RES_DENY; + } + return MOD_RES_PASSTHRU; + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + tokens["EXTBAN"].push_back('n'); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Class 'n' - Connection class ban", VF_VENDOR | VF_OPTCOMMON); + } +}; + +MODULE_INIT(ModuleClassBan) diff --git a/src/modules/m_clearchan.cpp b/src/modules/m_clearchan.cpp new file mode 100644 index 000000000..016d28737 --- /dev/null +++ b/src/modules/m_clearchan.cpp @@ -0,0 +1,218 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "xline.h" + +class CommandClearChan : public Command +{ + public: + Channel* activechan; + + CommandClearChan(Module* Creator) + : Command(Creator, "CLEARCHAN", 1, 3) + { + syntax = "<channel> [<KILL|KICK|G|Z>] [<reason>]"; + flags_needed = 'o'; + + // Stop the linking mod from forwarding ENCAP'd CLEARCHAN commands, see below why + force_manual_route = true; + } + + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + Channel* chan = activechan = ServerInstance->FindChan(parameters[0]); + if (!chan) + { + user->WriteNotice("The channel " + parameters[0] + " does not exist."); + return CMD_FAILURE; + } + + // See what method the oper wants to use, default to KILL + std::string method("KILL"); + if (parameters.size() > 1) + { + method = parameters[1]; + std::transform(method.begin(), method.end(), method.begin(), ::toupper); + } + + XLineFactory* xlf = NULL; + bool kick = (method == "KICK"); + if ((!kick) && (method != "KILL")) + { + if ((method != "Z") && (method != "G")) + { + user->WriteNotice("Invalid method for clearing " + chan->name); + return CMD_FAILURE; + } + + xlf = ServerInstance->XLines->GetFactory(method); + if (!xlf) + return CMD_FAILURE; + } + + const std::string reason = parameters.size() > 2 ? parameters.back() : "Clearing " + chan->name; + + if (!user->server->IsSilentULine()) + ServerInstance->SNO->WriteToSnoMask((IS_LOCAL(user) ? 'a' : 'A'), user->nick + " has cleared \002" + chan->name + "\002 (" + method + "): " + reason); + + user->WriteNotice("Clearing \002" + chan->name + "\002 (" + method + "): " + reason); + + { + // Route this command manually so it is sent before the QUITs we are about to generate. + // The idea is that by the time our QUITs reach the next hop, it has already removed all their + // clients from the channel, meaning victims on other servers won't see the victims on this + // server quitting. + CommandBase::Params eparams; + eparams.push_back(chan->name); + eparams.push_back(method); + eparams.push_back(":"); + eparams.back().append(reason); + ServerInstance->PI->BroadcastEncap(this->name, eparams, user, user); + } + + // Attach to the appropriate hook so we're able to hide the QUIT/KICK messages + Implementation hook = (kick ? I_OnUserKick : I_OnBuildNeighborList); + ServerInstance->Modules->Attach(hook, creator); + + std::string mask; + // Now remove all local non-opers from the channel + Channel::MemberMap& users = chan->userlist; + for (Channel::MemberMap::iterator i = users.begin(); i != users.end(); ) + { + User* curr = i->first; + const Channel::MemberMap::iterator currit = i; + ++i; + + if (!IS_LOCAL(curr) || curr->IsOper()) + continue; + + // If kicking users, remove them and skip the QuitUser() + if (kick) + { + chan->KickUser(ServerInstance->FakeClient, currit, reason); + continue; + } + + // If we are banning users then create the XLine and add it + if (xlf) + { + XLine* xline; + try + { + mask = ((method[0] == 'Z') ? curr->GetIPString() : "*@" + curr->GetRealHost()); + xline = xlf->Generate(ServerInstance->Time(), 60*60, user->nick, reason, mask); + } + catch (ModuleException&) + { + // Nothing, move on to the next user + continue; + } + + if (!ServerInstance->XLines->AddLine(xline, user)) + delete xline; + } + + ServerInstance->Users->QuitUser(curr, reason); + } + + ServerInstance->Modules->Detach(hook, creator); + if (xlf) + ServerInstance->XLines->ApplyLines(); + + return CMD_SUCCESS; + } +}; + +class ModuleClearChan : public Module +{ + CommandClearChan cmd; + + public: + ModuleClearChan() + : cmd(this) + { + } + + void init() CXX11_OVERRIDE + { + // Only attached while we are working; don't react to events otherwise + ServerInstance->Modules->DetachAll(this); + } + + void OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) CXX11_OVERRIDE + { + bool found = false; + for (IncludeChanList::iterator i = include.begin(); i != include.end(); ++i) + { + if ((*i)->chan == cmd.activechan) + { + // Don't show the QUIT to anyone in the channel by default + include.erase(i); + found = true; + break; + } + } + + const Channel::MemberMap& users = cmd.activechan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) + { + LocalUser* curr = IS_LOCAL(i->first); + if (!curr) + continue; + + if (curr->IsOper()) + { + // If another module has removed the channel we're working on from the list of channels + // to consider for sending the QUIT to then don't add exceptions for opers, because the + // module before us doesn't want them to see it or added the exceptions already. + // If there is a value for this oper in excepts already, this won't overwrite it. + if (found) + exception.insert(std::make_pair(curr, true)); + continue; + } + else if (!include.empty() && curr->chans.size() > 1) + { + // This is a victim and potentially has another common channel with the user quitting, + // add a negative exception overwriting the previous value, if any. + exception[curr] = false; + } + } + } + + void OnUserKick(User* source, Membership* memb, const std::string& reason, CUList& excepts) CXX11_OVERRIDE + { + // Hide the KICK from all non-opers + User* leaving = memb->user; + const Channel::MemberMap& users = memb->chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) + { + User* curr = i->first; + if ((IS_LOCAL(curr)) && (!curr->IsOper()) && (curr != leaving)) + excepts.insert(curr); + } + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Adds /CLEARCHAN that allows opers to masskick, masskill or mass-G/ZLine users on a channel", VF_VENDOR|VF_OPTCOMMON); + } +}; + +MODULE_INIT(ModuleClearChan) diff --git a/src/modules/m_cloaking.cpp b/src/modules/m_cloaking.cpp index f4cfdb54f..b9ff085c3 100644 --- a/src/modules/m_cloaking.cpp +++ b/src/modules/m_cloaking.cpp @@ -24,16 +24,10 @@ #include "inspircd.h" -#include "hash.h" - -/* $ModDesc: Provides masking of user hostnames */ +#include "modules/hash.h" enum CloakMode { - /** 1.2-compatible host-based cloak */ - MODE_COMPAT_HOST, - /** 1.2-compatible IP-only cloak */ - MODE_COMPAT_IPONLY, /** 2.0 cloak of "half" of the hostname plus the full IP hash */ MODE_HALF_CLOAK, /** 2.0 cloak of IP hash, split at 2 common CIDR range points */ @@ -48,19 +42,22 @@ static const char base32[] = "0123456789abcdefghijklmnopqrstuv"; class CloakUser : public ModeHandler { public: + bool active; LocalStringExt ext; - std::string debounce_uid; time_t debounce_ts; int debounce_count; CloakUser(Module* source) - : ModeHandler(source, "cloak", 'x', PARAM_NONE, MODETYPE_USER), - ext("cloaked_host", source), debounce_ts(0), debounce_count(0) + : ModeHandler(source, "cloak", 'x', PARAM_NONE, MODETYPE_USER) + , active(false) + , ext("cloaked_host", ExtensionItem::EXT_USER, source) + , debounce_ts(0) + , debounce_count(0) { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { LocalUser* user = IS_LOCAL(dest); @@ -70,7 +67,9 @@ class CloakUser : public ModeHandler */ if (!user) { - dest->SetMode('x',adding); + // Remote setters broadcast mode before host while local setters do the opposite, so this takes that into account + active = IS_LOCAL(source) ? adding : !adding; + dest->SetMode(this, adding); return MODEACTION_ALLOW; } @@ -87,7 +86,7 @@ class CloakUser : public ModeHandler debounce_ts = ServerInstance->Time(); } - if (adding == user->IsModeSet('x')) + if (adding == user->IsModeSet(this)) return MODEACTION_DENY; /* don't allow this user to spam modechanges */ @@ -97,7 +96,7 @@ class CloakUser : public ModeHandler if (adding) { // assume this is more correct - if (user->registered != REG_ALL && user->host != user->dhost) + if (user->registered != REG_ALL && user->GetRealHost() != user->GetDisplayedHost()) return MODEACTION_DENY; std::string* cloak = ext.get(user); @@ -110,8 +109,8 @@ class CloakUser : public ModeHandler } if (cloak) { - user->ChangeDisplayedHost(cloak->c_str()); - user->SetMode('x',true); + user->ChangeDisplayedHost(*cloak); + user->SetMode(this, true); return MODEACTION_ALLOW; } else @@ -122,12 +121,11 @@ class CloakUser : public ModeHandler /* User is removing the mode, so restore their real host * and make it match the displayed one. */ - user->SetMode('x',false); - user->ChangeDisplayedHost(user->host.c_str()); + user->SetMode(this, false); + user->ChangeDisplayedHost(user->GetRealHost().c_str()); return MODEACTION_ALLOW; } } - }; class CommandCloak : public Command @@ -139,7 +137,7 @@ class CommandCloak : public Command syntax = "<host>"; } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; }; class ModuleCloaking : public Module @@ -151,24 +149,15 @@ class ModuleCloaking : public Module std::string prefix; std::string suffix; std::string key; - unsigned int compatkey[4]; - const char* xtab[4]; + unsigned int domainparts; dynamic_reference<HashProvider> Hash; - ModuleCloaking() : cu(this), mode(MODE_OPAQUE), ck(this), Hash(this, "hash/md5") - { - } - - void init() + ModuleCloaking() + : cu(this) + , mode(MODE_OPAQUE) + , ck(this) + , Hash(this, "hash/md5") { - OnRehash(NULL); - - ServerInstance->Modules->AddService(cu); - ServerInstance->Modules->AddService(ck); - ServerInstance->Modules->AddService(cu.ext); - - Implementation eventlist[] = { I_OnRehash, I_OnCheckBan, I_OnUserConnect, I_OnChangeHost }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } /** This function takes a domain name string and returns just the last two domain parts, @@ -182,7 +171,7 @@ class ModuleCloaking : public Module */ std::string LastTwoDomainParts(const std::string &host) { - int dots = 0; + unsigned int dots = 0; std::string::size_type splitdot = host.length(); for (std::string::size_type x = host.length() - 1; x; --x) @@ -192,7 +181,7 @@ class ModuleCloaking : public Module splitdot = x; dots++; } - if (dots >= 3) + if (dots >= domainparts) break; } @@ -208,7 +197,7 @@ class ModuleCloaking : public Module * @param id A unique ID for this type of item (to make it unique if the item matches) * @param len The length of the output. Maximum for MD5 is 16 characters. */ - std::string SegmentCloak(const std::string& item, char id, int len) + std::string SegmentCloak(const std::string& item, char id, size_t len) { std::string input; input.reserve(key.length() + 3 + item.length()); @@ -217,8 +206,8 @@ class ModuleCloaking : public Module input.append(1, '\0'); // null does not terminate a C++ string input.append(item); - std::string rv = Hash->sum(input).substr(0,len); - for(int i=0; i < len; i++) + std::string rv = Hash->GenerateRaw(input).substr(0,len); + for(size_t i = 0; i < len; i++) { // this discards 3 bits per byte. We have an // overabundance of bits in the hash output, doesn't @@ -228,70 +217,13 @@ class ModuleCloaking : public Module return rv; } - std::string CompatCloak4(const char* ip) - { - irc::sepstream seps(ip, '.'); - std::string octet[4]; - int i[4]; - - for (int j = 0; j < 4; j++) - { - seps.GetToken(octet[j]); - i[j] = atoi(octet[j].c_str()); - } - - octet[3] = octet[0] + "." + octet[1] + "." + octet[2] + "." + octet[3]; - octet[2] = octet[0] + "." + octet[1] + "." + octet[2]; - octet[1] = octet[0] + "." + octet[1]; - - /* Reset the Hash module and send it our IV */ - - std::string rv; - - /* Send the Hash module a different hex table for each octet group's Hash sum */ - for (int k = 0; k < 4; k++) - { - rv.append(Hash->sumIV(compatkey, xtab[(compatkey[k]+i[k]) % 4], octet[k]).substr(0,6)); - if (k < 3) - rv.append("."); - } - /* Stick them all together */ - return rv; - } - - std::string CompatCloak6(const char* ip) - { - std::vector<std::string> hashies; - std::string item; - int rounds = 0; - - /* Reset the Hash module and send it our IV */ - - for (const char* input = ip; *input; input++) - { - item += *input; - if (item.length() > 7) - { - hashies.push_back(Hash->sumIV(compatkey, xtab[(compatkey[0]+rounds) % 4], item).substr(0,8)); - item.clear(); - } - rounds++; - } - if (!item.empty()) - { - hashies.push_back(Hash->sumIV(compatkey, xtab[(compatkey[0]+rounds) % 4], item).substr(0,8)); - } - /* Stick them all together */ - return irc::stringjoiner(":", hashies, 0, hashies.size() - 1).GetJoined(); - } - std::string SegmentIP(const irc::sockets::sockaddrs& ip, bool full) { std::string bindata; - int hop1, hop2, hop3; - int len1, len2; + size_t hop1, hop2, hop3; + size_t len1, len2; std::string rv; - if (ip.sa.sa_family == AF_INET6) + if (ip.family() == AF_INET6) { bindata = std::string((const char*)ip.in6.sin6_addr.s6_addr, 16); hop1 = 8; @@ -335,24 +267,22 @@ class ModuleCloaking : public Module } else { - char buf[50]; - if (ip.sa.sa_family == AF_INET6) + if (ip.family() == AF_INET6) { - snprintf(buf, 50, ".%02x%02x.%02x%02x%s", + rv.append(InspIRCd::Format(".%02x%02x.%02x%02x%s", ip.in6.sin6_addr.s6_addr[2], ip.in6.sin6_addr.s6_addr[3], - ip.in6.sin6_addr.s6_addr[0], ip.in6.sin6_addr.s6_addr[1], suffix.c_str()); + ip.in6.sin6_addr.s6_addr[0], ip.in6.sin6_addr.s6_addr[1], suffix.c_str())); } else { const unsigned char* ip4 = (const unsigned char*)&ip.in4.sin_addr; - snprintf(buf, 50, ".%d.%d%s", ip4[1], ip4[0], suffix.c_str()); + rv.append(InspIRCd::Format(".%d.%d%s", ip4[1], ip4[0], suffix.c_str())); } - rv.append(buf); } return rv; } - ModResult OnCheckBan(User* user, Channel* chan, const std::string& mask) + ModResult OnCheckBan(User* user, Channel* chan, const std::string& mask) CXX11_OVERRIDE { LocalUser* lu = IS_LOCAL(user); if (!lu) @@ -361,17 +291,16 @@ class ModuleCloaking : public Module OnUserConnect(lu); std::string* cloak = cu.ext.get(user); /* Check if they have a cloaked host, but are not using it */ - if (cloak && *cloak != user->dhost) + if (cloak && *cloak != user->GetDisplayedHost()) { - char cmask[MAXBUF]; - snprintf(cmask, MAXBUF, "%s!%s@%s", user->nick.c_str(), user->ident.c_str(), cloak->c_str()); - if (InspIRCd::Match(cmask,mask)) + const std::string cloakMask = user->nick + "!" + user->ident + "@" + *cloak; + if (InspIRCd::Match(cloakMask, mask)) return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { /* Needs to be after m_banexception etc. */ ServerInstance->Modules->SetPriority(this, I_OnCheckBan, PRIORITY_LAST); @@ -379,34 +308,39 @@ class ModuleCloaking : public Module // this unsets umode +x on every host change. If we are actually doing a +x // mode change, we will call SetMode back to true AFTER the host change is done. - void OnChangeHost(User* u, const std::string& host) + void OnChangeHost(User* u, const std::string& host) CXX11_OVERRIDE { - if(u->IsModeSet('x')) + if (u->IsModeSet(cu) && !cu.active) { - u->SetMode('x', false); - u->WriteServ("MODE %s -x", u->nick.c_str()); + u->SetMode(cu, false); + + if (!IS_LOCAL(u)) + return; + Modes::ChangeList modechangelist; + modechangelist.push_remove(&cu); + ClientProtocol::Events::Mode modeevent(ServerInstance->FakeClient, NULL, u, modechangelist); + static_cast<LocalUser*>(u)->Send(modeevent); } + cu.active = false; } - ~ModuleCloaking() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { std::string testcloak = "broken"; if (Hash) { switch (mode) { - case MODE_COMPAT_HOST: - testcloak = prefix + "-" + Hash->sumIV(compatkey, xtab[0], "*").substr(0,10); - break; - case MODE_COMPAT_IPONLY: - testcloak = Hash->sumIV(compatkey, xtab[0], "*").substr(0,10); - break; case MODE_HALF_CLOAK: - testcloak = prefix + SegmentCloak("*", 3, 8) + suffix; + // Use old cloaking verification to stay compatible with 2.0 + // But verify domainparts when use 3.0-only features + if (domainparts == 3) + testcloak = prefix + SegmentCloak("*", 3, 8) + suffix; + else + { + irc::sockets::sockaddrs sa; + testcloak = GenCloak(sa, "", testcloak + ConvToStr(domainparts)); + } break; case MODE_OPAQUE: testcloak = prefix + SegmentCloak("*", 4, 8) + suffix; @@ -415,82 +349,26 @@ class ModuleCloaking : public Module return Version("Provides masking of user hostnames", VF_COMMON|VF_VENDOR, testcloak); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("cloak"); prefix = tag->getString("prefix"); suffix = tag->getString("suffix", ".IP"); std::string modestr = tag->getString("mode"); - if (modestr == "compat-host") - mode = MODE_COMPAT_HOST; - else if (modestr == "compat-ip") - mode = MODE_COMPAT_IPONLY; - else if (modestr == "half") + if (stdalgo::string::equalsci(modestr, "half")) + { mode = MODE_HALF_CLOAK; - else if (modestr == "full") + domainparts = tag->getUInt("domainparts", 3, 1, 10); + } + else if (stdalgo::string::equalsci(modestr, "full")) mode = MODE_OPAQUE; else - throw ModuleException("Bad value for <cloak:mode>; must be one of compat-host, compat-ip, half, full"); - - if (mode == MODE_COMPAT_HOST || mode == MODE_COMPAT_IPONLY) - { - bool lowercase = tag->getBool("lowercase"); - - /* These are *not* using the need_positive parameter of ReadInteger - - * that will limit the valid values to only the positive values in a - * signed int. Instead, accept any value that fits into an int and - * cast it to an unsigned int. That will, a bit oddly, give us the full - * spectrum of an unsigned integer. - Special - * - * We must limit the keys or else we get different results on - * amd64/x86 boxes. - psychon */ - const unsigned int limit = 0x80000000; - compatkey[0] = (unsigned int) tag->getInt("key1"); - compatkey[1] = (unsigned int) tag->getInt("key2"); - compatkey[2] = (unsigned int) tag->getInt("key3"); - compatkey[3] = (unsigned int) tag->getInt("key4"); - - if (!lowercase) - { - xtab[0] = "F92E45D871BCA630"; - xtab[1] = "A1B9D80C72E653F4"; - xtab[2] = "1ABC078934DEF562"; - xtab[3] = "ABCDEF5678901234"; - } - else - { - xtab[0] = "f92e45d871bca630"; - xtab[1] = "a1b9d80c72e653f4"; - xtab[2] = "1abc078934def562"; - xtab[3] = "abcdef5678901234"; - } + throw ModuleException("Bad value for <cloak:mode>; must be half or full"); - if (prefix.empty()) - prefix = ServerInstance->Config->Network; - - if (!compatkey[0] || !compatkey[1] || !compatkey[2] || !compatkey[3] || - compatkey[0] >= limit || compatkey[1] >= limit || compatkey[2] >= limit || compatkey[3] >= limit) - { - std::string detail; - if (!compatkey[0] || compatkey[0] >= limit) - detail = "<cloak:key1> is not valid, it may be set to a too high/low value, or it may not exist."; - else if (!compatkey[1] || compatkey[1] >= limit) - detail = "<cloak:key2> is not valid, it may be set to a too high/low value, or it may not exist."; - else if (!compatkey[2] || compatkey[2] >= limit) - detail = "<cloak:key3> is not valid, it may be set to a too high/low value, or it may not exist."; - else if (!compatkey[3] || compatkey[3] >= limit) - detail = "<cloak:key4> is not valid, it may be set to a too high/low value, or it may not exist."; - - throw ModuleException("You have not defined cloak keys for m_cloaking!!! THIS IS INSECURE AND SHOULD BE CHECKED! - " + detail); - } - } - else - { - key = tag->getString("key"); - if (key.empty() || key == "secret") - throw ModuleException("You have not defined cloak keys for m_cloaking. Define <cloak:key> as a network-wide secret."); - } + key = tag->getString("key"); + if (key.empty() || key == "secret") + throw ModuleException("You have not defined cloak keys for m_cloaking. Define <cloak:key> as a network-wide secret."); } std::string GenCloak(const irc::sockets::sockaddrs& ip, const std::string& ipstr, const std::string& host) @@ -502,29 +380,6 @@ class ModuleCloaking : public Module switch (mode) { - case MODE_COMPAT_HOST: - { - if (!host_is_ip) - { - std::string tail = LastTwoDomainParts(host); - - // xtab is not used here due to a bug in 1.2 cloaking - chost = prefix + "-" + Hash->sumIV(compatkey, "0123456789abcdef", host).substr(0,8) + tail; - - /* Fix by brain - if the cloaked host is > the max length of a host (64 bytes - * according to the DNS RFC) then they get cloaked as an IP. - */ - if (chost.length() <= 64) - break; - } - // fall through to IP cloak - } - case MODE_COMPAT_IPONLY: - if (ip.sa.sa_family == AF_INET6) - chost = CompatCloak6(ipstr.c_str()); - else - chost = CompatCloak4(ipstr.c_str()); - break; case MODE_HALF_CLOAK: { if (!host_is_ip) @@ -540,17 +395,21 @@ class ModuleCloaking : public Module return chost; } - void OnUserConnect(LocalUser* dest) + void OnUserConnect(LocalUser* dest) CXX11_OVERRIDE { std::string* cloak = cu.ext.get(dest); if (cloak) return; - cu.ext.set(dest, GenCloak(dest->client_sa, dest->GetIPString(), dest->host)); + // TODO: decide how we are going to cloak AF_UNIX hostnames. + if (dest->client_sa.family() != AF_INET && dest->client_sa.family() != AF_INET6) + return; + + cu.ext.set(dest, GenCloak(dest->client_sa, dest->GetIPString(), dest->GetRealHost())); } }; -CmdResult CommandCloak::Handle(const std::vector<std::string> ¶meters, User *user) +CmdResult CommandCloak::Handle(User* user, const Params& parameters) { ModuleCloaking* mod = (ModuleCloaking*)(Module*)creator; irc::sockets::sockaddrs sa; @@ -561,7 +420,7 @@ CmdResult CommandCloak::Handle(const std::vector<std::string> ¶meters, User else cloak = mod->GenCloak(sa, "", parameters[0]); - user->WriteServ("NOTICE %s :*** Cloak for %s is %s", user->nick.c_str(), parameters[0].c_str(), cloak.c_str()); + user->WriteNotice("*** Cloak for " + parameters[0] + " is " + cloak); return CMD_SUCCESS; } diff --git a/src/modules/m_clones.cpp b/src/modules/m_clones.cpp index 92b1bda78..06956c564 100644 --- a/src/modules/m_clones.cpp +++ b/src/modules/m_clones.cpp @@ -20,74 +20,84 @@ #include "inspircd.h" +#include "modules/ircv3_batch.h" -/* $ModDesc: Provides the /CLONES command to retrieve information on clones. */ +enum +{ + // InspIRCd-specific. + RPL_CLONES = 399 +}; -/** Handle /CLONES - */ -class CommandClones : public Command +class CommandClones : public SplitCommand { + private: + IRCv3::Batch::API batchmanager; + IRCv3::Batch::Batch batch; + public: - CommandClones(Module* Creator) : Command(Creator,"CLONES", 1) + CommandClones(Module* Creator) + : SplitCommand(Creator,"CLONES", 1) + , batchmanager(Creator) + , batch("inspircd.org/clones") { - flags_needed = 'o'; syntax = "<limit>"; + flags_needed = 'o'; + syntax = "<limit>"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { + unsigned int limit = ConvToNum<unsigned int>(parameters[0]); - std::string clonesstr = "304 " + user->nick + " :CLONES"; - - unsigned long limit = atoi(parameters[0].c_str()); + // Syntax of a CLONES reply: + // :irc.example.com BATCH +<id> inspircd.org/clones :<min-count> + // @batch=<id> :irc.example.com 399 <client> <local-count> <remote-count> <cidr-mask> + /// :irc.example.com BATCH :-<id> - /* - * Syntax of a /clones reply: - * :server.name 304 target :CLONES START - * :server.name 304 target :CLONES <count> <ip> - * :server.name 304 target :CLONES END - */ - - user->WriteServ(clonesstr + " START"); + if (batchmanager) + { + batchmanager->Start(batch); + batch.GetBatchStartMessage().PushParam(ConvToStr(limit)); + } - /* hostname or other */ - // XXX I really don't like marking global_clones public for this. at all. -- w00t - for (clonemap::iterator x = ServerInstance->Users->global_clones.begin(); x != ServerInstance->Users->global_clones.end(); x++) + const UserManager::CloneMap& clonemap = ServerInstance->Users->GetCloneMap(); + for (UserManager::CloneMap::const_iterator i = clonemap.begin(); i != clonemap.end(); ++i) { - if (x->second >= limit) - user->WriteServ(clonesstr + " "+ ConvToStr(x->second) + " " + x->first.str()); + const UserManager::CloneCounts& counts = i->second; + if (counts.global < limit) + continue; + + Numeric::Numeric numeric(RPL_CLONES); + numeric.push(counts.local); + numeric.push(counts.global); + numeric.push(i->first.str()); + + ClientProtocol::Messages::Numeric numericmsg(numeric, user); + batch.AddToBatch(numericmsg); + user->Send(ServerInstance->GetRFCEvents().numeric, numericmsg); } - user->WriteServ(clonesstr + " END"); + if (batchmanager) + batchmanager->End(batch); return CMD_SUCCESS; } }; - class ModuleClones : public Module { - private: - CommandClones cmd; public: - ModuleClones() : cmd(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(cmd); - } + CommandClones cmd; - virtual ~ModuleClones() + public: + ModuleClones() + : cmd(this) { } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the /CLONES command to retrieve information on clones.", VF_VENDOR); } - - }; MODULE_INIT(ModuleClones) diff --git a/src/modules/m_close.cpp b/src/modules/m_close.cpp index 8b0ea3417..b0b45b4b6 100644 --- a/src/modules/m_close.cpp +++ b/src/modules/m_close.cpp @@ -20,8 +20,6 @@ #include "inspircd.h" -/* $ModDesc: Provides /CLOSE functionality */ - /** Handle /CLOSE */ class CommandClose : public Command @@ -30,15 +28,19 @@ class CommandClose : public Command /* Command 'close', needs operator */ CommandClose(Module* Creator) : Command(Creator,"CLOSE", 0) { - flags_needed = 'o'; } + flags_needed = 'o'; + } - CmdResult Handle (const std::vector<std::string> ¶meters, User *src) + CmdResult Handle(User* src, const Params& parameters) CXX11_OVERRIDE { std::map<std::string,int> closed; - for (LocalUserList::const_iterator u = ServerInstance->Users->local_users.begin(); u != ServerInstance->Users->local_users.end(); ++u) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator u = list.begin(); u != list.end(); ) { + // Quitting the user removes it from the list LocalUser* user = *u; + ++u; if (user->registered != REG_ALL) { ServerInstance->Users->QuitUser(user, "Closing all unknown connections per request"); @@ -50,13 +52,14 @@ class CommandClose : public Command int total = 0; for (std::map<std::string,int>::iterator ci = closed.begin(); ci != closed.end(); ci++) { - src->WriteServ("NOTICE %s :*** Closed %d unknown connection%s from [%s]",src->nick.c_str(),(*ci).second,((*ci).second>1)?"s":"",(*ci).first.c_str()); - total += (*ci).second; + src->WriteNotice("*** Closed " + ConvToStr(ci->second) + " unknown " + (ci->second == 1 ? "connection" : "connections") + + " from [" + ci->first + "]"); + total += ci->second; } if (total) - src->WriteServ("NOTICE %s :*** %i unknown connection%s closed",src->nick.c_str(),total,(total>1)?"s":""); + src->WriteNotice("*** " + ConvToStr(total) + " unknown " + (total == 1 ? "connection" : "connections") + " closed"); else - src->WriteServ("NOTICE %s :*** No unknown connections found",src->nick.c_str()); + src->WriteNotice("*** No unknown connections found"); return CMD_SUCCESS; } @@ -71,16 +74,7 @@ class ModuleClose : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleClose() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides /CLOSE functionality", VF_VENDOR); } diff --git a/src/modules/m_commonchans.cpp b/src/modules/m_commonchans.cpp index afa17add4..4cef93029 100644 --- a/src/modules/m_commonchans.cpp +++ b/src/modules/m_commonchans.cpp @@ -19,59 +19,33 @@ #include "inspircd.h" -/* $ModDesc: Adds user mode +c, which if set, users must be on a common channel with you to private message you */ - -/** Handles user mode +c - */ -class PrivacyMode : public SimpleUserModeHandler -{ - public: - PrivacyMode(Module* Creator) : SimpleUserModeHandler(Creator, "deaf_commonchan", 'c') { } -}; - class ModulePrivacyMode : public Module { - PrivacyMode pm; + SimpleUserModeHandler pm; public: - ModulePrivacyMode() : pm(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(pm); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModulePrivacyMode() + ModulePrivacyMode() + : pm(this, "deaf_commonchan", 'c') { } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Adds user mode +c, which if set, users must be on a common channel with you to private message you", VF_VENDOR); } - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (target_type == TYPE_USER) + if (target.type == MessageTarget::TYPE_USER) { - User* t = (User*)dest; - if (!IS_OPER(user) && (t->IsModeSet('c')) && (!ServerInstance->ULine(user->server)) && !user->SharesChannelWith(t)) + User* t = target.Get<User>(); + if (!user->IsOper() && (t->IsModeSet(pm)) && (!user->server->IsULine()) && !user->SharesChannelWith(t)) { - user->WriteNumeric(ERR_CANTSENDTOUSER, "%s %s :You are not permitted to send private messages to this user (+c set)", user->nick.c_str(), t->nick.c_str()); + user->WriteNumeric(ERR_CANTSENDTOUSER, t->nick, "You are not permitted to send private messages to this user (+c set)"); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreMessage(user, dest, target_type, text, status, exempt_list); - } }; - MODULE_INIT(ModulePrivacyMode) diff --git a/src/modules/m_conn_join.cpp b/src/modules/m_conn_join.cpp index 6b13ab1aa..7a06aedd3 100644 --- a/src/modules/m_conn_join.cpp +++ b/src/modules/m_conn_join.cpp @@ -22,45 +22,94 @@ #include "inspircd.h" -/* $ModDesc: Forces users to join the specified channel(s) on connect */ +static void JoinChannels(LocalUser* u, const std::string& chanlist) +{ + irc::commasepstream chans(chanlist); + std::string chan; + + while (chans.GetToken(chan)) + { + if (ServerInstance->IsChannel(chan)) + Channel::JoinUser(u, chan); + } +} + +class JoinTimer : public Timer +{ + private: + LocalUser* const user; + const std::string channels; + SimpleExtItem<JoinTimer>& ext; + + public: + JoinTimer(LocalUser* u, SimpleExtItem<JoinTimer>& ex, const std::string& chans, unsigned int delay) + : Timer(delay, false) + , user(u), channels(chans), ext(ex) + { + ServerInstance->Timers.AddTimer(this); + } + + bool Tick(time_t time) CXX11_OVERRIDE + { + if (user->chans.empty()) + JoinChannels(user, channels); + + ext.unset(user); + return false; + } +}; class ModuleConnJoin : public Module { - public: - void init() - { - Implementation eventlist[] = { I_OnPostConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } + SimpleExtItem<JoinTimer> ext; + std::string defchans; + unsigned int defdelay; - void Prioritize() - { - ServerInstance->Modules->SetPriority(this, I_OnPostConnect, PRIORITY_LAST); - } + public: + ModuleConnJoin() + : ext("join_timer", ExtensionItem::EXT_USER, this) + { + } - Version GetVersion() - { - return Version("Forces users to join the specified channel(s) on connect", VF_VENDOR); - } + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("autojoin"); + defchans = tag->getString("channel"); + defdelay = tag->getDuration("delay", 0, 0, 60); + } - void OnPostConnect(User* user) - { - if (!IS_LOCAL(user)) - return; + void Prioritize() CXX11_OVERRIDE + { + ServerInstance->Modules->SetPriority(this, I_OnPostConnect, PRIORITY_LAST); + } - std::string chanlist = ServerInstance->Config->ConfValue("autojoin")->getString("channel"); - chanlist = user->GetClass()->config->getString("autojoin", chanlist); + Version GetVersion() CXX11_OVERRIDE + { + return Version("Forces users to join the specified channel(s) on connect", VF_VENDOR); + } - irc::commasepstream chans(chanlist); - std::string chan; + void OnPostConnect(User* user) CXX11_OVERRIDE + { + LocalUser* localuser = IS_LOCAL(user); + if (!localuser) + return; - while (chans.GetToken(chan)) - { - if (ServerInstance->IsChannel(chan.c_str(), ServerInstance->Config->Limits.ChanMax)) - Channel::JoinUser(user, chan.c_str(), false, "", false, ServerInstance->Time()); - } + std::string chanlist = localuser->GetClass()->config->getString("autojoin"); + unsigned int chandelay = localuser->GetClass()->config->getDuration("autojoindelay", 0, 0, 60); + + if (chanlist.empty()) + { + if (defchans.empty()) + return; + chanlist = defchans; + chandelay = defdelay; } -}; + if (!chandelay) + JoinChannels(localuser, chanlist); + else + ext.set(localuser, new JoinTimer(localuser, ext, chanlist, chandelay)); + } +}; MODULE_INIT(ModuleConnJoin) diff --git a/src/modules/m_conn_umodes.cpp b/src/modules/m_conn_umodes.cpp index a21462ddf..1f2fe7455 100644 --- a/src/modules/m_conn_umodes.cpp +++ b/src/modules/m_conn_umodes.cpp @@ -22,69 +22,44 @@ #include "inspircd.h" -/* $ModDesc: Sets (and unsets) modes on users when they connect */ - class ModuleModesOnConnect : public Module { public: - void init() - { - ServerInstance->Modules->Attach(I_OnUserConnect, this); - } - - void Prioritize() + void Prioritize() CXX11_OVERRIDE { // for things like +x on connect, important, otherwise we have to resort to config order (bleh) -- w00t ServerInstance->Modules->SetPriority(this, I_OnUserConnect, PRIORITY_FIRST); } - virtual ~ModuleModesOnConnect() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Sets (and unsets) modes on users when they connect", VF_VENDOR); } - virtual void OnUserConnect(LocalUser* user) + void OnUserConnect(LocalUser* user) CXX11_OVERRIDE { // Backup and zero out the disabled usermodes, so that we can override them here. - char save[64]; - memcpy(save, ServerInstance->Config->DisabledUModes, - sizeof(ServerInstance->Config->DisabledUModes)); - memset(ServerInstance->Config->DisabledUModes, 0, 64); + const std::bitset<64> save = ServerInstance->Config->DisabledUModes; + ServerInstance->Config->DisabledUModes.reset(); ConfigTag* tag = user->MyClass->config; std::string ThisModes = tag->getString("modes"); if (!ThisModes.empty()) { std::string buf; - std::stringstream ss(ThisModes); - - std::vector<std::string> tokens; + irc::spacesepstream ss(ThisModes); - // split ThisUserModes into modes and mode params - while (ss >> buf) - tokens.push_back(buf); - - std::vector<std::string> modes; + CommandBase::Params modes; modes.push_back(user->nick); - modes.push_back(tokens[0]); - if (tokens.size() > 1) - { - // process mode params - for (unsigned int k = 1; k < tokens.size(); k++) - { - modes.push_back(tokens[k]); - } - } + // split ThisUserModes into modes and mode params + while (ss.GetToken(buf)) + modes.push_back(buf); - ServerInstance->Parser->CallHandler("MODE", modes, user); + ServerInstance->Parser.CallHandler("MODE", modes, user); } - memcpy(ServerInstance->Config->DisabledUModes, save, 64); + ServerInstance->Config->DisabledUModes = save; } }; diff --git a/src/modules/m_conn_waitpong.cpp b/src/modules/m_conn_waitpong.cpp index 1d48220a6..f2e9590c8 100644 --- a/src/modules/m_conn_waitpong.cpp +++ b/src/modules/m_conn_waitpong.cpp @@ -24,8 +24,6 @@ #include "inspircd.h" -/* $ModDesc: Forces connecting clients to send a PONG message back to the server before they can complete their connection */ - class ModuleWaitPong : public Module { bool sendsnotice; @@ -34,39 +32,33 @@ class ModuleWaitPong : public Module public: ModuleWaitPong() - : ext("waitpong_pingstr", this) - { - } - - void init() + : ext("waitpong_pingstr", ExtensionItem::EXT_USER, this) { - ServerInstance->Modules->AddService(ext); - OnRehash(NULL); - Implementation eventlist[] = { I_OnUserRegister, I_OnCheckReady, I_OnPreCommand, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("waitpong"); sendsnotice = tag->getBool("sendsnotice", true); killonbadreply = tag->getBool("killonbadreply", true); } - ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { std::string pingrpl = ServerInstance->GenRandomStr(10); - - user->Write("PING :%s", pingrpl.c_str()); + { + ClientProtocol::Messages::Ping pingmsg(pingrpl); + user->Send(ServerInstance->GetRFCEvents().ping, pingmsg); + } if(sendsnotice) - user->WriteServ("NOTICE %s :*** If you are having problems connecting due to ping timeouts, please type /quote PONG %s or /raw PONG %s now.", user->nick.c_str(), pingrpl.c_str(), pingrpl.c_str()); + user->WriteNotice("*** If you are having problems connecting due to ping timeouts, please type /quote PONG " + pingrpl + " or /raw PONG " + pingrpl + " now."); ext.set(user, pingrpl); return MOD_RES_PASSTHRU; } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser* user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { if (command == "PONG") { @@ -90,20 +82,15 @@ class ModuleWaitPong : public Module return MOD_RES_PASSTHRU; } - ModResult OnCheckReady(LocalUser* user) + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { return ext.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU; } - ~ModuleWaitPong() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Require pong prior to registration", VF_VENDOR); } - }; MODULE_INIT(ModuleWaitPong) diff --git a/src/modules/m_connectban.cpp b/src/modules/m_connectban.cpp index 26120add9..d57ffca02 100644 --- a/src/modules/m_connectban.cpp +++ b/src/modules/m_connectban.cpp @@ -20,63 +20,41 @@ #include "inspircd.h" #include "xline.h" -/* $ModDesc: Throttles the connections of IP ranges who try to connect flood. */ - class ModuleConnectBan : public Module { - private: - clonemap connects; + typedef std::map<irc::sockets::cidr_mask, unsigned int> ConnectMap; + ConnectMap connects; unsigned int threshold; unsigned int banduration; unsigned int ipv4_cidr; unsigned int ipv6_cidr; - public: - void init() - { - Implementation eventlist[] = { I_OnSetUserIP, I_OnGarbageCollect, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } + std::string banmessage; - virtual ~ModuleConnectBan() - { - } - - virtual Version GetVersion() + public: + Version GetVersion() CXX11_OVERRIDE { return Version("Throttles the connections of IP ranges who try to connect flood.", VF_VENDOR); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("connectban"); - ipv4_cidr = tag->getInt("ipv4cidr", 32); - if (ipv4_cidr == 0) - ipv4_cidr = 32; - - ipv6_cidr = tag->getInt("ipv6cidr", 128); - if (ipv6_cidr == 0) - ipv6_cidr = 128; - - threshold = tag->getInt("threshold", 10); - if (threshold == 0) - threshold = 10; - - banduration = ServerInstance->Duration(tag->getString("duration", "10m")); - if (banduration == 0) - banduration = 10*60; + ipv4_cidr = tag->getUInt("ipv4cidr", 32, 1, 32); + ipv6_cidr = tag->getUInt("ipv6cidr", 128, 1, 128); + threshold = tag->getUInt("threshold", 10, 1); + banduration = tag->getDuration("duration", 10*60, 1); + banmessage = tag->getString("banmessage", "Your IP range has been attempting to connect too many times in too short a duration. Wait a while, and you will be able to connect."); } - virtual void OnSetUserIP(LocalUser* u) + void OnSetUserIP(LocalUser* u) CXX11_OVERRIDE { if (u->exempt) return; - int range = 32; - clonemap::iterator i; + unsigned char range = 32; - switch (u->client_sa.sa.sa_family) + switch (u->client_sa.family()) { case AF_INET6: range = ipv6_cidr; @@ -87,7 +65,7 @@ class ModuleConnectBan : public Module } irc::sockets::cidr_mask mask(u->client_sa, range); - i = connects.find(mask); + ConnectMap::iterator i = connects.find(mask); if (i != connects.end()) { @@ -96,7 +74,7 @@ class ModuleConnectBan : public Module if (i->second >= threshold) { // Create zline for set duration. - ZLine* zl = new ZLine(ServerInstance->Time(), banduration, ServerInstance->Config->ServerName, "Your IP range has been attempting to connect too many times in too short a duration. Wait a while, and you will be able to connect.", mask.str()); + ZLine* zl = new ZLine(ServerInstance->Time(), banduration, ServerInstance->Config->ServerName, banmessage, mask.str()); if (!ServerInstance->XLines->AddLine(zl, NULL)) { delete zl; @@ -104,7 +82,7 @@ class ModuleConnectBan : public Module } ServerInstance->XLines->ApplyLines(); std::string maskstr = mask.str(); - std::string timestr = ServerInstance->TimeString(zl->expiry); + std::string timestr = InspIRCd::TimeString(zl->expiry); ServerInstance->SNO->WriteGlobalSno('x',"Module m_connectban added Z:line on *@%s to expire on %s: Connect flooding", maskstr.c_str(), timestr.c_str()); ServerInstance->SNO->WriteGlobalSno('a', "Connect flooding from IP range %s (%d)", maskstr.c_str(), threshold); @@ -117,9 +95,9 @@ class ModuleConnectBan : public Module } } - virtual void OnGarbageCollect() + void OnGarbageCollect() CXX11_OVERRIDE { - ServerInstance->Logs->Log("m_connectban",DEBUG, "Clearing map."); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Clearing map."); connects.clear(); } }; diff --git a/src/modules/m_connflood.cpp b/src/modules/m_connflood.cpp index f77691e32..5070dd3a7 100644 --- a/src/modules/m_connflood.cpp +++ b/src/modules/m_connflood.cpp @@ -21,12 +21,11 @@ #include "inspircd.h" -/* $ModDesc: Connection throttle */ - class ModuleConnFlood : public Module { -private: - int seconds, timeout, boot_wait; + unsigned int seconds; + unsigned int timeout; + unsigned int boot_wait; unsigned int conns; unsigned int maxconns; bool throttled; @@ -39,35 +38,28 @@ public: { } - void init() - { - InitConf(); - Implementation eventlist[] = { I_OnRehash, I_OnUserRegister }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Connection throttle", VF_VENDOR); } - void InitConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { /* read configuration variables */ ConfigTag* tag = ServerInstance->Config->ConfValue("connflood"); /* throttle configuration */ - seconds = tag->getInt("seconds"); - maxconns = tag->getInt("maxconns"); - timeout = tag->getInt("timeout"); + seconds = tag->getDuration("period", tag->getDuration("seconds", 30)); + maxconns = tag->getUInt("maxconns", 3); + timeout = tag->getDuration("timeout", 30); quitmsg = tag->getString("quitmsg"); /* seconds to wait when the server just booted */ - boot_wait = tag->getInt("bootwait"); + boot_wait = tag->getDuration("bootwait", 10); first = ServerInstance->Time(); } - virtual ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { if (user->exempt) return MOD_RES_PASSTHRU; @@ -114,12 +106,6 @@ public: } return MOD_RES_PASSTHRU; } - - virtual void OnRehash(User* user) - { - InitConf(); - } - }; MODULE_INIT(ModuleConnFlood) diff --git a/src/modules/m_customprefix.cpp b/src/modules/m_customprefix.cpp index dfc60e082..befc9750d 100644 --- a/src/modules/m_customprefix.cpp +++ b/src/modules/m_customprefix.cpp @@ -19,77 +19,23 @@ #include "inspircd.h" -/* $ModDesc: Allows custom prefix modes to be created. */ - -class CustomPrefixMode : public ModeHandler +class CustomPrefixMode : public PrefixMode { public: reference<ConfigTag> tag; - int rank; - bool depriv; - CustomPrefixMode(Module* parent, ConfigTag* Tag) - : ModeHandler(parent, Tag->getString("name"), 0, PARAM_ALWAYS, MODETYPE_CHANNEL), tag(Tag) - { - list = true; - m_paramtype = TR_NICK; - std::string v = tag->getString("prefix"); - prefix = v.c_str()[0]; - v = tag->getString("letter"); - mode = v.c_str()[0]; - rank = tag->getInt("rank"); - levelrequired = tag->getInt("ranktoset", rank); - depriv = tag->getBool("depriv", true); - } - - unsigned int GetPrefixRank() - { - return rank; - } - - ModResult AccessCheck(User* src, Channel*, std::string& value, bool adding) - { - if (!adding && src->nick == value && depriv) - return MOD_RES_ALLOW; - return MOD_RES_PASSTHRU; - } - - void RemoveMode(Channel* channel, irc::modestacker* stack) - { - const UserMembList* cl = channel->GetUsers(); - std::vector<std::string> mode_junk; - mode_junk.push_back(channel->name); - irc::modestacker modestack(false); - std::deque<std::string> stackresult; - - for (UserMembCIter i = cl->begin(); i != cl->end(); i++) - { - if (i->second->hasMode(mode)) - { - if (stack) - stack->Push(this->GetModeChar(), i->first->nick); - else - modestack.Push(this->GetModeChar(), i->first->nick); - } - } - - if (stack) - return; - while (modestack.GetStackedLine(stackresult)) - { - mode_junk.insert(mode_junk.end(), stackresult.begin(), stackresult.end()); - ServerInstance->SendMode(mode_junk, ServerInstance->FakeClient); - mode_junk.erase(mode_junk.begin() + 1, mode_junk.end()); - } - } - - void RemoveMode(User* user, irc::modestacker* stack) + CustomPrefixMode(Module* parent, const std::string& Name, char Letter, char Prefix, ConfigTag* Tag) + : PrefixMode(parent, Name, Letter, 0, Prefix) + , tag(Tag) { - } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - return MODEACTION_ALLOW; + unsigned long rank = tag->getUInt("rank", 0, 0, UINT_MAX); + unsigned long setrank = tag->getUInt("ranktoset", prefixrank, rank, UINT_MAX); + unsigned long unsetrank = tag->getUInt("ranktounset", setrank, setrank, UINT_MAX); + bool depriv = tag->getBool("depriv", true); + this->Update(rank, setrank, unsetrank, depriv); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Created the %s prefix: letter=%c prefix=%c rank=%u ranktoset=%u ranktounset=%i depriv=%d", + name.c_str(), GetModeChar(), GetPrefix(), GetPrefixRank(), GetLevelRequired(true), GetLevelRequired(false), CanSelfRemove()); } }; @@ -97,41 +43,65 @@ class ModuleCustomPrefix : public Module { std::vector<CustomPrefixMode*> modes; public: - ModuleCustomPrefix() - { - } - - void init() + void init() CXX11_OVERRIDE { ConfigTagList tags = ServerInstance->Config->ConfTags("customprefix"); - while (tags.first != tags.second) + for (ConfigIter iter = tags.first; iter != tags.second; ++iter) { - ConfigTag* tag = tags.first->second; - tags.first++; - CustomPrefixMode* mh = new CustomPrefixMode(this, tag); - modes.push_back(mh); - if (mh->rank <= 0) - throw ModuleException("Rank must be specified for prefix at " + tag->getTagLocation()); - if (!isalpha(mh->GetModeChar())) - throw ModuleException("Mode must be a letter for prefix at " + tag->getTagLocation()); + ConfigTag* tag = iter->second; + + const std::string name = tag->getString("name"); + if (name.empty()) + throw ModuleException("<customprefix:name> must be specified at " + tag->getTagLocation()); + + if (tag->getBool("change")) + { + ModeHandler* mh = ServerInstance->Modes->FindMode(name, MODETYPE_CHANNEL); + if (!mh) + throw ModuleException("<customprefix:change> specified for a non-existent mode at " + tag->getTagLocation()); + + PrefixMode* pm = mh->IsPrefixMode(); + if (!pm) + throw ModuleException("<customprefix:change> specified for a non-prefix mode at " + tag->getTagLocation()); + + unsigned long rank = tag->getUInt("rank", pm->GetPrefixRank(), 0, UINT_MAX); + unsigned long setrank = tag->getUInt("ranktoset", pm->GetLevelRequired(true), rank, UINT_MAX); + unsigned long unsetrank = tag->getUInt("ranktounset", pm->GetLevelRequired(false), setrank, UINT_MAX); + bool depriv = tag->getBool("depriv", pm->CanSelfRemove()); + pm->Update(rank, setrank, unsetrank, depriv); + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changed the %s prefix: depriv=%u rank=%u ranktoset=%u ranktounset=%u", + pm->name.c_str(), pm->CanSelfRemove(), pm->GetPrefixRank(), pm->GetLevelRequired(true), pm->GetLevelRequired(false)); + continue; + } + + const std::string letter = tag->getString("letter"); + if (letter.length() != 1) + throw ModuleException("<customprefix:letter> must be set to a mode character at " + tag->getTagLocation()); + + const std::string prefix = tag->getString("prefix"); + if (prefix.length() != 1) + throw ModuleException("<customprefix:prefix> must be set to a mode prefix at " + tag->getTagLocation()); + try { + CustomPrefixMode* mh = new CustomPrefixMode(this, name, letter[0], prefix[0], tag); + modes.push_back(mh); ServerInstance->Modules->AddService(*mh); } catch (ModuleException& e) { - throw ModuleException(e.err + " (while creating mode from " + tag->getTagLocation() + ")"); + throw ModuleException(e.GetReason() + " (while creating mode from " + tag->getTagLocation() + ")"); } } } ~ModuleCustomPrefix() { - for (std::vector<CustomPrefixMode*>::iterator i = modes.begin(); i != modes.end(); i++) - delete *i; + stdalgo::delete_all(modes); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides custom prefix channel modes", VF_VENDOR); } diff --git a/src/modules/m_customtitle.cpp b/src/modules/m_customtitle.cpp index c65645bc9..c16b1eda2 100644 --- a/src/modules/m_customtitle.cpp +++ b/src/modules/m_customtitle.cpp @@ -20,8 +20,13 @@ #include "inspircd.h" +#include "modules/whois.h" -/* $ModDesc: Provides the TITLE command which allows setting of CUSTOM WHOIS TITLE line */ +enum +{ + // From UnrealIRCd. + RPL_WHOISSPECIAL = 320 +}; /** Handle /TITLE */ @@ -30,32 +35,15 @@ class CommandTitle : public Command public: StringExtItem ctitle; CommandTitle(Module* Creator) : Command(Creator,"TITLE", 2), - ctitle("ctitle", Creator) + ctitle("ctitle", ExtensionItem::EXT_USER, Creator) { syntax = "<user> <password>"; } - bool OneOfMatches(const char* host, const char* ip, const char* hostlist) - { - std::stringstream hl(hostlist); - std::string xhost; - while (hl >> xhost) - { - if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) - { - return true; - } - } - return false; - } - - CmdResult Handle(const std::vector<std::string> ¶meters, User* user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - char TheHost[MAXBUF]; - char TheIP[MAXBUF]; - - snprintf(TheHost,MAXBUF,"%s@%s",user->ident.c_str(), user->host.c_str()); - snprintf(TheIP, MAXBUF,"%s@%s",user->ident.c_str(), user->GetIPString()); + const std::string userHost = user->ident + "@" + user->GetRealHost(); + const std::string userIP = user->ident + "@" + user->GetIPString(); ConfigTagList tags = ServerInstance->Config->ConfTags("title"); for (ConfigIter i = tags.first; i != tags.second; ++i) @@ -67,65 +55,57 @@ class CommandTitle : public Command std::string title = i->second->getString("title"); std::string vhost = i->second->getString("vhost"); - if (Name == parameters[0] && !ServerInstance->PassCompare(user, pass, parameters[1], hash) && OneOfMatches(TheHost,TheIP,host.c_str()) && !title.empty()) + if (Name == parameters[0] && ServerInstance->PassCompare(user, pass, parameters[1], hash) && + InspIRCd::MatchMask(host, userHost, userIP) && !title.empty()) { ctitle.set(user, title); ServerInstance->PI->SendMetaData(user, "ctitle", title); if (!vhost.empty()) - user->ChangeDisplayedHost(vhost.c_str()); + user->ChangeDisplayedHost(vhost); - user->WriteServ("NOTICE %s :Custom title set to '%s'",user->nick.c_str(), title.c_str()); + user->WriteNotice("Custom title set to '" + title + "'"); return CMD_SUCCESS; } } - user->WriteServ("NOTICE %s :Invalid title credentials",user->nick.c_str()); + user->WriteNotice("Invalid title credentials"); return CMD_SUCCESS; } }; -class ModuleCustomTitle : public Module +class ModuleCustomTitle : public Module, public Whois::LineEventListener { CommandTitle cmd; public: - ModuleCustomTitle() : cmd(this) - { - } - - void init() + ModuleCustomTitle() + : Whois::LineEventListener(this) + , cmd(this) { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(cmd.ctitle); - ServerInstance->Modules->Attach(I_OnWhoisLine, this); } // :kenny.chatspike.net 320 Brain Azhrarn :is getting paid to play games. - ModResult OnWhoisLine(User* user, User* dest, int &numeric, std::string &text) + ModResult OnWhoisLine(Whois::Context& whois, Numeric::Numeric& numeric) CXX11_OVERRIDE { /* We use this and not OnWhois because this triggers for remote, too */ - if (numeric == 312) + if (numeric.GetNumeric() == 312) { /* Insert our numeric before 312 */ - const std::string* ctitle = cmd.ctitle.get(dest); + const std::string* ctitle = cmd.ctitle.get(whois.GetTarget()); if (ctitle) { - ServerInstance->SendWhoisLine(user, dest, 320, "%s %s :%s",user->nick.c_str(), dest->nick.c_str(), ctitle->c_str()); + whois.SendLine(RPL_WHOISSPECIAL, ctitle); } } /* Don't block anything */ return MOD_RES_PASSTHRU; } - ~ModuleCustomTitle() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Custom Title for users", VF_OPTCOMMON | VF_VENDOR); } diff --git a/src/modules/m_cycle.cpp b/src/modules/m_cycle.cpp index 383e7b5a2..3ead72a45 100644 --- a/src/modules/m_cycle.cpp +++ b/src/modules/m_cycle.cpp @@ -20,23 +20,21 @@ #include "inspircd.h" -/* $ModDesc: Provides command CYCLE, acts as a server-side HOP command to part and rejoin a channel. */ - /** Handle /CYCLE */ -class CommandCycle : public Command +class CommandCycle : public SplitCommand { public: - CommandCycle(Module* Creator) : Command(Creator,"CYCLE", 1) + CommandCycle(Module* Creator) + : SplitCommand(Creator, "CYCLE", 1) { Penalty = 3; syntax = "<channel> :[reason]"; - TRANSLATE3(TR_TEXT, TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { Channel* channel = ServerInstance->FindChan(parameters[0]); - std::string reason = ConvToStr("Cycling"); + std::string reason = "Cycling"; if (parameters.size() > 1) { @@ -46,34 +44,27 @@ class CommandCycle : public Command if (!channel) { - user->WriteNumeric(403, "%s %s :No such channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); return CMD_FAILURE; } if (channel->HasUser(user)) { - /* - * technically, this is only ever sent locally, but pays to be safe ;p - */ - if (IS_LOCAL(user)) + if (channel->GetPrefixValue(user) < VOICE_VALUE && channel->IsBanned(user)) { - if (channel->GetPrefixValue(user) < VOICE_VALUE && channel->IsBanned(user)) - { - /* banned, boned. drop the message. */ - user->WriteServ("NOTICE "+user->nick+" :*** You may not cycle, as you are banned on channel " + channel->name); - return CMD_FAILURE; - } - - channel->PartUser(user, reason); - - Channel::JoinUser(user, parameters[0].c_str(), true, "", false, ServerInstance->Time()); + // User is banned, send an error and don't cycle them + user->WriteNotice("*** You may not cycle, as you are banned on channel " + channel->name); + return CMD_FAILURE; } + channel->PartUser(user, reason); + Channel::JoinUser(user, parameters[0], true); + return CMD_SUCCESS; } else { - user->WriteNumeric(442, "%s %s :You're not on that channel", user->nick.c_str(), channel->name.c_str()); + user->WriteNumeric(ERR_NOTONCHANNEL, channel->name, "You're not on that channel"); } return CMD_FAILURE; @@ -84,26 +75,17 @@ class CommandCycle : public Command class ModuleCycle : public Module { CommandCycle cmd; + public: ModuleCycle() : cmd(this) { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleCycle() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides command CYCLE, acts as a server-side HOP command to part and rejoin a channel.", VF_VENDOR); } - }; MODULE_INIT(ModuleCycle) diff --git a/src/modules/m_dccallow.cpp b/src/modules/m_dccallow.cpp index 05fff8937..f2d6bf105 100644 --- a/src/modules/m_dccallow.cpp +++ b/src/modules/m_dccallow.cpp @@ -25,7 +25,46 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the /DCCALLOW command */ +enum +{ + // From ircd-ratbox. + RPL_HELPSTART = 704, + RPL_HELPTXT = 705, + RPL_ENDOFHELP = 706, + + // InspIRCd-specific? + RPL_DCCALLOWSTART = 990, + RPL_DCCALLOWLIST = 991, + RPL_DCCALLOWEND = 992, + RPL_DCCALLOWTIMED = 993, + RPL_DCCALLOWPERMANENT = 994, + RPL_DCCALLOWREMOVED = 995, + ERR_DCCALLOWINVALID = 996, + RPL_DCCALLOWEXPIRED = 997, + ERR_UNKNOWNDCCALLOWCMD = 998 +}; + +static const char* const helptext[] = +{ + "You may allow DCCs from specific users by specifying a", + "DCC allow for the user you want to receive DCCs from.", + "For example, to allow the user Brain to send you inspircd.exe", + "you would type:", + "/DCCALLOW +Brain", + "Brain would then be able to send you files. They would have to", + "resend the file again if the server gave them an error message", + "before you added them to your DCCALLOW list.", + "DCCALLOW entries will be temporary by default, if you want to add", + "them to your DCCALLOW list until you leave IRC, type:", + "/DCCALLOW +Brain 0", + "To remove the user from your DCCALLOW list, type:", + "/DCCALLOW -Brain", + "To see the users in your DCCALLOW list, type:", + "/DCCALLOW LIST", + "NOTE: If the user leaves IRC or changes their nickname", + " they will be removed from your DCCALLOW list.", + " your DCCALLOW list will be deleted when you leave IRC." +}; class BannedFileList { @@ -40,11 +79,17 @@ class DCCAllow std::string nickname; std::string hostmask; time_t set_on; - long length; + unsigned long length; DCCAllow() { } - DCCAllow(const std::string &nick, const std::string &hm, const time_t so, const long ln) : nickname(nick), hostmask(hm), set_on(so), length(ln) { } + DCCAllow(const std::string& nick, const std::string& hm, time_t so, unsigned long ln) + : nickname(nick) + , hostmask(hm) + , set_on(so) + , length(ln) + { + } }; typedef std::vector<User *> userlist; @@ -53,19 +98,23 @@ typedef std::vector<DCCAllow> dccallowlist; dccallowlist* dl; typedef std::vector<BannedFileList> bannedfilelist; bannedfilelist bfl; -SimpleExtItem<dccallowlist>* ext; +typedef SimpleExtItem<dccallowlist> DCCAllowExt; class CommandDccallow : public Command { + DCCAllowExt& ext; + public: unsigned int maxentries; - CommandDccallow(Module* parent) : Command(parent, "DCCALLOW", 0) + CommandDccallow(Module* parent, DCCAllowExt& Ext) + : Command(parent, "DCCALLOW", 0) + , ext(Ext) { syntax = "[(+|-)<nick> [<time>]]|[LIST|HELP]"; /* XXX we need to fix this so it can work with translation stuff (i.e. move +- into a seperate param */ } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { /* syntax: DCCALLOW [+|-]<nick> (<time>) */ if (!parameters.size()) @@ -95,21 +144,21 @@ class CommandDccallow : public Command } else { - user->WriteNumeric(998, "%s :DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP", user->nick.c_str()); + user->WriteNumeric(ERR_UNKNOWNDCCALLOWCMD, "DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP"); return CMD_FAILURE; } } - std::string nick = parameters[0].substr(1); + std::string nick(parameters[0], 1); User *target = ServerInstance->FindNickOnly(nick); - if ((target) && (!IS_SERVER(target)) && (!target->quitting) && (target->registered == REG_ALL)) + if ((target) && (!target->quitting) && (target->registered == REG_ALL)) { if (action == '-') { // check if it contains any entries - dl = ext->get(user); + dl = ext.get(user); if (dl) { for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i) @@ -118,7 +167,7 @@ class CommandDccallow : public Command if (i->nickname == target->nick) { dl->erase(i); - user->WriteNumeric(995, "%s %s :Removed %s from your DCCALLOW list", user->nick.c_str(), user->nick.c_str(), target->nick.c_str()); + user->WriteNumeric(RPL_DCCALLOWREMOVED, user->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", target->nick.c_str())); break; } } @@ -128,22 +177,22 @@ class CommandDccallow : public Command { if (target == user) { - user->WriteNumeric(996, "%s %s :You cannot add yourself to your own DCCALLOW list!", user->nick.c_str(), user->nick.c_str()); + user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "You cannot add yourself to your own DCCALLOW list!"); return CMD_FAILURE; } - dl = ext->get(user); + dl = ext.get(user); if (!dl) { dl = new dccallowlist; - ext->set(user, dl); + ext.set(user, dl); // add this user to the userlist ul.push_back(user); } if (dl->size() >= maxentries) { - user->WriteNumeric(996, "%s %s :Too many nicks on DCCALLOW list", user->nick.c_str(), user->nick.c_str()); + user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "Too many nicks on DCCALLOW list"); return CMD_FAILURE; } @@ -151,18 +200,18 @@ class CommandDccallow : public Command { if (k->nickname == target->nick) { - user->WriteNumeric(996, "%s %s :%s is already on your DCCALLOW list", user->nick.c_str(), user->nick.c_str(), target->nick.c_str()); + user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is already on your DCCALLOW list", target->nick.c_str())); return CMD_FAILURE; } } - std::string mask = target->nick+"!"+target->ident+"@"+target->dhost; + std::string mask = target->nick+"!"+target->ident+"@"+target->GetDisplayedHost(); std::string default_length = ServerInstance->Config->ConfValue("dccallow")->getString("length"); - long length; + unsigned long length; if (parameters.size() < 2) { - length = ServerInstance->Duration(default_length); + length = InspIRCd::Duration(default_length); } else if (!atoi(parameters[1].c_str())) { @@ -170,10 +219,10 @@ class CommandDccallow : public Command } else { - length = ServerInstance->Duration(parameters[1]); + length = InspIRCd::Duration(parameters[1]); } - if (!ServerInstance->IsValidMask(mask)) + if (!InspIRCd::IsValidMask(mask)) { return CMD_FAILURE; } @@ -182,11 +231,11 @@ class CommandDccallow : public Command if (length > 0) { - user->WriteNumeric(993, "%s %s :Added %s to DCCALLOW list for %ld seconds", user->nick.c_str(), user->nick.c_str(), target->nick.c_str(), length); + user->WriteNumeric(RPL_DCCALLOWTIMED, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for %ld seconds", target->nick.c_str(), length)); } else { - user->WriteNumeric(994, "%s %s :Added %s to DCCALLOW list for this session", user->nick.c_str(), user->nick.c_str(), target->nick.c_str()); + user->WriteNumeric(RPL_DCCALLOWPERMANENT, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for this session", target->nick.c_str())); } /* route it. */ @@ -196,40 +245,24 @@ class CommandDccallow : public Command else { // nick doesn't exist - user->WriteNumeric(401, "%s %s :No such nick/channel", user->nick.c_str(), nick.c_str()); + user->WriteNumeric(Numerics::NoSuchNick(nick)); return CMD_FAILURE; } } return CMD_FAILURE; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } void DisplayHelp(User* user) { - user->WriteNumeric(998, "%s :DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]", user->nick.c_str()); - user->WriteNumeric(998, "%s :You may allow DCCs from specific users by specifying a", user->nick.c_str()); - user->WriteNumeric(998, "%s :DCC allow for the user you want to receive DCCs from.", user->nick.c_str()); - user->WriteNumeric(998, "%s :For example, to allow the user Brain to send you inspircd.exe", user->nick.c_str()); - user->WriteNumeric(998, "%s :you would type:", user->nick.c_str()); - user->WriteNumeric(998, "%s :/DCCALLOW +Brain", user->nick.c_str()); - user->WriteNumeric(998, "%s :Brain would then be able to send you files. They would have to", user->nick.c_str()); - user->WriteNumeric(998, "%s :resend the file again if the server gave them an error message", user->nick.c_str()); - user->WriteNumeric(998, "%s :before you added them to your DCCALLOW list.", user->nick.c_str()); - user->WriteNumeric(998, "%s :DCCALLOW entries will be temporary by default, if you want to add", user->nick.c_str()); - user->WriteNumeric(998, "%s :them to your DCCALLOW list until you leave IRC, type:", user->nick.c_str()); - user->WriteNumeric(998, "%s :/DCCALLOW +Brain 0", user->nick.c_str()); - user->WriteNumeric(998, "%s :To remove the user from your DCCALLOW list, type:", user->nick.c_str()); - user->WriteNumeric(998, "%s :/DCCALLOW -Brain", user->nick.c_str()); - user->WriteNumeric(998, "%s :To see the users in your DCCALLOW list, type:", user->nick.c_str()); - user->WriteNumeric(998, "%s :/DCCALLOW LIST", user->nick.c_str()); - user->WriteNumeric(998, "%s :NOTE: If the user leaves IRC or changes their nickname", user->nick.c_str()); - user->WriteNumeric(998, "%s : they will be removed from your DCCALLOW list.", user->nick.c_str()); - user->WriteNumeric(998, "%s : your DCCALLOW list will be deleted when you leave IRC.", user->nick.c_str()); - user->WriteNumeric(999, "%s :End of DCCALLOW HELP", user->nick.c_str()); + user->WriteNumeric(RPL_HELPSTART, "*", "DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]"); + for (size_t i = 0; i < sizeof(helptext)/sizeof(helptext[0]); i++) + user->WriteNumeric(RPL_HELPTXT, "*", helptext[i]); + user->WriteNumeric(RPL_ENDOFHELP, "*", "End of DCCALLOW HELP"); LocalUser* localuser = IS_LOCAL(user); if (localuser) @@ -239,100 +272,75 @@ class CommandDccallow : public Command void DisplayDCCAllowList(User* user) { // display current DCCALLOW list - user->WriteNumeric(990, "%s :Users on your DCCALLOW list:", user->nick.c_str()); + user->WriteNumeric(RPL_DCCALLOWSTART, "Users on your DCCALLOW list:"); - dl = ext->get(user); + dl = ext.get(user); if (dl) { for (dccallowlist::const_iterator c = dl->begin(); c != dl->end(); ++c) { - user->WriteNumeric(991, "%s %s :%s (%s)", user->nick.c_str(), user->nick.c_str(), c->nickname.c_str(), c->hostmask.c_str()); + user->WriteNumeric(RPL_DCCALLOWLIST, user->nick, InspIRCd::Format("%s (%s)", c->nickname.c_str(), c->hostmask.c_str())); } } - user->WriteNumeric(992, "%s :End of DCCALLOW list", user->nick.c_str()); + user->WriteNumeric(RPL_DCCALLOWEND, "End of DCCALLOW list"); } }; class ModuleDCCAllow : public Module { + DCCAllowExt ext; CommandDccallow cmd; - public: + public: ModuleDCCAllow() - : cmd(this) + : ext("dccallow", ExtensionItem::EXT_USER, this) + , cmd(this, ext) { - ext = NULL; } - void init() + void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE { - ext = new SimpleExtItem<dccallowlist>("dccallow", this); - ServerInstance->Modules->AddService(*ext); - ServerInstance->Modules->AddService(cmd); - OnRehash(NULL); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_OnUserQuit, I_OnUserPostNick, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void OnRehash(User* user) - { - ReadFileConf(); - ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow"); - cmd.maxentries = tag->getInt("maxentries", 20); - } - - virtual void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) - { - dccallowlist* udl = ext->get(user); + dccallowlist* udl = ext.get(user); // remove their DCCALLOW list if they have one if (udl) - { - userlist::iterator it = std::find(ul.begin(), ul.end(), user); - if (it != ul.end()) - ul.erase(it); - } + stdalgo::erase(ul, user); // remove them from any DCCALLOW lists // they are currently on RemoveNick(user); } - virtual void OnUserPostNick(User* user, const std::string &oldnick) + void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE { RemoveNick(user); } - virtual ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreNotice(user, dest, target_type, text, status, exempt_list); - } - - virtual ModResult OnUserPreNotice(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; - if (target_type == TYPE_USER) + if (target.type == MessageTarget::TYPE_USER) { - User* u = (User*)dest; + User* u = target.Get<User>(); /* Always allow a user to dcc themselves (although... why?) */ if (user == u) return MOD_RES_PASSTHRU; - if ((text.length()) && (text[0] == '\1')) + if ((details.text.length()) && (details.text[0] == '\1')) { Expire(); // :jamie!jamie@test-D4457903BA652E0F.silverdream.org PRIVMSG eimaj :DCC SEND m_dnsbl.cpp 3232235786 52650 9676 // :jamie!jamie@test-D4457903BA652E0F.silverdream.org PRIVMSG eimaj :VERSION - if (strncmp(text.c_str(), "\1DCC ", 5) == 0) + if (strncmp(details.text.c_str(), "\1DCC ", 5) == 0) { - dl = ext->get(u); + dl = ext.get(u); if (dl && dl->size()) { for (dccallowlist::const_iterator iter = dl->begin(); iter != dl->end(); ++iter) @@ -340,17 +348,17 @@ class ModuleDCCAllow : public Module return MOD_RES_PASSTHRU; } - std::string buf = text.substr(5); + std::string buf = details.text.substr(5); size_t s = buf.find(' '); if (s == std::string::npos) return MOD_RES_PASSTHRU; - irc::string type = assign(buf.substr(0, s)); + const std::string type = buf.substr(0, s); ConfigTag* conftag = ServerInstance->Config->ConfValue("dccallow"); bool blockchat = conftag->getBool("blockchat"); - if (type == "SEND") + if (stdalgo::string::equalsci(type, "SEND")) { size_t first; @@ -384,7 +392,7 @@ class ModuleDCCAllow : public Module if (InspIRCd::Match(filename, bfl[i].filemask, ascii_case_insensitive_map)) { /* We have a matching badfile entry, override whatever the default action is */ - if (bfl[i].action == "allow") + if (stdalgo::string::equalsci(bfl[i].action, "allow")) return MOD_RES_PASSTHRU; else { @@ -398,16 +406,16 @@ class ModuleDCCAllow : public Module if ((!found) && (defaultaction == "allow")) return MOD_RES_PASSTHRU; - user->WriteServ("NOTICE %s :The user %s is not accepting DCC SENDs from you. Your file %s was not sent.", user->nick.c_str(), u->nick.c_str(), filename.c_str()); - u->WriteServ("NOTICE %s :%s (%s@%s) attempted to send you a file named %s, which was blocked.", u->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str(), filename.c_str()); - u->WriteServ("NOTICE %s :If you trust %s and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.", u->nick.c_str(), user->nick.c_str()); + user->WriteNotice("The user " + u->nick + " is not accepting DCC SENDs from you. Your file " + filename + " was not sent."); + u->WriteNotice(user->nick + " (" + user->ident + "@" + user->GetDisplayedHost() + ") attempted to send you a file named " + filename + ", which was blocked."); + u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system."); return MOD_RES_DENY; } - else if ((type == "CHAT") && (blockchat)) + else if ((blockchat) && (stdalgo::string::equalsci(type, "CHAT"))) { - user->WriteServ("NOTICE %s :The user %s is not accepting DCC CHAT requests from you.", user->nick.c_str(), u->nick.c_str()); - u->WriteServ("NOTICE %s :%s (%s@%s) attempted to initiate a DCC CHAT session, which was blocked.", u->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str()); - u->WriteServ("NOTICE %s :If you trust %s and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.", u->nick.c_str(), user->nick.c_str()); + user->WriteNotice("The user " + u->nick + " is not accepting DCC CHAT requests from you."); + u->WriteNotice(user->nick + " (" + user->ident + "@" + user->GetDisplayedHost() + ") attempted to initiate a DCC CHAT session, which was blocked."); + u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system."); return MOD_RES_DENY; } } @@ -421,7 +429,7 @@ class ModuleDCCAllow : public Module for (userlist::iterator iter = ul.begin(); iter != ul.end();) { User* u = (User*)(*iter); - dl = ext->get(u); + dl = ext.get(u); if (dl) { if (dl->size()) @@ -429,9 +437,10 @@ class ModuleDCCAllow : public Module dccallowlist::iterator iter2 = dl->begin(); while (iter2 != dl->end()) { - if (iter2->length != 0 && (iter2->set_on + iter2->length) <= ServerInstance->Time()) + time_t expires = iter2->set_on + iter2->length; + if (iter2->length != 0 && expires <= ServerInstance->Time()) { - u->WriteNumeric(997, "%s %s :DCCALLOW entry for %s has expired", u->nick.c_str(), u->nick.c_str(), iter2->nickname.c_str()); + u->WriteNumeric(RPL_DCCALLOWEXPIRED, u->nick, InspIRCd::Format("DCCALLOW entry for %s has expired", iter2->nickname.c_str())); iter2 = dl->erase(iter2); } else @@ -455,7 +464,7 @@ class ModuleDCCAllow : public Module for (userlist::iterator iter = ul.begin(); iter != ul.end();) { User *u = (User*)(*iter); - dl = ext->get(u); + dl = ext.get(u); if (dl) { if (dl->size()) @@ -465,8 +474,8 @@ class ModuleDCCAllow : public Module if (i->nickname == user->nick) { - u->WriteServ("NOTICE %s :%s left the network or changed their nickname and has been removed from your DCCALLOW list", u->nick.c_str(), i->nickname.c_str()); - u->WriteNumeric(995, "%s %s :Removed %s from your DCCALLOW list", u->nick.c_str(), u->nick.c_str(), i->nickname.c_str()); + u->WriteNotice(i->nickname + " left the network or changed their nickname and has been removed from your DCCALLOW list"); + u->WriteNumeric(RPL_DCCALLOWREMOVED, u->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", i->nickname.c_str())); dl->erase(i); break; } @@ -495,8 +504,11 @@ class ModuleDCCAllow : public Module } } - void ReadFileConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { + ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow"); + cmd.maxentries = tag->getUInt("maxentries", 20); + bfl.clear(); ConfigTagList tags = ServerInstance->Config->ConfTags("banfile"); for (ConfigIter i = tags.first; i != tags.second; ++i) @@ -508,12 +520,7 @@ class ModuleDCCAllow : public Module } } - virtual ~ModuleDCCAllow() - { - delete ext; - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the /DCCALLOW command", VF_COMMON | VF_VENDOR); } diff --git a/src/modules/m_deaf.cpp b/src/modules/m_deaf.cpp index 43b24cfae..e58d130a5 100644 --- a/src/modules/m_deaf.cpp +++ b/src/modules/m_deaf.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides usermode +d to block channel messages and channel notices */ - /** User mode +d - filter out channel messages and channel notices */ class User_d : public ModeHandler @@ -30,33 +28,22 @@ class User_d : public ModeHandler public: User_d(Module* Creator) : ModeHandler(Creator, "deaf", 'd', PARAM_NONE, MODETYPE_USER) { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { + if (adding == dest->IsModeSet(this)) + return MODEACTION_DENY; + if (adding) - { - if (!dest->IsModeSet('d')) - { - dest->WriteServ("NOTICE %s :*** You have enabled usermode +d, deaf mode. This mode means you WILL NOT receive any messages from any channels you are in. If you did NOT mean to do this, use /mode %s -d.", dest->nick.c_str(), dest->nick.c_str()); - dest->SetMode('d',true); - return MODEACTION_ALLOW; - } - } - else - { - if (dest->IsModeSet('d')) - { - dest->SetMode('d',false); - return MODEACTION_ALLOW; - } - } - return MODEACTION_DENY; + dest->WriteNotice("*** You have enabled usermode +d, deaf mode. This mode means you WILL NOT receive any messages from any channels you are in. If you did NOT mean to do this, use /mode " + dest->nick + " -d."); + + dest->SetMode(this, adding); + return MODEACTION_ALLOW; } }; class ModuleDeaf : public Module { User_d m1; - std::string deaf_bypasschars; std::string deaf_bypasschars_uline; @@ -66,84 +53,40 @@ class ModuleDeaf : public Module { } - void init() - { - ServerInstance->Modules->AddService(m1); - - OnRehash(NULL); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("deaf"); deaf_bypasschars = tag->getString("bypasschars"); deaf_bypasschars_uline = tag->getString("bypasscharsuline"); } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (target_type == TYPE_CHANNEL) - { - Channel* chan = (Channel*)dest; - if (chan) - this->BuildDeafList(MSG_NOTICE, chan, user, status, text, exempt_list); - } + if (target.type != MessageTarget::TYPE_CHANNEL) + return MOD_RES_PASSTHRU; - return MOD_RES_PASSTHRU; - } - - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - if (target_type == TYPE_CHANNEL) - { - Channel* chan = (Channel*)dest; - if (chan) - this->BuildDeafList(MSG_PRIVMSG, chan, user, status, text, exempt_list); - } - - return MOD_RES_PASSTHRU; - } - - virtual void BuildDeafList(MessageType message_type, Channel* chan, User* sender, char status, const std::string &text, CUList &exempt_list) - { - const UserMembList *ulist = chan->GetUsers(); - bool is_a_uline; - bool is_bypasschar, is_bypasschar_avail; - bool is_bypasschar_uline, is_bypasschar_uline_avail; - - is_bypasschar = is_bypasschar_avail = is_bypasschar_uline = is_bypasschar_uline_avail = 0; - if (!deaf_bypasschars.empty()) - { - is_bypasschar_avail = 1; - if (deaf_bypasschars.find(text[0], 0) != std::string::npos) - is_bypasschar = 1; - } - if (!deaf_bypasschars_uline.empty()) - { - is_bypasschar_uline_avail = 1; - if (deaf_bypasschars_uline.find(text[0], 0) != std::string::npos) - is_bypasschar_uline = 1; - } + Channel* chan = target.Get<Channel>(); + bool is_bypasschar = (deaf_bypasschars.find(details.text[0]) != std::string::npos); + bool is_bypasschar_uline = (deaf_bypasschars_uline.find(details.text[0]) != std::string::npos); /* * If we have no bypasschars_uline in config, and this is a bypasschar (regular) * Than it is obviously going to get through +d, no build required */ - if (!is_bypasschar_uline_avail && is_bypasschar) - return; + if (deaf_bypasschars_uline.empty() && is_bypasschar) + return MOD_RES_PASSTHRU; - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + const Channel::MemberMap& ulist = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { /* not +d ? */ - if (!i->first->IsModeSet('d')) + if (!i->first->IsModeSet(m1)) continue; /* deliver message */ /* matched both U-line only and regular bypasses */ if (is_bypasschar && is_bypasschar_uline) continue; /* deliver message */ - is_a_uline = ServerInstance->ULine(i->first->server); + bool is_a_uline = i->first->server->IsULine(); /* matched a U-line only bypass */ if (is_bypasschar_uline && is_a_uline) continue; /* deliver message */ @@ -151,23 +94,17 @@ class ModuleDeaf : public Module if (is_bypasschar && !is_a_uline) continue; /* deliver message */ - if (status && !strchr(chan->GetAllPrefixChars(i->first), status)) - continue; - /* don't deliver message! */ - exempt_list.insert(i->first); + details.exemptions.insert(i->first); } - } - virtual ~ModuleDeaf() - { + return MOD_RES_PASSTHRU; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides usermode +d to block channel messages and channel notices", VF_VENDOR); } - }; MODULE_INIT(ModuleDeaf) diff --git a/src/modules/m_delayjoin.cpp b/src/modules/m_delayjoin.cpp index 20d4c8e8f..d157b97bc 100644 --- a/src/modules/m_delayjoin.cpp +++ b/src/modules/m_delayjoin.cpp @@ -20,56 +20,91 @@ */ -/* $ModDesc: Allows for delay-join channels (+D) where users don't appear to join until they speak */ - #include "inspircd.h" -#include <stdarg.h> class DelayJoinMode : public ModeHandler { - private: - CUList empty; public: DelayJoinMode(Module* Parent) : ModeHandler(Parent, "delayjoin", 'D', PARAM_NONE, MODETYPE_CHANNEL) { - levelrequired = OP_VALUE; + ranktoset = ranktounset = OP_VALUE; + } + + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE; +}; + + +namespace +{ + +/** Hook handler for join client protocol events. + * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY). + * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module + * such as hostcycle generates a join. + */ +class JoinHook : public ClientProtocol::EventHook +{ + const LocalIntExt& unjoined; + + public: + JoinHook(Module* mod, const LocalIntExt& unjoinedref) + : ClientProtocol::EventHook(mod, "JOIN", 10) + , unjoined(unjoinedref) + { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding); + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE + { + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + const User* const u = join.GetMember()->user; + if ((unjoined.get(u)) && (u != user)) + return MOD_RES_DENY; + return MOD_RES_PASSTHRU; + } }; +} + class ModuleDelayJoin : public Module { DelayJoinMode djm; + void RevealUser(User* user, Channel* chan); public: LocalIntExt unjoined; - ModuleDelayJoin() : djm(this), unjoined("delayjoin", this) - { - } + JoinHook joinhook; - void init() + ModuleDelayJoin() + : djm(this) + , unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this) + , joinhook(this, unjoined) { - ServerInstance->Modules->AddService(djm); - ServerInstance->Modules->AddService(unjoined); - Implementation eventlist[] = { I_OnUserJoin, I_OnUserPart, I_OnUserKick, I_OnBuildNeighborList, I_OnNamesListItem, I_OnText, I_OnRawMode }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - ~ModuleDelayJoin(); - Version GetVersion(); - void OnNamesListItem(User* issuer, Membership*, std::string &prefixes, std::string &nick); - void OnUserJoin(Membership*, bool, bool, CUList&); + + Version GetVersion() CXX11_OVERRIDE; + ModResult OnNamesListItem(User* issuer, Membership*, std::string& prefixes, std::string& nick) CXX11_OVERRIDE; + void OnUserJoin(Membership*, bool, bool, CUList&) CXX11_OVERRIDE; void CleanUser(User* user); - void OnUserPart(Membership*, std::string &partmessage, CUList&); - void OnUserKick(User* source, Membership*, const std::string &reason, CUList&); - void OnBuildNeighborList(User* source, UserChanList &include, std::map<User*,bool> &exception); - void OnText(User* user, void* dest, int target_type, const std::string &text, char status, CUList &exempt_list); - ModResult OnRawMode(User* user, Channel* channel, const char mode, const std::string ¶m, bool adding, int pcnt); + void OnUserPart(Membership*, std::string &partmessage, CUList&) CXX11_OVERRIDE; + void OnUserKick(User* source, Membership*, const std::string &reason, CUList&) CXX11_OVERRIDE; + void OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) CXX11_OVERRIDE; + void OnUserMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE; + ModResult OnRawMode(User* user, Channel* channel, ModeHandler* mh, const std::string& param, bool adding) CXX11_OVERRIDE; +}; + +// TODO: make RevealUser accessible from DelayJoinMode and get rid of this. +class DummyMessageDetails : public MessageDetails +{ +public: + DummyMessageDetails() : MessageDetails(MSG_PRIVMSG, "", ClientProtocol::TagMap()) { } + bool IsCTCP(std::string& name, std::string& body) const CXX11_OVERRIDE { return false; } + bool IsCTCP(std::string&) const CXX11_OVERRIDE { return false; } + bool IsCTCP() const CXX11_OVERRIDE { return false; } }; ModeAction DelayJoinMode::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { /* no change */ - if (channel->IsModeSet('D') == adding) + if (channel->IsModeSet(this) == adding) return MODEACTION_DENY; if (!adding) @@ -78,38 +113,40 @@ ModeAction DelayJoinMode::OnModeChange(User* source, User* dest, Channel* channe * Make all users visible, as +D is being removed. If we don't do this, * they remain permanently invisible on this channel! */ - const UserMembList* names = channel->GetUsers(); - for (UserMembCIter n = names->begin(); n != names->end(); ++n) - creator->OnText(n->first, channel, TYPE_CHANNEL, "", 0, empty); + MessageTarget msgtarget(channel, 0); + DummyMessageDetails msgdetails; + const Channel::MemberMap& users = channel->GetUsers(); + for (Channel::MemberMap::const_iterator n = users.begin(); n != users.end(); ++n) + { + creator->OnUserMessage(n->first, msgtarget, msgdetails); + } } - channel->SetMode('D', adding); + channel->SetMode(this, adding); return MODEACTION_ALLOW; } -ModuleDelayJoin::~ModuleDelayJoin() -{ -} - Version ModuleDelayJoin::GetVersion() { return Version("Allows for delay-join channels (+D) where users don't appear to join until they speak", VF_VENDOR); } -void ModuleDelayJoin::OnNamesListItem(User* issuer, Membership* memb, std::string &prefixes, std::string &nick) +ModResult ModuleDelayJoin::OnNamesListItem(User* issuer, Membership* memb, std::string& prefixes, std::string& nick) { /* don't prevent the user from seeing themself */ if (issuer == memb->user) - return; + return MOD_RES_PASSTHRU; /* If the user is hidden by delayed join, hide them from the NAMES list */ if (unjoined.get(memb)) - nick.clear(); + return MOD_RES_DENY; + + return MOD_RES_PASSTHRU; } static void populate(CUList& except, Membership* memb) { - const UserMembList* users = memb->chan->GetUsers(); - for(UserMembCIter i = users->begin(); i != users->end(); i++) + const Channel::MemberMap& users = memb->chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) { if (i->first == memb->user || !IS_LOCAL(i->first)) continue; @@ -119,11 +156,8 @@ static void populate(CUList& except, Membership* memb) void ModuleDelayJoin::OnUserJoin(Membership* memb, bool sync, bool created, CUList& except) { - if (memb->chan->IsModeSet('D')) - { + if (memb->chan->IsModeSet(djm)) unjoined.set(memb, 1); - populate(except, memb); - } } void ModuleDelayJoin::OnUserPart(Membership* memb, std::string &partmessage, CUList& except) @@ -138,53 +172,48 @@ void ModuleDelayJoin::OnUserKick(User* source, Membership* memb, const std::stri populate(except, memb); } -void ModuleDelayJoin::OnBuildNeighborList(User* source, UserChanList &include, std::map<User*,bool> &exception) +void ModuleDelayJoin::OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) { - UCListIter i = include.begin(); - while (i != include.end()) + for (IncludeChanList::iterator i = include.begin(); i != include.end(); ) { - Channel* c = *i++; - Membership* memb = c->GetUser(source); - if (memb && unjoined.get(memb)) - include.erase(c); + Membership* memb = *i; + if (unjoined.get(memb)) + i = include.erase(i); + else + ++i; } } -void ModuleDelayJoin::OnText(User* user, void* dest, int target_type, const std::string &text, char status, CUList &exempt_list) +void ModuleDelayJoin::OnUserMessage(User* user, const MessageTarget& target, const MessageDetails& details) { - /* Server origin */ - if (!user) - return; - - if (target_type != TYPE_CHANNEL) + if (target.type != MessageTarget::TYPE_CHANNEL) return; - Channel* channel = static_cast<Channel*>(dest); + Channel* channel = target.Get<Channel>(); + RevealUser(user, channel); +} - Membership* memb = channel->GetUser(user); +void ModuleDelayJoin::RevealUser(User* user, Channel* chan) +{ + Membership* memb = chan->GetUser(user); if (!memb || !unjoined.set(memb, 0)) return; /* Display the join to everyone else (the user who joined got it earlier) */ - channel->WriteAllExceptSender(user, false, 0, "JOIN %s", channel->name.c_str()); - - std::string ms = memb->modes; - for(unsigned int i=0; i < memb->modes.length(); i++) - ms.append(" ").append(user->nick); - - if (ms.length() > 0) - channel->WriteAllExceptSender(user, false, 0, "MODE %s +%s", channel->name.c_str(), ms.c_str()); + CUList except_list; + except_list.insert(user); + ClientProtocol::Events::Join joinevent(memb); + chan->Write(joinevent, 0, except_list); } /* make the user visible if he receives any mode change */ -ModResult ModuleDelayJoin::OnRawMode(User* user, Channel* channel, const char mode, const std::string ¶m, bool adding, int pcnt) +ModResult ModuleDelayJoin::OnRawMode(User* user, Channel* channel, ModeHandler* mh, const std::string& param, bool adding) { - if (!user || !channel || param.empty()) + if (!channel || param.empty()) return MOD_RES_PASSTHRU; - ModeHandler* mh = ServerInstance->Modes->FindMode(mode, MODETYPE_CHANNEL); // If not a prefix mode then we got nothing to do here - if (!mh || !mh->GetPrefixRank()) + if (!mh->IsPrefixMode()) return MOD_RES_PASSTHRU; User* dest; @@ -196,9 +225,7 @@ ModResult ModuleDelayJoin::OnRawMode(User* user, Channel* channel, const char mo if (!dest) return MOD_RES_PASSTHRU; - Membership* memb = channel->GetUser(dest); - if (memb && unjoined.set(memb, 0)) - channel->WriteAllExceptSender(dest, false, 0, "JOIN %s", channel->name.c_str()); + RevealUser(dest, channel); return MOD_RES_PASSTHRU; } diff --git a/src/modules/m_delaymsg.cpp b/src/modules/m_delaymsg.cpp index 978ab55d2..b39fb1d0a 100644 --- a/src/modules/m_delaymsg.cpp +++ b/src/modules/m_delaymsg.cpp @@ -19,82 +19,67 @@ #include "inspircd.h" -/* $ModDesc: Provides channelmode +d <int>, to deny messages to a channel until <int> seconds. */ - -class DelayMsgMode : public ModeHandler +class DelayMsgMode : public ParamMode<DelayMsgMode, LocalIntExt> { public: LocalIntExt jointime; - DelayMsgMode(Module* Parent) : ModeHandler(Parent, "delaymsg", 'd', PARAM_SETONLY, MODETYPE_CHANNEL) - , jointime("delaymsg", Parent) + DelayMsgMode(Module* Parent) + : ParamMode<DelayMsgMode, LocalIntExt>(Parent, "delaymsg", 'd') + , jointime("delaymsg", ExtensionItem::EXT_MEMBERSHIP, Parent) { - levelrequired = OP_VALUE; + ranktoset = ranktounset = OP_VALUE; } - bool ResolveModeConflict(std::string &their_param, const std::string &our_param, Channel*) + bool ResolveModeConflict(std::string& their_param, const std::string& our_param, Channel*) CXX11_OVERRIDE { return (atoi(their_param.c_str()) < atoi(our_param.c_str())); } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding); + ModeAction OnSet(User* source, Channel* chan, std::string& parameter) CXX11_OVERRIDE; + void OnUnset(User* source, Channel* chan); + + void SerializeParam(Channel* chan, int n, std::string& out) + { + out += ConvToStr(n); + } }; class ModuleDelayMsg : public Module { - private: DelayMsgMode djm; + bool allownotice; public: ModuleDelayMsg() : djm(this) { } - void init() - { - ServerInstance->Modules->AddService(djm); - ServerInstance->Modules->AddService(djm.jointime); - Implementation eventlist[] = { I_OnUserJoin, I_OnUserPreMessage, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } - Version GetVersion(); - void OnUserJoin(Membership* memb, bool sync, bool created, CUList&); - ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list); - ModResult OnUserPreNotice(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list); - void OnRehash(User* user); + Version GetVersion() CXX11_OVERRIDE; + void OnUserJoin(Membership* memb, bool sync, bool created, CUList&) CXX11_OVERRIDE; + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE; + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE; }; -ModeAction DelayMsgMode::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) +ModeAction DelayMsgMode::OnSet(User* source, Channel* chan, std::string& parameter) { - if (adding) - { - if ((channel->IsModeSet('d')) && (channel->GetModeParameter('d') == parameter)) - return MODEACTION_DENY; - - /* Setting a new limit, sanity check */ - long limit = atoi(parameter.c_str()); - - /* Wrap low values at 32768 */ - if (limit < 0) - limit = 0x7FFF; + // Setting a new limit, sanity check + unsigned int limit = ConvToInt(parameter); + if (limit == 0) + limit = 1; - parameter = ConvToStr(limit); - } - else - { - if (!channel->IsModeSet('d')) - return MODEACTION_DENY; - - /* - * Clean up metadata - */ - const UserMembList* names = channel->GetUsers(); - for (UserMembCIter n = names->begin(); n != names->end(); ++n) - jointime.set(n->second, 0); - } - channel->SetModeParam('d', adding ? parameter : ""); + ext.set(chan, limit); return MODEACTION_ALLOW; } +void DelayMsgMode::OnUnset(User* source, Channel* chan) +{ + /* + * Clean up metadata + */ + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator n = users.begin(); n != users.end(); ++n) + jointime.set(n->second, 0); +} + Version ModuleDelayMsg::GetVersion() { return Version("Provides channelmode +d <int>, to deny messages to a channel until <int> seconds.", VF_VENDOR); @@ -102,22 +87,21 @@ Version ModuleDelayMsg::GetVersion() void ModuleDelayMsg::OnUserJoin(Membership* memb, bool sync, bool created, CUList&) { - if ((IS_LOCAL(memb->user)) && (memb->chan->IsModeSet('d'))) + if ((IS_LOCAL(memb->user)) && (memb->chan->IsModeSet(djm))) { djm.jointime.set(memb, ServerInstance->Time()); } } -ModResult ModuleDelayMsg::OnUserPreMessage(User* user, void* dest, int target_type, std::string &text, char status, CUList &exempt_list) +ModResult ModuleDelayMsg::OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) { - /* Server origin */ - if ((!user) || (!IS_LOCAL(user))) + if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; - if (target_type != TYPE_CHANNEL) + if ((target.type != MessageTarget::TYPE_CHANNEL) || ((!allownotice) && (details.type == MSG_NOTICE))) return MOD_RES_PASSTHRU; - Channel* channel = (Channel*) dest; + Channel* channel = target.Get<Channel>(); Membership* memb = channel->GetUser(user); if (!memb) @@ -128,14 +112,13 @@ ModResult ModuleDelayMsg::OnUserPreMessage(User* user, void* dest, int target_ty if (ts == 0) return MOD_RES_PASSTHRU; - std::string len = channel->GetModeParameter('d'); + int len = djm.ext.get(channel); - if (ts + atoi(len.c_str()) > ServerInstance->Time()) + if ((ts + len) > ServerInstance->Time()) { if (channel->GetPrefixValue(user) < VOICE_VALUE) { - user->WriteNumeric(404, "%s %s :You must wait %s seconds after joining to send to channel (+d)", - user->nick.c_str(), channel->name.c_str(), len.c_str()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, channel->name, InspIRCd::Format("You must wait %d seconds after joining to send to channel (+d)", len)); return MOD_RES_DENY; } } @@ -147,19 +130,10 @@ ModResult ModuleDelayMsg::OnUserPreMessage(User* user, void* dest, int target_ty return MOD_RES_PASSTHRU; } -ModResult ModuleDelayMsg::OnUserPreNotice(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list) -{ - return OnUserPreMessage(user, dest, target_type, text, status, exempt_list); -} - -void ModuleDelayMsg::OnRehash(User* user) +void ModuleDelayMsg::ReadConfig(ConfigStatus& status) { ConfigTag* tag = ServerInstance->Config->ConfValue("delaymsg"); - if (tag->getBool("allownotice", true)) - ServerInstance->Modules->Detach(I_OnUserPreNotice, this); - else - ServerInstance->Modules->Attach(I_OnUserPreNotice, this); + allownotice = tag->getBool("allownotice", true); } MODULE_INIT(ModuleDelayMsg) - diff --git a/src/modules/m_denychans.cpp b/src/modules/m_denychans.cpp index 39d9e0d34..cc4172529 100644 --- a/src/modules/m_denychans.cpp +++ b/src/modules/m_denychans.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + *. Copyright (C) 2018 Peter Powell <petpow@saberuk.com> * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net> * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> @@ -22,112 +23,160 @@ #include "inspircd.h" -/* $ModDesc: Implements config tags which allow blocking of joins to channels */ +enum +{ + // InspIRCd-specific. + ERR_BADCHANNEL = 926 +}; + +struct BadChannel +{ + bool allowopers; + std::string name; + std::string reason; + std::string redirect; + + BadChannel(const std::string& Name, const std::string& Redirect, const std::string& Reason, bool AllowOpers) + : allowopers(AllowOpers) + , name(Name) + , reason(Reason) + , redirect(Redirect) + { + } +}; + +typedef std::vector<BadChannel> BadChannels; +typedef std::vector<std::string> GoodChannels; class ModuleDenyChannels : public Module { + private: + BadChannels badchannels; + GoodChannels goodchannels; + UserModeReference antiredirectmode; + ChanModeReference redirectmode; + public: - void init() + ModuleDenyChannels() + : antiredirectmode(this, "antiredirect") + , redirectmode(this, "redirect") { - Implementation eventlist[] = { I_OnUserPreJoin, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - /* check for redirect validity and loops/chains */ - ConfigTagList tags = ServerInstance->Config->ConfTags("badchan"); + GoodChannels goodchans; + ConfigTagList tags = ServerInstance->Config->ConfTags("goodchan"); + for (ConfigIter iter = tags.first; iter != tags.second; ++iter) + { + ConfigTag* tag = iter->second; + + // Ensure that we have the <goodchan:name> parameter. + const std::string name = tag->getString("name"); + if (name.empty()) + throw ModuleException("<goodchan:name> is a mandatory field, at " + tag->getTagLocation()); + + goodchans.push_back(name); + } + + BadChannels badchans; + tags = ServerInstance->Config->ConfTags("badchan"); for (ConfigIter i = tags.first; i != tags.second; ++i) { - std::string name = i->second->getString("name"); - std::string redirect = i->second->getString("redirect"); + ConfigTag* tag = i->second; + + // Ensure that we have the <badchan:name> parameter. + const std::string name = tag->getString("name"); + if (name.empty()) + throw ModuleException("<badchan:name> is a mandatory field, at " + tag->getTagLocation()); + + // Ensure that we have the <badchan:reason> parameter. + const std::string reason = tag->getString("reason"); + if (reason.empty()) + throw ModuleException("<badchan:reason> is a mandatory field, at " + tag->getTagLocation()); + const std::string redirect = tag->getString("redirect"); if (!redirect.empty()) { + // Ensure that <badchan:redirect> contains a channel name. + if (!ServerInstance->IsChannel(redirect)) + throw ModuleException("<badchan:redirect> is not a valid channel name, at " + tag->getTagLocation()); - if (!ServerInstance->IsChannel(redirect.c_str(), ServerInstance->Config->Limits.ChanMax)) - { - if (user) - user->WriteServ("NOTICE %s :Invalid badchan redirect '%s'", user->nick.c_str(), redirect.c_str()); - throw ModuleException("Invalid badchan redirect, not a channel"); - } - - for (ConfigIter j = tags.first; j != tags.second; ++j) - { - if (InspIRCd::Match(redirect, j->second->getString("name"))) - { - bool goodchan = false; - ConfigTagList goodchans = ServerInstance->Config->ConfTags("goodchan"); - for (ConfigIter k = goodchans.first; k != goodchans.second; ++k) - { - if (InspIRCd::Match(redirect, k->second->getString("name"))) - goodchan = true; - } - - if (!goodchan) - { - /* <badchan:redirect> is a badchan */ - if (user) - user->WriteServ("NOTICE %s :Badchan %s redirects to badchan %s", user->nick.c_str(), name.c_str(), redirect.c_str()); - throw ModuleException("Badchan redirect loop"); - } - } - } + // We defer the rest of the validation of the redirect channel until we have + // finished parsing all of the badchans. } + + badchans.push_back(BadChannel(name, redirect, reason, tag->getBool("allowopers"))); } - } - virtual ~ModuleDenyChannels() - { + // Now we have all of the badchan information recorded we can check that all redirect + // channels can actually be redirected to. + for (BadChannels::const_iterator i = badchans.begin(); i != badchans.end(); ++i) + { + const BadChannel& badchan = *i; + + // If there is no redirect channel we have nothing to do. + if (badchan.redirect.empty()) + continue; + + // If the redirect channel is whitelisted then it is okay. + for (GoodChannels::const_iterator j = goodchans.begin(); j != goodchans.end(); ++j) + if (InspIRCd::Match(badchan.redirect, *j)) + continue; + + // If the redirect channel is not blacklisted then it is okay. + for (BadChannels::const_iterator j = badchans.begin(); j != badchans.end(); ++j) + if (InspIRCd::Match(badchan.redirect, j->name)) + throw ModuleException("<badchan:redirect> cannot be a blacklisted channel name"); + } + + // The config file contained no errors so we can apply the new configuration. + badchannels.swap(badchans); + goodchannels.swap(goodchans); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements config tags which allow blocking of joins to channels", VF_VENDOR); } - virtual ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - ConfigTagList tags = ServerInstance->Config->ConfTags("badchan"); - for (ConfigIter j = tags.first; j != tags.second; ++j) + for (BadChannels::const_iterator j = badchannels.begin(); j != badchannels.end(); ++j) { - if (InspIRCd::Match(cname, j->second->getString("name"))) - { - if (IS_OPER(user) && j->second->getBool("allowopers")) - { + const BadChannel& badchan = *j; + + // If the channel does not match the current entry we have nothing else to do. + if (!InspIRCd::Match(cname, badchan.name)) + continue; + + // If the user is an oper and opers are allowed to enter this blacklisted channel + // then allow the join. + if (user->IsOper() && badchan.allowopers) + return MOD_RES_PASSTHRU; + + // If the channel matches a whitelist then allow the join. + for (GoodChannels::const_iterator i = goodchannels.begin(); i != goodchannels.end(); ++i) + if (InspIRCd::Match(cname, *i)) return MOD_RES_PASSTHRU; - } - else - { - std::string reason = j->second->getString("reason"); - std::string redirect = j->second->getString("redirect"); - - ConfigTagList goodchans = ServerInstance->Config->ConfTags("goodchan"); - for (ConfigIter i = goodchans.first; i != goodchans.second; ++i) - { - if (InspIRCd::Match(cname, i->second->getString("name"))) - { - return MOD_RES_PASSTHRU; - } - } - - if (ServerInstance->IsChannel(redirect.c_str(), ServerInstance->Config->Limits.ChanMax)) - { - /* simple way to avoid potential loops: don't redirect to +L channels */ - Channel *newchan = ServerInstance->FindChan(redirect); - if ((!newchan) || (!(newchan->IsModeSet('L')))) - { - user->WriteNumeric(926, "%s %s :Channel %s is forbidden, redirecting to %s: %s",user->nick.c_str(),cname,cname,redirect.c_str(), reason.c_str()); - Channel::JoinUser(user,redirect.c_str(),false,"",false,ServerInstance->Time()); - return MOD_RES_DENY; - } - } - - user->WriteNumeric(926, "%s %s :Channel %s is forbidden: %s",user->nick.c_str(),cname,cname,reason.c_str()); - return MOD_RES_DENY; - } + + // If there is no redirect chan, the user has enabled the antiredirect mode, or + // the target channel redirects elsewhere we just tell the user and deny the join. + Channel* target = NULL; + if (badchan.redirect.empty() || user->IsModeSet(antiredirectmode) + || ((target = ServerInstance->FindChan(badchan.redirect)) && target->IsModeSet(redirectmode))) + { + user->WriteNumeric(ERR_BADCHANNEL, cname, InspIRCd::Format("Channel %s is forbidden: %s", + cname.c_str(), badchan.reason.c_str())); + return MOD_RES_DENY; } + + // Redirect the user to the target channel. + user->WriteNumeric(ERR_BADCHANNEL, cname, InspIRCd::Format("Channel %s is forbidden, redirecting to %s: %s", + cname.c_str(), badchan.redirect.c_str(), badchan.reason.c_str())); + Channel::JoinUser(user, badchan.redirect); + return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } diff --git a/src/modules/m_devoice.cpp b/src/modules/m_devoice.cpp deleted file mode 100644 index 2b5de2bd6..000000000 --- a/src/modules/m_devoice.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2005, 2007 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -/* - * DEVOICE module for InspIRCd - * Syntax: /DEVOICE <#chan> - */ - -/* $ModDesc: Provides voiced users with the ability to devoice themselves. */ - -#include "inspircd.h" - -/** Handle /DEVOICE - */ -class CommandDevoice : public Command -{ - public: - CommandDevoice(Module* Creator) : Command(Creator,"DEVOICE", 1) - { - syntax = "<channel>"; - TRANSLATE2(TR_TEXT, TR_END); - } - - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) - { - Channel* c = ServerInstance->FindChan(parameters[0]); - if (c && c->HasUser(user)) - { - std::vector<std::string> modes; - modes.push_back(parameters[0]); - modes.push_back("-v"); - modes.push_back(user->nick); - - ServerInstance->SendGlobalMode(modes, ServerInstance->FakeClient); - return CMD_SUCCESS; - } - - return CMD_FAILURE; - } -}; - -class ModuleDeVoice : public Module -{ - CommandDevoice cmd; - public: - ModuleDeVoice() : cmd(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleDeVoice() - { - } - - virtual Version GetVersion() - { - return Version("Provides voiced users with the ability to devoice themselves.", VF_VENDOR); - } -}; - -MODULE_INIT(ModuleDeVoice) diff --git a/src/modules/m_dnsbl.cpp b/src/modules/m_dnsbl.cpp index 3dea080ce..89fecf696 100644 --- a/src/modules/m_dnsbl.cpp +++ b/src/modules/m_dnsbl.cpp @@ -23,8 +23,8 @@ #include "inspircd.h" #include "xline.h" - -/* $ModDesc: Provides handling of DNS blacklists */ +#include "modules/dns.h" +#include "modules/stats.h" /* Class holding data for a single entry */ class DNSBLConfEntry : public refcountbase @@ -35,18 +35,17 @@ class DNSBLConfEntry : public refcountbase std::string name, ident, host, domain, reason; EnumBanaction banaction; EnumType type; - long duration; - int bitmask; + unsigned long duration; + unsigned int bitmask; unsigned char records[256]; unsigned long stats_hits, stats_misses; DNSBLConfEntry(): type(A_BITMASK),duration(86400),bitmask(0),stats_hits(0), stats_misses(0) {} - ~DNSBLConfEntry() { } }; -/** Resolver for CGI:IRC hostnames encoded in ident/GECOS +/** Resolver for CGI:IRC hostnames encoded in ident/real name */ -class DNSBLResolver : public Resolver +class DNSBLResolver : public DNS::Request { std::string theiruid; LocalStringExt& nameExt; @@ -55,175 +54,177 @@ class DNSBLResolver : public Resolver public: - DNSBLResolver(Module *me, LocalStringExt& match, LocalIntExt& ctr, const std::string &hostname, LocalUser* u, reference<DNSBLConfEntry> conf, bool &cached) - : Resolver(hostname, DNS_QUERY_A, cached, me), theiruid(u->uuid), nameExt(match), countExt(ctr), ConfEntry(conf) + DNSBLResolver(DNS::Manager *mgr, Module *me, LocalStringExt& match, LocalIntExt& ctr, const std::string &hostname, LocalUser* u, reference<DNSBLConfEntry> conf) + : DNS::Request(mgr, me, hostname, DNS::QUERY_A, true), theiruid(u->uuid), nameExt(match), countExt(ctr), ConfEntry(conf) { } /* Note: This may be called multiple times for multiple A record results */ - virtual void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) + void OnLookupComplete(const DNS::Query *r) CXX11_OVERRIDE { /* Check the user still exists */ LocalUser* them = (LocalUser*)ServerInstance->FindUUID(theiruid); - if (them) + if (!them) + return; + + const DNS::ResourceRecord* const ans_record = r->FindAnswerOfType(DNS::QUERY_A); + if (!ans_record) + return; + + // All replies should be in 127.0.0.0/8 + if (ans_record->rdata.compare(0, 4, "127.") != 0) + { + ServerInstance->SNO->WriteGlobalSno('d', "DNSBL: %s returned address outside of acceptable subnet 127.0.0.0/8: %s", ConfEntry->domain.c_str(), ans_record->rdata.c_str()); + ConfEntry->stats_misses++; + return; + } + + int i = countExt.get(them); + if (i) + countExt.set(them, i - 1); + + // Now we calculate the bitmask: 256*(256*(256*a+b)+c)+d + + unsigned int bitmask = 0, record = 0; + bool match = false; + in_addr resultip; + + inet_pton(AF_INET, ans_record->rdata.c_str(), &resultip); + + switch (ConfEntry->type) + { + case DNSBLConfEntry::A_BITMASK: + bitmask = resultip.s_addr >> 24; /* Last octet (network byte order) */ + bitmask &= ConfEntry->bitmask; + match = (bitmask != 0); + break; + case DNSBLConfEntry::A_RECORD: + record = resultip.s_addr >> 24; /* Last octet */ + match = (ConfEntry->records[record] == 1); + break; + } + + if (match) { - int i = countExt.get(them); - if (i) - countExt.set(them, i - 1); - // All replies should be in 127.0.0.0/8 - if (result.compare(0, 4, "127.") == 0) + std::string reason = ConfEntry->reason; + std::string::size_type x = reason.find("%ip%"); + while (x != std::string::npos) { - unsigned int bitmask = 0, record = 0; - bool match = false; - in_addr resultip; + reason.erase(x, 4); + reason.insert(x, them->GetIPString()); + x = reason.find("%ip%"); + } - inet_aton(result.c_str(), &resultip); + ConfEntry->stats_hits++; - switch (ConfEntry->type) + switch (ConfEntry->banaction) + { + case DNSBLConfEntry::I_KILL: { - case DNSBLConfEntry::A_BITMASK: - // Now we calculate the bitmask: 256*(256*(256*a+b)+c)+d - bitmask = resultip.s_addr >> 24; /* Last octet (network byte order) */ - bitmask &= ConfEntry->bitmask; - match = (bitmask != 0); - break; - case DNSBLConfEntry::A_RECORD: - record = resultip.s_addr >> 24; /* Last octet */ - match = (ConfEntry->records[record] == 1); + ServerInstance->Users->QuitUser(them, "Killed (" + reason + ")"); break; } - - if (match) + case DNSBLConfEntry::I_MARK: { - std::string reason = ConfEntry->reason; - std::string::size_type x = reason.find("%ip%"); - while (x != std::string::npos) + if (!ConfEntry->ident.empty()) { - reason.erase(x, 4); - reason.insert(x, them->GetIPString()); - x = reason.find("%ip%"); + them->WriteNotice("Your ident has been set to " + ConfEntry->ident + " because you matched " + reason); + them->ChangeIdent(ConfEntry->ident); } - ConfEntry->stats_hits++; - - switch (ConfEntry->banaction) + if (!ConfEntry->host.empty()) { - case DNSBLConfEntry::I_KILL: - { - ServerInstance->Users->QuitUser(them, "Killed (" + reason + ")"); - break; - } - case DNSBLConfEntry::I_MARK: - { - if (!ConfEntry->ident.empty()) - { - them->WriteServ("304 " + them->nick + " :Your ident has been set to " + ConfEntry->ident + " because you matched " + reason); - them->ChangeIdent(ConfEntry->ident.c_str()); - } - - if (!ConfEntry->host.empty()) - { - them->WriteServ("304 " + them->nick + " :Your host has been set to " + ConfEntry->host + " because you matched " + reason); - them->ChangeDisplayedHost(ConfEntry->host.c_str()); - } - - nameExt.set(them, ConfEntry->name); - break; - } - case DNSBLConfEntry::I_KLINE: - { - KLine* kl = new KLine(ServerInstance->Time(), ConfEntry->duration, ServerInstance->Config->ServerName.c_str(), reason.c_str(), - "*", them->GetIPString()); - if (ServerInstance->XLines->AddLine(kl,NULL)) - { - std::string timestr = ServerInstance->TimeString(kl->expiry); - ServerInstance->SNO->WriteGlobalSno('x',"K:line added due to DNSBL match on *@%s to expire on %s: %s", - them->GetIPString(), timestr.c_str(), reason.c_str()); - ServerInstance->XLines->ApplyLines(); - } - else - { - delete kl; - return; - } - break; - } - case DNSBLConfEntry::I_GLINE: - { - GLine* gl = new GLine(ServerInstance->Time(), ConfEntry->duration, ServerInstance->Config->ServerName.c_str(), reason.c_str(), - "*", them->GetIPString()); - if (ServerInstance->XLines->AddLine(gl,NULL)) - { - std::string timestr = ServerInstance->TimeString(gl->expiry); - ServerInstance->SNO->WriteGlobalSno('x',"G:line added due to DNSBL match on *@%s to expire on %s: %s", - them->GetIPString(), timestr.c_str(), reason.c_str()); - ServerInstance->XLines->ApplyLines(); - } - else - { - delete gl; - return; - } - break; - } - case DNSBLConfEntry::I_ZLINE: - { - ZLine* zl = new ZLine(ServerInstance->Time(), ConfEntry->duration, ServerInstance->Config->ServerName.c_str(), reason.c_str(), - them->GetIPString()); - if (ServerInstance->XLines->AddLine(zl,NULL)) - { - std::string timestr = ServerInstance->TimeString(zl->expiry); - ServerInstance->SNO->WriteGlobalSno('x',"Z:line added due to DNSBL match on *@%s to expire on %s: %s", - them->GetIPString(), timestr.c_str(), reason.c_str()); - ServerInstance->XLines->ApplyLines(); - } - else - { - delete zl; - return; - } - break; - } - case DNSBLConfEntry::I_UNKNOWN: - { - break; - } - break; + them->WriteNotice("Your host has been set to " + ConfEntry->host + " because you matched " + reason); + them->ChangeDisplayedHost(ConfEntry->host); } - ServerInstance->SNO->WriteGlobalSno('a', "Connecting user %s%s detected as being on a DNS blacklist (%s) with result %d", them->nick.empty() ? "<unknown>" : "", them->GetFullRealHost().c_str(), ConfEntry->domain.c_str(), (ConfEntry->type==DNSBLConfEntry::A_BITMASK) ? bitmask : record); + nameExt.set(them, ConfEntry->name); + break; } - else - ConfEntry->stats_misses++; - } - else - { - if (!result.empty()) - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL: %s returned address outside of acceptable subnet 127.0.0.0/8: %s", ConfEntry->domain.c_str(), result.c_str()); - ConfEntry->stats_misses++; + case DNSBLConfEntry::I_KLINE: + { + KLine* kl = new KLine(ServerInstance->Time(), ConfEntry->duration, ServerInstance->Config->ServerName.c_str(), reason.c_str(), + "*", them->GetIPString()); + if (ServerInstance->XLines->AddLine(kl,NULL)) + { + std::string timestr = InspIRCd::TimeString(kl->expiry); + ServerInstance->SNO->WriteGlobalSno('x',"K:line added due to DNSBL match on *@%s to expire on %s: %s", + them->GetIPString().c_str(), timestr.c_str(), reason.c_str()); + ServerInstance->XLines->ApplyLines(); + } + else + { + delete kl; + return; + } + break; + } + case DNSBLConfEntry::I_GLINE: + { + GLine* gl = new GLine(ServerInstance->Time(), ConfEntry->duration, ServerInstance->Config->ServerName.c_str(), reason.c_str(), + "*", them->GetIPString()); + if (ServerInstance->XLines->AddLine(gl,NULL)) + { + std::string timestr = InspIRCd::TimeString(gl->expiry); + ServerInstance->SNO->WriteGlobalSno('x',"G:line added due to DNSBL match on *@%s to expire on %s: %s", + them->GetIPString().c_str(), timestr.c_str(), reason.c_str()); + ServerInstance->XLines->ApplyLines(); + } + else + { + delete gl; + return; + } + break; + } + case DNSBLConfEntry::I_ZLINE: + { + ZLine* zl = new ZLine(ServerInstance->Time(), ConfEntry->duration, ServerInstance->Config->ServerName.c_str(), reason.c_str(), + them->GetIPString()); + if (ServerInstance->XLines->AddLine(zl,NULL)) + { + std::string timestr = InspIRCd::TimeString(zl->expiry); + ServerInstance->SNO->WriteGlobalSno('x',"Z:line added due to DNSBL match on %s to expire on %s: %s", + them->GetIPString().c_str(), timestr.c_str(), reason.c_str()); + ServerInstance->XLines->ApplyLines(); + } + else + { + delete zl; + return; + } + break; + } + case DNSBLConfEntry::I_UNKNOWN: + default: + break; } + + ServerInstance->SNO->WriteGlobalSno('d', "Connecting user %s%s detected as being on a DNS blacklist (%s) with result %d", them->nick.empty() ? "<unknown>" : "", them->GetFullRealHost().c_str(), ConfEntry->domain.c_str(), (ConfEntry->type==DNSBLConfEntry::A_BITMASK) ? bitmask : record); } + else + ConfEntry->stats_misses++; } - virtual void OnError(ResolverError e, const std::string &errormessage) + void OnError(const DNS::Query *q) CXX11_OVERRIDE { LocalUser* them = (LocalUser*)ServerInstance->FindUUID(theiruid); - if (them) - { - int i = countExt.get(them); - if (i) - countExt.set(them, i - 1); - } - } + if (!them) + return; - virtual ~DNSBLResolver() - { + int i = countExt.get(them); + if (i) + countExt.set(them, i - 1); + + if (q->error == DNS::ERROR_NO_RECORDS || q->error == DNS::ERROR_DOMAIN_NOT_FOUND) + ConfEntry->stats_misses++; } }; -class ModuleDNSBL : public Module +class ModuleDNSBL : public Module, public Stats::EventListener { std::vector<reference<DNSBLConfEntry> > DNSBLConfEntries; + dynamic_reference<DNS::Manager> DNS; LocalStringExt nameExt; LocalIntExt countExt; @@ -246,25 +247,27 @@ class ModuleDNSBL : public Module return DNSBLConfEntry::I_UNKNOWN; } public: - ModuleDNSBL() : nameExt("dnsbl_match", this), countExt("dnsbl_pending", this) { } + ModuleDNSBL() + : Stats::EventListener(this) + , DNS(this, "DNS") + , nameExt("dnsbl_match", ExtensionItem::EXT_USER, this) + , countExt("dnsbl_pending", ExtensionItem::EXT_USER, this) + { + } - void init() + void init() CXX11_OVERRIDE { - ReadConf(); - ServerInstance->Modules->AddService(nameExt); - ServerInstance->Modules->AddService(countExt); - Implementation eventlist[] = { I_OnRehash, I_OnSetUserIP, I_OnStats, I_OnSetConnectClass, I_OnCheckReady }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + ServerInstance->SNO->EnableSnomask('d', "DNSBL"); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides handling of DNS blacklists", VF_VENDOR); } /** Fill our conf vector with data */ - void ReadConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { DNSBLConfEntries.clear(); @@ -280,10 +283,10 @@ class ModuleDNSBL : public Module e->reason = tag->getString("reason"); e->domain = tag->getString("domain"); - if (tag->getString("type") == "bitmask") + if (stdalgo::string::equalsci(tag->getString("type"), "bitmask")) { e->type = DNSBLConfEntry::A_BITMASK; - e->bitmask = tag->getInt("bitmask"); + e->bitmask = tag->getUInt("bitmask", 0, 0, UINT_MAX); } else { @@ -296,7 +299,7 @@ class ModuleDNSBL : public Module } e->banaction = str2banaction(tag->getString("action")); - e->duration = ServerInstance->Duration(tag->getString("duration", "60")); + e->duration = tag->getDuration("duration", 60, 1); /* Use portparser for record replies */ @@ -304,34 +307,29 @@ class ModuleDNSBL : public Module if ((e->bitmask <= 0) && (DNSBLConfEntry::A_BITMASK == e->type)) { std::string location = tag->getTagLocation(); - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL(%s): invalid bitmask", location.c_str()); + ServerInstance->SNO->WriteGlobalSno('d', "DNSBL(%s): invalid bitmask", location.c_str()); } else if (e->name.empty()) { std::string location = tag->getTagLocation(); - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL(%s): Invalid name", location.c_str()); + ServerInstance->SNO->WriteGlobalSno('d', "DNSBL(%s): Invalid name", location.c_str()); } else if (e->domain.empty()) { std::string location = tag->getTagLocation(); - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL(%s): Invalid domain", location.c_str()); + ServerInstance->SNO->WriteGlobalSno('d', "DNSBL(%s): Invalid domain", location.c_str()); } else if (e->banaction == DNSBLConfEntry::I_UNKNOWN) { std::string location = tag->getTagLocation(); - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL(%s): Invalid banaction", location.c_str()); - } - else if (e->duration <= 0) - { - std::string location = tag->getTagLocation(); - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL(%s): Invalid duration", location.c_str()); + ServerInstance->SNO->WriteGlobalSno('d', "DNSBL(%s): Invalid banaction", location.c_str()); } else { if (e->reason.empty()) { std::string location = tag->getTagLocation(); - ServerInstance->SNO->WriteGlobalSno('a', "DNSBL(%s): empty reason, using defaults", location.c_str()); + ServerInstance->SNO->WriteGlobalSno('d', "DNSBL(%s): empty reason, using defaults", location.c_str()); e->reason = "Your IP has been blacklisted."; } @@ -341,14 +339,13 @@ class ModuleDNSBL : public Module } } - void OnRehash(User* user) + void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE { - ReadConf(); - } + if ((user->exempt) || !DNS) + return; - void OnSetUserIP(LocalUser* user) - { - if ((user->exempt) || (user->client_sa.sa.sa_family != AF_INET)) + // Clients can't be in a DNSBL if they aren't connected via IPv4 or IPv6. + if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6) return; if (user->MyClass) @@ -357,40 +354,61 @@ class ModuleDNSBL : public Module return; } else - ServerInstance->Logs->Log("m_dnsbl", DEBUG, "User has no connect class in OnSetUserIP"); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User has no connect class in OnSetUserIP"); - unsigned char a, b, c, d; - char reversedipbuf[128]; std::string reversedip; + if (user->client_sa.family() == AF_INET) + { + unsigned int a, b, c, d; + d = (unsigned int) (user->client_sa.in4.sin_addr.s_addr >> 24) & 0xFF; + c = (unsigned int) (user->client_sa.in4.sin_addr.s_addr >> 16) & 0xFF; + b = (unsigned int) (user->client_sa.in4.sin_addr.s_addr >> 8) & 0xFF; + a = (unsigned int) user->client_sa.in4.sin_addr.s_addr & 0xFF; - d = (unsigned char) (user->client_sa.in4.sin_addr.s_addr >> 24) & 0xFF; - c = (unsigned char) (user->client_sa.in4.sin_addr.s_addr >> 16) & 0xFF; - b = (unsigned char) (user->client_sa.in4.sin_addr.s_addr >> 8) & 0xFF; - a = (unsigned char) user->client_sa.in4.sin_addr.s_addr & 0xFF; + reversedip = ConvToStr(d) + "." + ConvToStr(c) + "." + ConvToStr(b) + "." + ConvToStr(a); + } + else if (user->client_sa.family() == AF_INET6) + { + const unsigned char* ip = user->client_sa.in6.sin6_addr.s6_addr; - snprintf(reversedipbuf, 128, "%d.%d.%d.%d", d, c, b, a); - reversedip = std::string(reversedipbuf); + std::string buf = BinToHex(ip, 16); + for (std::string::const_reverse_iterator it = buf.rbegin(); it != buf.rend(); ++it) + { + reversedip.push_back(*it); + reversedip.push_back('.'); + } + } + else + return; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Reversed IP %s -> %s", user->GetIPString().c_str(), reversedip.c_str()); countExt.set(user, DNSBLConfEntries.size()); // For each DNSBL, we will run through this lookup - unsigned int i = 0; - while (i < DNSBLConfEntries.size()) + for (unsigned i = 0; i < DNSBLConfEntries.size(); ++i) { // Fill hostname with a dnsbl style host (d.c.b.a.domain.tld) std::string hostname = reversedip + "." + DNSBLConfEntries[i]->domain; /* now we'd need to fire off lookups for `hostname'. */ - bool cached; - DNSBLResolver *r = new DNSBLResolver(this, nameExt, countExt, hostname, user, DNSBLConfEntries[i], cached); - ServerInstance->AddResolver(r, cached); + DNSBLResolver *r = new DNSBLResolver(*this->DNS, this, nameExt, countExt, hostname, user, DNSBLConfEntries[i]); + try + { + this->DNS->Process(r); + } + catch (DNS::Exception &ex) + { + delete r; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, ex.GetReason()); + } + if (user->quitting) break; - i++; } } - ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { std::string dnsbl; if (!myclass->config->readString("dnsbl", dnsbl)) @@ -401,17 +419,17 @@ class ModuleDNSBL : public Module return MOD_RES_PASSTHRU; return MOD_RES_DENY; } - - ModResult OnCheckReady(LocalUser *user) + + ModResult OnCheckReady(LocalUser *user) CXX11_OVERRIDE { if (countExt.get(user)) return MOD_RES_DENY; return MOD_RES_PASSTHRU; } - ModResult OnStats(char symbol, User* user, string_list &results) + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if (symbol != 'd') + if (stats.GetSymbol() != 'd') return MOD_RES_PASSTHRU; unsigned long total_hits = 0, total_misses = 0; @@ -421,12 +439,12 @@ class ModuleDNSBL : public Module total_hits += (*i)->stats_hits; total_misses += (*i)->stats_misses; - results.push_back(ServerInstance->Config->ServerName + " 304 " + user->nick + " :DNSBLSTATS DNSbl \"" + (*i)->name + "\" had " + + stats.AddRow(304, "DNSBLSTATS DNSbl \"" + (*i)->name + "\" had " + ConvToStr((*i)->stats_hits) + " hits and " + ConvToStr((*i)->stats_misses) + " misses"); } - results.push_back(ServerInstance->Config->ServerName + " 304 " + user->nick + " :DNSBLSTATS Total hits: " + ConvToStr(total_hits)); - results.push_back(ServerInstance->Config->ServerName + " 304 " + user->nick + " :DNSBLSTATS Total misses: " + ConvToStr(total_misses)); + stats.AddRow(304, "DNSBLSTATS Total hits: " + ConvToStr(total_hits)); + stats.AddRow(304, "DNSBLSTATS Total misses: " + ConvToStr(total_misses)); return MOD_RES_PASSTHRU; } diff --git a/src/modules/m_exemptchanops.cpp b/src/modules/m_exemptchanops.cpp index 8d6d65af2..b1d7b35df 100644 --- a/src/modules/m_exemptchanops.cpp +++ b/src/modules/m_exemptchanops.cpp @@ -18,9 +18,8 @@ #include "inspircd.h" -#include "u_listmode.h" - -/* $ModDesc: Provides the ability to allow channel operators to be exempt from certain modes. */ +#include "listmode.h" +#include "modules/exemption.h" /** Handles channel mode +X */ @@ -29,123 +28,112 @@ class ExemptChanOps : public ListModeBase public: ExemptChanOps(Module* Creator) : ListModeBase(Creator, "exemptchanops", 'X', "End of channel exemptchanops list", 954, 953, false, "exemptchanops") { } - bool ValidateParam(User* user, Channel* chan, std::string &word) - { - // TODO actually make sure there's a prop for this - if ((word.length() > 35) || (word.empty())) + bool ValidateParam(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { + std::string::size_type p = word.find(':'); + if (p == std::string::npos) + { + user->WriteNumeric(Numerics::InvalidModeParameter(chan, this, word, "Invalid exemptchanops entry, format is <restriction>:<prefix>")); + return false; + } + + std::string restriction(word, 0, p); + // If there is a '-' in the restriction string ignore it and everything after it + // to support "auditorium-vis" and "auditorium-see" in m_auditorium + p = restriction.find('-'); + if (p != std::string::npos) + restriction.erase(p); + + if (!ServerInstance->Modes->FindMode(restriction, MODETYPE_CHANNEL)) { - user->WriteNumeric(955, "%s %s %s :word is too %s for exemptchanops list",user->nick.c_str(), chan->name.c_str(), word.c_str(), (word.empty() ? "short" : "long")); + user->WriteNumeric(Numerics::InvalidModeParameter(chan, this, word, "Unknown restriction")); return false; } return true; } - bool TellListTooLong(User* user, Channel* chan, std::string &word) + void TellListTooLong(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - user->WriteNumeric(959, "%s %s %s :Channel exemptchanops list is full", user->nick.c_str(), chan->name.c_str(), word.c_str()); - return true; + user->WriteNumeric(959, chan->name, word, "Channel exemptchanops list is full"); } - void TellAlreadyOnList(User* user, Channel* chan, std::string &word) + void TellAlreadyOnList(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - user->WriteNumeric(957, "%s %s :The word %s is already on the exemptchanops list",user->nick.c_str(), chan->name.c_str(), word.c_str()); + user->WriteNumeric(957, chan->name, InspIRCd::Format("The word %s is already on the exemptchanops list", word.c_str())); } - void TellNotSet(User* user, Channel* chan, std::string &word) + void TellNotSet(User* user, Channel* chan, std::string& word) CXX11_OVERRIDE { - user->WriteNumeric(958, "%s %s :No such exemptchanops word is set",user->nick.c_str(), chan->name.c_str()); + user->WriteNumeric(958, chan->name, "No such exemptchanops word is set"); } }; -class ExemptHandler : public HandlerBase3<ModResult, User*, Channel*, const std::string&> +class ExemptHandler : public CheckExemption::EventListener { public: ExemptChanOps ec; - ExemptHandler(Module* me) : ec(me) {} - - ModeHandler* FindMode(const std::string& mid) + ExemptHandler(Module* me) + : CheckExemption::EventListener(me) + , ec(me) + { + } + + PrefixMode* FindMode(const std::string& mid) { if (mid.length() == 1) - return ServerInstance->Modes->FindMode(mid[0], MODETYPE_CHANNEL); - for(char c='A'; c <= 'z'; c++) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL); - if (mh && mh->name == mid) - return mh; - } - return NULL; + return ServerInstance->Modes->FindPrefixMode(mid[0]); + + ModeHandler* mh = ServerInstance->Modes->FindMode(mid, MODETYPE_CHANNEL); + return mh ? mh->IsPrefixMode() : NULL; } - ModResult Call(User* user, Channel* chan, const std::string& restriction) + ModResult OnCheckExemption(User* user, Channel* chan, const std::string& restriction) CXX11_OVERRIDE { unsigned int mypfx = chan->GetPrefixValue(user); std::string minmode; - modelist* list = ec.extItem.get(chan); + ListModeBase::ModeList* list = ec.GetList(chan); if (list) { - for (modelist::iterator i = list->begin(); i != list->end(); ++i) + for (ListModeBase::ModeList::iterator i = list->begin(); i != list->end(); ++i) { std::string::size_type pos = (*i).mask.find(':'); if (pos == std::string::npos) continue; - if ((*i).mask.substr(0,pos) == restriction) - minmode = (*i).mask.substr(pos + 1); + if (!i->mask.compare(0, pos, restriction)) + minmode.assign(i->mask, pos + 1, std::string::npos); } } - ModeHandler* mh = FindMode(minmode); + PrefixMode* mh = FindMode(minmode); if (mh && mypfx >= mh->GetPrefixRank()) return MOD_RES_ALLOW; if (mh || minmode == "*") return MOD_RES_DENY; - return ServerInstance->HandleOnCheckExemption.Call(user, chan, restriction); + return MOD_RES_PASSTHRU; } }; class ModuleExemptChanOps : public Module { - std::string defaults; ExemptHandler eh; public: - ModuleExemptChanOps() : eh(this) { } - void init() - { - ServerInstance->Modules->AddService(eh.ec); - Implementation eventlist[] = { I_OnRehash, I_OnSyncChannel }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - ServerInstance->OnCheckExemption = &eh; - - OnRehash(NULL); - } - - ~ModuleExemptChanOps() - { - ServerInstance->OnCheckExemption = &ServerInstance->HandleOnCheckExemption; - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the ability to allow channel operators to be exempt from certain modes.",VF_VENDOR); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { eh.ec.DoRehash(); } - - void OnSyncChannel(Channel* chan, Module* proto, void* opaque) - { - eh.ec.DoSyncChannel(chan, proto, opaque); - } }; MODULE_INIT(ModuleExemptChanOps) diff --git a/src/modules/m_filter.cpp b/src/modules/m_filter.cpp index 4090f5600..2d0657c99 100644 --- a/src/modules/m_filter.cpp +++ b/src/modules/m_filter.cpp @@ -22,11 +22,10 @@ #include "inspircd.h" #include "xline.h" -#include "m_regex.h" - -/* $ModDesc: Text (spam) filtering */ - -class ModuleFilter; +#include "modules/regex.h" +#include "modules/server.h" +#include "modules/shun.h" +#include "modules/stats.h" enum FilterFlags { @@ -42,16 +41,18 @@ enum FilterAction FA_BLOCK, FA_SILENT, FA_KILL, + FA_SHUN, FA_NONE }; class FilterResult { public: + Regex* regex; std::string freeform; std::string reason; FilterAction action; - long gline_time; + long duration; bool flag_no_opers; bool flag_part_message; @@ -60,9 +61,15 @@ class FilterResult bool flag_notice; bool flag_strip_color; - FilterResult(const std::string& free, const std::string& rea, FilterAction act, long gt, const std::string& fla) : - freeform(free), reason(rea), action(act), gline_time(gt) + FilterResult(dynamic_reference<RegexFactory>& RegexEngine, const std::string& free, const std::string& rea, FilterAction act, long gt, const std::string& fla) + : freeform(free) + , reason(rea) + , action(act) + , duration(gt) { + if (!RegexEngine) + throw ModuleException("Regex module implementing '"+RegexEngine.GetProvider()+"' is not loaded!"); + regex = RegexEngine->Create(free); this->FillFlags(fla); } @@ -144,28 +151,22 @@ class CommandFilter : public Command : Command(f, "FILTER", 1, 5) { flags_needed = 'o'; - this->syntax = "<filter-definition> <action> <flags> [<gline-duration>] :<reason>"; + this->syntax = "<filter-definition> <action> <flags> [<duration>] :<reason>"; } - CmdResult Handle(const std::vector<std::string>&, User*); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } }; -class ImplFilter : public FilterResult +class ModuleFilter : public Module, public ServerEventListener, public Stats::EventListener { - public: - Regex* regex; - - ImplFilter(ModuleFilter* mymodule, const std::string &rea, FilterAction act, long glinetime, const std::string &pat, const std::string &flgs); -}; + typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet; - -class ModuleFilter : public Module -{ bool initing; + bool notifyuser; RegexFactory* factory; void FreeFilters(); @@ -173,35 +174,38 @@ class ModuleFilter : public Module CommandFilter filtcommand; dynamic_reference<RegexFactory> RegexEngine; - std::vector<ImplFilter> filters; + std::vector<FilterResult> filters; int flags; - std::set<std::string> exemptfromfilter; // List of channel names excluded from filtering. + // List of channel names excluded from filtering. + ExemptTargetSet exemptedchans; + + // List of target nicknames excluded from filtering. + ExemptTargetSet exemptednicks; ModuleFilter(); - void init(); - CullResult cull(); - ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list); + void init() CXX11_OVERRIDE; + CullResult cull() CXX11_OVERRIDE; + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE; FilterResult* FilterMatch(User* user, const std::string &text, int flags); bool DeleteFilter(const std::string &freeform); std::pair<bool, std::string> AddFilter(const std::string &freeform, FilterAction type, const std::string &reason, long duration, const std::string &flags); - ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list); - void OnRehash(User* user); - Version GetVersion(); + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE; + Version GetVersion() CXX11_OVERRIDE; std::string EncodeFilter(FilterResult* filter); FilterResult DecodeFilter(const std::string &data); - void OnSyncNetwork(Module* proto, void* opaque); - void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata); - ModResult OnStats(char symbol, User* user, string_list &results); - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line); - void OnUnloadModule(Module* mod); + void OnSyncNetwork(ProtocolInterface::Server& server) CXX11_OVERRIDE; + void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata) CXX11_OVERRIDE; + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE; + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE; + void OnUnloadModule(Module* mod) CXX11_OVERRIDE; bool AppliesToMe(User* user, FilterResult* filter, int flags); void ReadFilters(); static bool StringToFilterAction(const std::string& str, FilterAction& fa); static std::string FilterActionToString(FilterAction fa); }; -CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User *user) +CmdResult CommandFilter::Handle(User* user, const Params& parameters) { if (parameters.size() == 1) { @@ -209,13 +213,13 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User Module *me = creator; if (static_cast<ModuleFilter *>(me)->DeleteFilter(parameters[0])) { - user->WriteServ("NOTICE %s :*** Removed filter '%s'", user->nick.c_str(), parameters[0].c_str()); - ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "FILTER: "+user->nick+" removed filter '"+parameters[0]+"'"); + user->WriteNotice("*** Removed filter '" + parameters[0] + "'"); + ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'f' : 'F', "FILTER: "+user->nick+" removed filter '"+parameters[0]+"'"); return CMD_SUCCESS; } else { - user->WriteServ("NOTICE %s :*** Filter '%s' not found in list, try /stats s.", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** Filter '" + parameters[0] + "' not found in list, try /stats s."); return CMD_FAILURE; } } @@ -232,20 +236,23 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User if (!ModuleFilter::StringToFilterAction(parameters[1], type)) { - user->WriteServ("NOTICE %s :*** Invalid filter type '%s'. Supported types are 'gline', 'none', 'block', 'silent' and 'kill'.", user->nick.c_str(), parameters[1].c_str()); + if (ServerInstance->XLines->GetFactory("SHUN")) + user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent', 'kill', and 'shun'."); + else + user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent', and 'kill'."); return CMD_FAILURE; } - if (type == FA_GLINE) + if (type == FA_GLINE || type == FA_SHUN) { if (parameters.size() >= 5) { - duration = ServerInstance->Duration(parameters[3]); + duration = InspIRCd::Duration(parameters[3]); reasonindex = 4; } else { - user->WriteServ("NOTICE %s :*** Not enough parameters: When setting a gline type filter, a gline duration must be specified as the third parameter.", user->nick.c_str()); + user->WriteNotice("*** Not enough parameters: When setting a gline or shun type filter, a duration must be specified as the third parameter."); return CMD_FAILURE; } } @@ -258,23 +265,23 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User std::pair<bool, std::string> result = static_cast<ModuleFilter *>(me)->AddFilter(freeform, type, parameters[reasonindex], duration, flags); if (result.first) { - user->WriteServ("NOTICE %s :*** Added filter '%s', type '%s'%s%s, flags '%s', reason: '%s'", user->nick.c_str(), freeform.c_str(), - parameters[1].c_str(), (duration ? ", duration " : ""), (duration ? parameters[3].c_str() : ""), - flags.c_str(), parameters[reasonindex].c_str()); + user->WriteNotice("*** Added filter '" + freeform + "', type '" + parameters[1] + "'" + + (duration ? ", duration " + parameters[3] : "") + ", flags '" + flags + "', reason: '" + + parameters[reasonindex] + "'"); - ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "FILTER: "+user->nick+" added filter '"+freeform+"', type '"+parameters[1]+"', "+(duration ? "duration "+parameters[3]+", " : "")+"flags '"+flags+"', reason: "+parameters[reasonindex]); + ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'f' : 'F', "FILTER: "+user->nick+" added filter '"+freeform+"', type '"+parameters[1]+"', "+(duration ? "duration "+parameters[3]+", " : "")+"flags '"+flags+"', reason: "+parameters[reasonindex]); return CMD_SUCCESS; } else { - user->WriteServ("NOTICE %s :*** Filter '%s' could not be added: %s", user->nick.c_str(), freeform.c_str(), result.second.c_str()); + user->WriteNotice("*** Filter '" + freeform + "' could not be added: " + result.second); return CMD_FAILURE; } } else { - user->WriteServ("NOTICE %s :*** Not enough parameters.", user->nick.c_str()); + user->WriteNotice("*** Not enough parameters."); return CMD_FAILURE; } @@ -283,7 +290,7 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags) { - if ((filter->flag_no_opers) && IS_OPER(user)) + if ((filter->flag_no_opers) && user->IsOper()) return false; if ((iflags & FLAG_PRIVMSG) && (!filter->flag_privmsg)) return false; @@ -297,16 +304,17 @@ bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags) } ModuleFilter::ModuleFilter() - : initing(true), filtcommand(this), RegexEngine(this, "regex") + : ServerEventListener(this) + , Stats::EventListener(this) + , initing(true) + , filtcommand(this) + , RegexEngine(this, "regex") { } void ModuleFilter::init() { - ServerInstance->Modules->AddService(filtcommand); - Implementation eventlist[] = { I_OnPreCommand, I_OnStats, I_OnSyncNetwork, I_OnDecodeMetaData, I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash, I_OnUnloadModule }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); + ServerInstance->SNO->EnableSnomask('f', "FILTER"); } CullResult ModuleFilter::cull() @@ -317,69 +325,90 @@ CullResult ModuleFilter::cull() void ModuleFilter::FreeFilters() { - for (std::vector<ImplFilter>::const_iterator i = filters.begin(); i != filters.end(); ++i) + for (std::vector<FilterResult>::const_iterator i = filters.begin(); i != filters.end(); ++i) delete i->regex; filters.clear(); } -ModResult ModuleFilter::OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) +ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtarget, MessageDetails& details) { + // Leave remote users and servers alone if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; - flags = FLAG_PRIVMSG; - return OnUserPreNotice(user,dest,target_type,text,status,exempt_list); -} - -ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) -{ - /* Leave ulines alone */ - if ((ServerInstance->ULine(user->server)) || (!IS_LOCAL(user))) - return MOD_RES_PASSTHRU; - - if (!flags) - flags = FLAG_NOTICE; + flags = (details.type == MSG_PRIVMSG) ? FLAG_PRIVMSG : FLAG_NOTICE; - FilterResult* f = this->FilterMatch(user, text, flags); + FilterResult* f = this->FilterMatch(user, details.text, flags); if (f) { std::string target; - if (target_type == TYPE_USER) + if (msgtarget.type == MessageTarget::TYPE_USER) { - User* t = (User*)dest; + User* t = msgtarget.Get<User>(); + // Check if the target nick is exempted, if yes, ignore this message + if (exemptednicks.count(t->nick)) + return MOD_RES_PASSTHRU; + target = t->nick; } - else if (target_type == TYPE_CHANNEL) + else if (msgtarget.type == MessageTarget::TYPE_CHANNEL) { - Channel* t = (Channel*)dest; - if (exemptfromfilter.find(t->name) != exemptfromfilter.end()) + Channel* t = msgtarget.Get<Channel>(); + if (exemptedchans.count(t->name)) return MOD_RES_PASSTHRU; target = t->name; } if (f->action == FA_BLOCK) { - ServerInstance->SNO->WriteGlobalSno('a', "FILTER: "+user->nick+" had their message filtered, target was "+target+": "+f->reason); - if (target_type == TYPE_CHANNEL) - user->WriteNumeric(404, "%s %s :Message to channel blocked and opers notified (%s)",user->nick.c_str(), target.c_str(), f->reason.c_str()); + ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s had their message to %s filtered as it matched %s (%s)", + user->nick.c_str(), target.c_str(), f->freeform.c_str(), f->reason.c_str())); + if (notifyuser) + { + if (msgtarget.type == MessageTarget::TYPE_CHANNEL) + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked and opers notified (%s)", f->reason.c_str())); + else + user->WriteNotice("Your message to "+target+" was blocked and opers notified: "+f->reason); + } else - user->WriteServ("NOTICE "+user->nick+" :Your message to "+target+" was blocked and opers notified: "+f->reason); + details.echo_original = true; } else if (f->action == FA_SILENT) { - if (target_type == TYPE_CHANNEL) - user->WriteNumeric(404, "%s %s :Message to channel blocked (%s)",user->nick.c_str(), target.c_str(), f->reason.c_str()); + if (notifyuser) + { + if (msgtarget.type == MessageTarget::TYPE_CHANNEL) + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked (%s)", f->reason.c_str())); + else + user->WriteNotice("Your message to "+target+" was blocked: "+f->reason); + } else - user->WriteServ("NOTICE "+user->nick+" :Your message to "+target+" was blocked: "+f->reason); + details.echo_original = true; } else if (f->action == FA_KILL) { + ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was killed because their message to %s matched %s (%s)", + user->nick.c_str(), target.c_str(), f->freeform.c_str(), f->reason.c_str())); ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason); } + else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN"))) + { + Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString()); + ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was shunned because their message to %s matched %s (%s)", + user->nick.c_str(), target.c_str(), f->freeform.c_str(), f->reason.c_str())); + if (ServerInstance->XLines->AddLine(sh, NULL)) + { + ServerInstance->XLines->ApplyLines(); + } + else + delete sh; + } else if (f->action == FA_GLINE) { - GLine* gl = new GLine(ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString()); + GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString()); + ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was glined because their message to %s matched %s (%s)", + user->nick.c_str(), target.c_str(), f->freeform.c_str(), f->reason.c_str())); if (ServerInstance->XLines->AddLine(gl,NULL)) { ServerInstance->XLines->ApplyLines(); @@ -388,15 +417,15 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s delete gl; } - ServerInstance->Logs->Log("FILTER",DEFAULT,"FILTER: "+ user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action)); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action)); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } -ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) +ModResult ModuleFilter::OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) { - if (validated && IS_LOCAL(user)) + if (validated) { flags = 0; bool parting; @@ -416,7 +445,7 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri if (parameters.size() < 2) return MOD_RES_PASSTHRU; - if (exemptfromfilter.find(parameters[0]) != exemptfromfilter.end()) + if (exemptedchans.count(parameters[0])) return MOD_RES_PASSTHRU; parting = true; @@ -446,13 +475,16 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri /* Are they parting, if so, kill is applicable */ if ((parting) && (f->action == FA_KILL)) { - user->WriteServ("NOTICE %s :*** Your PART message was filtered: %s", user->nick.c_str(), f->reason.c_str()); + user->WriteNotice("*** Your PART message was filtered: " + f->reason); ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason); } if (f->action == FA_GLINE) { /* Note: We gline *@IP so that if their host doesnt resolve the gline still applies. */ - GLine* gl = new GLine(ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString()); + GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString()); + ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was glined because their %s message matched %s (%s)", + user->nick.c_str(), command.c_str(), f->freeform.c_str(), f->reason.c_str())); + if (ServerInstance->XLines->AddLine(gl,NULL)) { ServerInstance->XLines->ApplyLines(); @@ -460,24 +492,49 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri else delete gl; } + else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN"))) + { + /* Note: We shun *!*@IP so that if their host doesnt resolve the shun still applies. */ + Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString()); + ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was shunned because their %s message matched %s (%s)", + user->nick.c_str(), command.c_str(), f->freeform.c_str(), f->reason.c_str())); + if (ServerInstance->XLines->AddLine(sh, NULL)) + { + ServerInstance->XLines->ApplyLines(); + } + else + delete sh; + } return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } -void ModuleFilter::OnRehash(User* user) +void ModuleFilter::ReadConfig(ConfigStatus& status) { ConfigTagList tags = ServerInstance->Config->ConfTags("exemptfromfilter"); - exemptfromfilter.clear(); + exemptedchans.clear(); + exemptednicks.clear(); + for (ConfigIter i = tags.first; i != tags.second; ++i) { - std::string chan = i->second->getString("channel"); - if (!chan.empty()) - exemptfromfilter.insert(chan); + ConfigTag* tag = i->second; + + // If "target" is not found, try the old "channel" key to keep compatibility with 2.0 configs + const std::string target = tag->getString("target", tag->getString("channel")); + if (!target.empty()) + { + if (target[0] == '#') + exemptedchans.insert(target); + else + exemptednicks.insert(target); + } } - std::string newrxengine = ServerInstance->Config->ConfValue("filteropts")->getString("engine"); + ConfigTag* tag = ServerInstance->Config->ConfValue("filteropts"); + std::string newrxengine = tag->getString("engine"); + notifyuser = tag->getBool("notifyuser", true); factory = RegexEngine ? (RegexEngine.operator->()) : NULL; @@ -489,9 +546,9 @@ void ModuleFilter::OnRehash(User* user) if (!RegexEngine) { if (newrxengine.empty()) - ServerInstance->SNO->WriteGlobalSno('a', "WARNING: No regex engine loaded - Filter functionality disabled until this is corrected."); + ServerInstance->SNO->WriteGlobalSno('f', "WARNING: No regex engine loaded - Filter functionality disabled until this is corrected."); else - ServerInstance->SNO->WriteGlobalSno('a', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", newrxengine.c_str()); + ServerInstance->SNO->WriteGlobalSno('f', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", newrxengine.c_str()); initing = false; FreeFilters(); @@ -500,7 +557,7 @@ void ModuleFilter::OnRehash(User* user) if ((!initing) && (RegexEngine.operator->() != factory)) { - ServerInstance->SNO->WriteGlobalSno('a', "Dumping all filters due to regex engine change"); + ServerInstance->SNO->WriteGlobalSno('f', "Dumping all filters due to regex engine change"); FreeFilters(); } @@ -523,7 +580,7 @@ std::string ModuleFilter::EncodeFilter(FilterResult* filter) if (*n == ' ') *n = '\7'; - stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->gline_time << " :" << filter->reason; + stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->duration << " :" << filter->reason; return stream.str(); } @@ -532,19 +589,22 @@ FilterResult ModuleFilter::DecodeFilter(const std::string &data) std::string filteraction; FilterResult res; irc::tokenstream tokens(data); - tokens.GetToken(res.freeform); - tokens.GetToken(filteraction); + tokens.GetMiddle(res.freeform); + tokens.GetMiddle(filteraction); if (!StringToFilterAction(filteraction, res.action)) throw ModuleException("Invalid action: " + filteraction); std::string filterflags; - tokens.GetToken(filterflags); + tokens.GetMiddle(filterflags); char c = res.FillFlags(filterflags); if (c != 0) throw ModuleException("Invalid flag: '" + std::string(1, c) + "'"); - tokens.GetToken(res.gline_time); - tokens.GetToken(res.reason); + std::string duration; + tokens.GetMiddle(duration); + res.duration = ConvToInt(duration); + + tokens.GetTrailing(res.reason); /* Hax to allow spaces in the freeform without changing the design of the irc protocol */ for (std::string::iterator n = res.freeform.begin(); n != res.freeform.end(); n++) @@ -554,11 +614,11 @@ FilterResult ModuleFilter::DecodeFilter(const std::string &data) return res; } -void ModuleFilter::OnSyncNetwork(Module* proto, void* opaque) +void ModuleFilter::OnSyncNetwork(ProtocolInterface::Server& server) { - for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); ++i) + for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i) { - proto->ProtoSendMetaData(opaque, NULL, "filter", EncodeFilter(&(*i))); + server.SendMetaData("filter", EncodeFilter(&(*i))); } } @@ -569,31 +629,23 @@ void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extna try { FilterResult data = DecodeFilter(extdata); - this->AddFilter(data.freeform, data.action, data.reason, data.gline_time, data.GetFlags()); + this->AddFilter(data.freeform, data.action, data.reason, data.duration, data.GetFlags()); } catch (ModuleException& e) { - ServerInstance->Logs->Log("m_filter", DEBUG, "Error when unserializing filter: " + std::string(e.GetReason())); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error when unserializing filter: " + e.GetReason()); } } } -ImplFilter::ImplFilter(ModuleFilter* mymodule, const std::string &rea, FilterAction act, long glinetime, const std::string &pat, const std::string &flgs) - : FilterResult(pat, rea, act, glinetime, flgs) -{ - if (!mymodule->RegexEngine) - throw ModuleException("Regex module implementing '"+mymodule->RegexEngine.GetProvider()+"' is not loaded!"); - regex = mymodule->RegexEngine->Create(pat); -} - FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int flgs) { static std::string stripped_text; stripped_text.clear(); - for (std::vector<ImplFilter>::iterator index = filters.begin(); index != filters.end(); index++) + for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i) { - FilterResult* filter = dynamic_cast<FilterResult*>(&(*index)); + FilterResult* filter = &*i; /* Skip ones that dont apply to us */ if (!AppliesToMe(user, filter, flgs)) @@ -605,20 +657,15 @@ FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int InspIRCd::StripColor(stripped_text); } - //ServerInstance->Logs->Log("m_filter", DEBUG, "Match '%s' against '%s'", text.c_str(), index->freeform.c_str()); - if (index->regex->Matches(filter->flag_strip_color ? stripped_text : text)) - { - //ServerInstance->Logs->Log("m_filter", DEBUG, "MATCH"); - return &*index; - } - //ServerInstance->Logs->Log("m_filter", DEBUG, "NO MATCH"); + if (filter->regex->Matches(filter->flag_strip_color ? stripped_text : text)) + return filter; } return NULL; } bool ModuleFilter::DeleteFilter(const std::string &freeform) { - for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++) + for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++) { if (i->freeform == freeform) { @@ -632,7 +679,7 @@ bool ModuleFilter::DeleteFilter(const std::string &freeform) std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform, FilterAction type, const std::string &reason, long duration, const std::string &flgs) { - for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++) + for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++) { if (i->freeform == freeform) { @@ -642,11 +689,11 @@ std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform try { - filters.push_back(ImplFilter(this, reason, type, duration, freeform, flgs)); + filters.push_back(FilterResult(RegexEngine, freeform, reason, type, duration, flgs)); } catch (ModuleException &e) { - ServerInstance->Logs->Log("m_filter", DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason().c_str()); return std::make_pair(false, e.GetReason()); } return std::make_pair(true, ""); @@ -654,17 +701,17 @@ std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform bool ModuleFilter::StringToFilterAction(const std::string& str, FilterAction& fa) { - irc::string s(str.c_str()); - - if (s == "gline") + if (stdalgo::string::equalsci(str, "gline")) fa = FA_GLINE; - else if (s == "block") + else if (stdalgo::string::equalsci(str, "block")) fa = FA_BLOCK; - else if (s == "silent") + else if (stdalgo::string::equalsci(str, "silent")) fa = FA_SILENT; - else if (s == "kill") + else if (stdalgo::string::equalsci(str, "kill")) fa = FA_KILL; - else if (s == "none") + else if (stdalgo::string::equalsci(str, "shun") && (ServerInstance->XLines->GetFactory("SHUN"))) + fa = FA_SHUN; + else if (stdalgo::string::equalsci(str, "none")) fa = FA_NONE; else return false; @@ -680,6 +727,7 @@ std::string ModuleFilter::FilterActionToString(FilterAction fa) case FA_BLOCK: return "block"; case FA_SILENT: return "silent"; case FA_KILL: return "kill"; + case FA_SHUN: return "shun"; default: return "none"; } } @@ -695,7 +743,7 @@ void ModuleFilter::ReadFilters() std::string reason = i->second->getString("reason"); std::string action = i->second->getString("action"); std::string flgs = i->second->getString("flags"); - long gline_time = ServerInstance->Duration(i->second->getString("duration")); + unsigned long duration = i->second->getDuration("duration", 10*60, 1); if (flgs.empty()) flgs = "*"; @@ -705,27 +753,31 @@ void ModuleFilter::ReadFilters() try { - filters.push_back(ImplFilter(this, reason, fa, gline_time, pattern, flgs)); - ServerInstance->Logs->Log("m_filter", DEFAULT, "Regular expression %s loaded.", pattern.c_str()); + filters.push_back(FilterResult(RegexEngine, pattern, reason, fa, duration, flgs)); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Regular expression %s loaded.", pattern.c_str()); } catch (ModuleException &e) { - ServerInstance->Logs->Log("m_filter", DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason().c_str()); } } } -ModResult ModuleFilter::OnStats(char symbol, User* user, string_list &results) +ModResult ModuleFilter::OnStats(Stats::Context& stats) { - if (symbol == 's') + if (stats.GetSymbol() == 's') { - for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++) + for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++) + { + stats.AddRow(223, RegexEngine.GetProvider()+":"+i->freeform+" "+i->GetFlags()+" "+FilterActionToString(i->action)+" "+ConvToStr(i->duration)+" :"+i->reason); + } + for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i) { - results.push_back(ServerInstance->Config->ServerName+" 223 "+user->nick+" :"+RegexEngine.GetProvider()+":"+i->freeform+" "+i->GetFlags()+" "+FilterActionToString(i->action)+" "+ConvToStr(i->gline_time)+" :"+i->reason); + stats.AddRow(223, "EXEMPT "+(*i)); } - for (std::set<std::string>::iterator i = exemptfromfilter.begin(); i != exemptfromfilter.end(); ++i) + for (ExemptTargetSet::const_iterator i = exemptednicks.begin(); i != exemptednicks.end(); ++i) { - results.push_back(ServerInstance->Config->ServerName+" 223 "+user->nick+" :EXEMPT "+(*i)); + stats.AddRow(223, "EXEMPT "+(*i)); } } return MOD_RES_PASSTHRU; diff --git a/src/modules/m_flashpolicyd.cpp b/src/modules/m_flashpolicyd.cpp new file mode 100644 index 000000000..d7f9a793b --- /dev/null +++ b/src/modules/m_flashpolicyd.cpp @@ -0,0 +1,165 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +class FlashPDSocket; + +namespace +{ + insp::intrusive_list<FlashPDSocket> sockets; + std::string policy_reply; + const std::string expected_request("<policy-file-request/>\0", 23); +} + +class FlashPDSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<FlashPDSocket> +{ + /** True if this object is in the cull list + */ + bool waitingcull; + + bool Tick(time_t currtime) CXX11_OVERRIDE + { + AddToCull(); + return false; + } + + public: + FlashPDSocket(int newfd, unsigned int timeoutsec) + : BufferedSocket(newfd) + , Timer(timeoutsec) + , waitingcull(false) + { + ServerInstance->Timers.AddTimer(this); + } + + ~FlashPDSocket() + { + sockets.erase(this); + } + + void OnError(BufferedSocketError) CXX11_OVERRIDE + { + AddToCull(); + } + + void OnDataReady() CXX11_OVERRIDE + { + if (recvq == expected_request) + WriteData(policy_reply); + AddToCull(); + } + + void AddToCull() + { + if (waitingcull) + return; + + waitingcull = true; + Close(); + ServerInstance->GlobalCulls.AddItem(this); + } +}; + +class ModuleFlashPD : public Module +{ + unsigned int timeout; + + public: + ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE + { + if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "flashpolicyd")) + return MOD_RES_PASSTHRU; + + if (policy_reply.empty()) + return MOD_RES_DENY; + + sockets.push_front(new FlashPDSocket(nfd, timeout)); + return MOD_RES_ALLOW; + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("flashpolicyd"); + timeout = tag->getDuration("timeout", 5, 1); + std::string file = tag->getString("file"); + + if (!file.empty()) + { + try + { + FileReader reader(file); + policy_reply = reader.GetString(); + } + catch (CoreException&) + { + const std::string error_message = "A file was specified for FlashPD, but it could not be loaded."; + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, error_message); + ServerInstance->SNO->WriteGlobalSno('a', error_message); + policy_reply.clear(); + } + return; + } + + // A file was not specified. Set the default setting. + // We allow access to all client ports by default + std::string to_ports; + for (std::vector<ListenSocket*>::const_iterator i = ServerInstance->ports.begin(); i != ServerInstance->ports.end(); ++i) + { + ListenSocket* ls = *i; + if (!stdalgo::string::equalsci(ls->bind_tag->getString("type", "clients"), "clients") || !ls->bind_tag->getString("ssl").empty()) + continue; + + to_ports.append(ConvToStr(ls->bind_sa.port())).push_back(','); + } + + if (to_ports.empty()) + { + policy_reply.clear(); + return; + } + + to_ports.erase(to_ports.size() - 1); + + policy_reply = +"<?xml version=\"1.0\"?>\ +<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\ +<cross-domain-policy>\ +<site-control permitted-cross-domain-policies=\"master-only\"/>\ +<allow-access-from domain=\"*\" to-ports=\"" + to_ports + "\" />\ +</cross-domain-policy>"; + } + + CullResult cull() CXX11_OVERRIDE + { + for (insp::intrusive_list<FlashPDSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i) + { + FlashPDSocket* sock = *i; + sock->AddToCull(); + } + return Module::cull(); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Flash Policy Daemon. Allows Flash IRC clients to connect", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleFlashPD) diff --git a/src/modules/m_gecosban.cpp b/src/modules/m_gecosban.cpp index 1497c1b87..ae399a81f 100644 --- a/src/modules/m_gecosban.cpp +++ b/src/modules/m_gecosban.cpp @@ -19,39 +19,27 @@ #include "inspircd.h" -/* $ModDesc: Implements extban +b r: - realname (gecos) bans */ - class ModuleGecosBan : public Module { public: - void init() - { - Implementation eventlist[] = { I_OnCheckBan, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ~ModuleGecosBan() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Extban 'r' - realname (gecos) ban", VF_OPTCOMMON|VF_VENDOR); + return Version("Extban 'r' - real name ban", VF_OPTCOMMON|VF_VENDOR); } - ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) + ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) CXX11_OVERRIDE { if ((mask.length() > 2) && (mask[0] == 'r') && (mask[1] == ':')) { - if (InspIRCd::Match(user->fullname, mask.substr(2))) + if (InspIRCd::Match(user->GetRealName(), mask.substr(2))) return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('r'); + tokens["EXTBAN"].push_back('r'); } }; diff --git a/src/modules/m_globalload.cpp b/src/modules/m_globalload.cpp index aed65045f..c0ce025bd 100644 --- a/src/modules/m_globalload.cpp +++ b/src/modules/m_globalload.cpp @@ -22,8 +22,6 @@ */ -/* $ModDesc: Allows global loading of a module. */ - #include "inspircd.h" /** Handle /GLOADMODULE @@ -35,10 +33,9 @@ class CommandGloadmodule : public Command { flags_needed = 'o'; syntax = "<modulename> [servermask]"; - TRANSLATE3(TR_TEXT, TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { std::string servername = parameters.size() > 1 ? parameters[1] : "*"; @@ -47,11 +44,11 @@ class CommandGloadmodule : public Command if (ServerInstance->Modules->Load(parameters[0].c_str())) { ServerInstance->SNO->WriteToSnoMask('a', "NEW MODULE '%s' GLOBALLY LOADED BY '%s'",parameters[0].c_str(), user->nick.c_str()); - user->WriteNumeric(975, "%s %s :Module successfully loaded.",user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "Module successfully loaded."); } else { - user->WriteNumeric(974, "%s %s :%s",user->nick.c_str(), parameters[0].c_str(), ServerInstance->Modules->LastError().c_str()); + user->WriteNumeric(ERR_CANTLOADMODULE, parameters[0], ServerInstance->Modules->LastError()); } } else @@ -60,7 +57,7 @@ class CommandGloadmodule : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } @@ -77,8 +74,15 @@ class CommandGunloadmodule : public Command syntax = "<modulename> [servermask]"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { + if (!ServerInstance->Config->ConfValue("security")->getBool("allowcoreunload") && + InspIRCd::Match(parameters[0], "core_*.so", ascii_case_insensitive_map)) + { + user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], "You cannot unload core commands!"); + return CMD_FAILURE; + } + std::string servername = parameters.size() > 1 ? parameters[1] : "*"; if (InspIRCd::Match(ServerInstance->Config->ServerName.c_str(), servername)) @@ -89,16 +93,15 @@ class CommandGunloadmodule : public Command if (ServerInstance->Modules->Unload(m)) { ServerInstance->SNO->WriteToSnoMask('a', "MODULE '%s' GLOBALLY UNLOADED BY '%s'",parameters[0].c_str(), user->nick.c_str()); - user->SendText(":%s 973 %s %s :Module successfully unloaded.", - ServerInstance->Config->ServerName.c_str(), user->nick.c_str(), parameters[0].c_str()); + user->WriteRemoteNumeric(RPL_UNLOADEDMODULE, parameters[0], "Module successfully unloaded."); } else { - user->WriteNumeric(972, "%s %s :%s",user->nick.c_str(), parameters[0].c_str(), ServerInstance->Modules->LastError().c_str()); + user->WriteNumeric(ERR_CANTUNLOADMODULE, parameters[0], ServerInstance->Modules->LastError()); } } else - user->SendText(":%s 972 %s %s :No such module", ServerInstance->Config->ServerName.c_str(), user->nick.c_str(), parameters[0].c_str()); + user->WriteRemoteNumeric(ERR_CANTUNLOADMODULE, parameters[0], "No such module"); } else ServerInstance->SNO->WriteToSnoMask('a', "MODULE '%s' GLOBAL UNLOAD BY '%s' (not unloaded here)",parameters[0].c_str(), user->nick.c_str()); @@ -106,31 +109,12 @@ class CommandGunloadmodule : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } }; -class GReloadModuleWorker : public HandlerBase1<void, bool> -{ - public: - const std::string nick; - const std::string name; - const std::string uid; - GReloadModuleWorker(const std::string& usernick, const std::string& uuid, const std::string& modn) - : nick(usernick), name(modn), uid(uuid) {} - void Call(bool result) - { - ServerInstance->SNO->WriteToSnoMask('a', "MODULE '%s' GLOBALLY RELOADED BY '%s'%s", name.c_str(), nick.c_str(), result ? "" : " (failed here)"); - User* user = ServerInstance->FindNick(uid); - if (user) - user->WriteNumeric(975, "%s %s :Module %ssuccessfully reloaded.", - user->nick.c_str(), name.c_str(), result ? "" : "un"); - ServerInstance->GlobalCulls.AddItem(this); - } -}; - /** Handle /GRELOADMODULE */ class CommandGreloadmodule : public Command @@ -141,7 +125,7 @@ class CommandGreloadmodule : public Command flags_needed = 'o'; syntax = "<modulename> [servermask]"; } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { std::string servername = parameters.size() > 1 ? parameters[1] : "*"; @@ -150,14 +134,12 @@ class CommandGreloadmodule : public Command Module* m = ServerInstance->Modules->Find(parameters[0]); if (m) { - GReloadModuleWorker* worker = NULL; - if ((m != creator) && (!creator->dying)) - worker = new GReloadModuleWorker(user->nick, user->uuid, parameters[0]); - ServerInstance->Modules->Reload(m, worker); + ServerInstance->SNO->WriteToSnoMask('a', "MODULE '%s' GLOBALLY RELOADED BY '%s'", parameters[0].c_str(), user->nick.c_str()); + ServerInstance->Parser.CallHandler("RELOADMODULE", parameters, user); } else { - user->WriteNumeric(975, "%s %s :Could not find module by that name", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(RPL_LOADEDMODULE, parameters[0], "Could not find module by that name"); return CMD_FAILURE; } } @@ -167,7 +149,7 @@ class CommandGreloadmodule : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } @@ -185,22 +167,10 @@ class ModuleGlobalLoad : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd1); - ServerInstance->Modules->AddService(cmd2); - ServerInstance->Modules->AddService(cmd3); - } - - ~ModuleGlobalLoad() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows global loading of a module.", VF_COMMON | VF_VENDOR); } }; MODULE_INIT(ModuleGlobalLoad) - diff --git a/src/modules/m_globops.cpp b/src/modules/m_globops.cpp index 85d84252b..b98adce35 100644 --- a/src/modules/m_globops.cpp +++ b/src/modules/m_globops.cpp @@ -23,8 +23,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for GLOBOPS and snomask +g */ - /** Handle /GLOBOPS */ class CommandGlobops : public Command @@ -33,10 +31,9 @@ class CommandGlobops : public Command CommandGlobops(Module* Creator) : Command(Creator,"GLOBOPS", 1,1) { flags_needed = 'o'; syntax = "<any-text>"; - TRANSLATE2(TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { ServerInstance->SNO->WriteGlobalSno('g', "From " + user->nick + ": " + parameters[0]); return CMD_SUCCESS; @@ -49,17 +46,15 @@ class ModuleGlobops : public Module public: ModuleGlobops() : cmd(this) {} - void init() + void init() CXX11_OVERRIDE { - ServerInstance->Modules->AddService(cmd); ServerInstance->SNO->EnableSnomask('g',"GLOBOPS"); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for GLOBOPS and snomask +g", VF_VENDOR); } - }; MODULE_INIT(ModuleGlobops) diff --git a/src/modules/m_halfop.cpp b/src/modules/m_halfop.cpp deleted file mode 100644 index 3194fcde8..000000000 --- a/src/modules/m_halfop.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -/* $ModDesc: Channel half-operator mode provider */ - -#include "inspircd.h" - -class ModeChannelHalfOp : public ModeHandler -{ - public: - ModeChannelHalfOp(Module* parent); - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding); - unsigned int GetPrefixRank(); - void RemoveMode(Channel* channel, irc::modestacker* stack = NULL); - void RemoveMode(User* user, irc::modestacker* stack = NULL); - - ModResult AccessCheck(User* src, Channel*, std::string& value, bool adding) - { - if (!adding && src->nick == value) - return MOD_RES_ALLOW; - return MOD_RES_PASSTHRU; - } -}; - -ModeChannelHalfOp::ModeChannelHalfOp(Module* parent) : ModeHandler(parent, "halfop", 'h', PARAM_ALWAYS, MODETYPE_CHANNEL) -{ - list = true; - prefix = '%'; - levelrequired = OP_VALUE; - m_paramtype = TR_NICK; -} - -unsigned int ModeChannelHalfOp::GetPrefixRank() -{ - return HALFOP_VALUE; -} - -void ModeChannelHalfOp::RemoveMode(Channel* channel, irc::modestacker* stack) -{ - const UserMembList* clist = channel->GetUsers(); - - for (UserMembCIter i = clist->begin(); i != clist->end(); i++) - { - if (stack) - { - stack->Push(this->GetModeChar(), i->first->nick); - } - else - { - std::vector<std::string> parameters; - parameters.push_back(channel->name); - parameters.push_back("-h"); - parameters.push_back(i->first->nick); - ServerInstance->SendMode(parameters, ServerInstance->FakeClient); - } - } - -} - -void ModeChannelHalfOp::RemoveMode(User*, irc::modestacker* stack) -{ -} - -ModeAction ModeChannelHalfOp::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) -{ - return MODEACTION_ALLOW; -} - -class ModuleHalfop : public Module -{ - ModeChannelHalfOp mh; - public: - ModuleHalfop() : mh(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(mh); - } - - Version GetVersion() - { - return Version("Channel half-operator mode provider", VF_VENDOR); - } -}; - -MODULE_INIT(ModuleHalfop) diff --git a/src/modules/m_haproxy.cpp b/src/modules/m_haproxy.cpp new file mode 100644 index 000000000..8272c2980 --- /dev/null +++ b/src/modules/m_haproxy.cpp @@ -0,0 +1,326 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2018 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "iohook.h" + +enum +{ + // The family for TCP over IPv4. + PP2_FAMILY_IPV4 = 0x11, + + // The length of the PP2_FAMILY_IPV4 endpoints. + PP2_FAMILY_IPV4_LENGTH = 12, + + // The family for TCP over IPv6. + PP2_FAMILY_IPV6 = 0x21, + + // The length of the PP2_FAMILY_IPV6 endpoints. + PP2_FAMILY_IPV6_LENGTH = 36, + + // The family for UNIX sockets. + PP2_FAMILY_UNIX = 0x31, + + // The length of the PP2_FAMILY_UNIX endpoints. + PP2_FAMILY_UNIX_LENGTH = 216, + + // The bitmask we apply to extract the command. + PP2_COMMAND_MASK = 0x0F, + + // The length of the PROXY protocol header. + PP2_HEADER_LENGTH = 16, + + // The length of the PROXY protocol signature. + PP2_SIGNATURE_LENGTH = 12, + + // The PROXY protocol version we support. + PP2_VERSION = 0x20, + + // The bitmask we apply to extract the protocol version. + PP2_VERSION_MASK = 0xF0 +}; + +enum HAProxyState +{ + // We are waiting for the PROXY header section. + HPS_WAITING_FOR_HEADER, + + // We are waiting for the PROXY address section. + HPS_WAITING_FOR_ADDRESS, + + // The client is fully connected. + HPS_CONNECTED +}; + +enum HAProxyCommand +{ + // LOCAL command. + HPC_LOCAL = 0x00, + + // PROXY command. + HPC_PROXY = 0x01 +}; + +struct HAProxyHeader +{ + // The signature used to identify the HAProxy protocol. + uint8_t signature[PP2_SIGNATURE_LENGTH]; + + // The version of the PROXY protocol and command being sent. + uint8_t version_command; + + // The family for the address. + uint8_t family; + + // The length of the address section. + uint16_t length; +}; + +class HAProxyHookProvider : public IOHookProvider +{ + public: + HAProxyHookProvider(Module* mod) + : IOHookProvider(mod, "haproxy", IOHookProvider::IOH_UNKNOWN, true) + { + } + + void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE; + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + // We don't need to implement this. + } +}; + +// The signature for a HAProxy PROXY protocol header. +static const char proxy_signature[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; + +class HAProxyHook : public IOHookMiddle +{ + private: + // The length of the address section. + uint16_t address_length; + + // The endpoint the client is connecting from. + irc::sockets::sockaddrs client; + + // The command sent by the proxy server. + HAProxyCommand command; + + // The endpoint the client is connected to. + irc::sockets::sockaddrs server; + + // The current state of the PROXY parser. + HAProxyState state; + + int ReadProxyAddress(StreamSocket* sock) + { + // Block until we have the entire address. + std::string& recvq = GetRecvQ(); + if (recvq.length() < address_length) + return 0; + + switch (command) + { + case HPC_LOCAL: + // Skip the address completely. + recvq.erase(0, address_length); + break; + + case HPC_PROXY: + // Store the endpoint information. + switch (client.family()) + { + case AF_INET: + memcpy(&client.in4.sin_addr.s_addr, &recvq[0], 4); + memcpy(&server.in4.sin_addr.s_addr, &recvq[4], 8); + memcpy(&client.in4.sin_port, &recvq[8], 2); + memcpy(&server.in4.sin_port, &recvq[10], 2); + break; + + case AF_INET6: + memcpy(client.in6.sin6_addr.s6_addr, &recvq[0], 16); + memcpy(server.in6.sin6_addr.s6_addr, &recvq[16], 16); + memcpy(&client.in6.sin6_port, &recvq[32], 2); + memcpy(&server.in6.sin6_port, &recvq[34], 2); + break; + + case AF_UNIX: + memcpy(client.un.sun_path, &recvq[0], 108); + memcpy(client.un.sun_path, &recvq[108], 108); + break; + } + + sock->OnSetEndPoint(server, client); + + // XXX: HAProxy's PROXY v2 specification defines Type-Length-Values that + // could appear here but as of 2018-07-25 it does not send anything. We + // should revisit this in the future to see if they actually send them. + recvq.erase(0, address_length); + } + + // We're done! + state = HPS_CONNECTED; + return 1; + } + + int ReadProxyHeader(StreamSocket* sock) + { + // Block until we have a header. + std::string& recvq = GetRecvQ(); + if (recvq.length() < PP2_HEADER_LENGTH) + return 0; + + // Read the header. + HAProxyHeader header; + memcpy(&header, recvq.c_str(), PP2_HEADER_LENGTH); + recvq.erase(0, PP2_HEADER_LENGTH); + + // Check we are actually parsing a HAProxy header. + if (memcmp(&header.signature, proxy_signature, PP2_SIGNATURE_LENGTH) != 0) + { + // If we've reached this point the proxy server did not send a proxy information. + sock->SetError("Invalid HAProxy PROXY signature"); + return -1; + } + + // We only support this version of the protocol. + const uint8_t version = (header.version_command & PP2_VERSION_MASK); + if (version != PP2_VERSION) + { + sock->SetError("Unsupported HAProxy PROXY protocol version"); + return -1; + } + + // We only support the LOCAL and PROXY commands. + command = static_cast<HAProxyCommand>(header.version_command & PP2_COMMAND_MASK); + switch (command) + { + case HPC_LOCAL: + // Intentionally left blank. + break; + + case HPC_PROXY: + // Check the protocol support and initialise the sockaddrs. + uint16_t shortest_length; + switch (header.family) + { + case PP2_FAMILY_IPV4: // TCP over IPv4. + client.sa.sa_family = server.sa.sa_family = AF_INET; + shortest_length = PP2_FAMILY_IPV4_LENGTH; + break; + + case PP2_FAMILY_IPV6: // TCP over IPv6. + client.sa.sa_family = server.sa.sa_family = AF_INET6; + shortest_length = PP2_FAMILY_IPV6_LENGTH; + break; + + case PP2_FAMILY_UNIX: // UNIX stream. + client.sa.sa_family = server.sa.sa_family = AF_UNIX; + shortest_length = PP2_FAMILY_UNIX_LENGTH; + break; + + default: // Unknown protocol. + sock->SetError("Invalid HAProxy PROXY protocol type"); + return -1; + } + + // Check that the length can actually contain the addresses. + address_length = ntohs(header.length); + if (address_length < shortest_length) + { + sock->SetError("Truncated HAProxy PROXY address section"); + return -1; + } + break; + + default: + sock->SetError("Unsupported HAProxy PROXY command"); + return -1; + } + + state = HPS_WAITING_FOR_ADDRESS; + return ReadProxyAddress(sock); + } + + public: + HAProxyHook(IOHookProvider* Prov, StreamSocket* sock) + : IOHookMiddle(Prov) + , state(HPS_WAITING_FOR_HEADER) + { + sock->AddIOHook(this); + } + + int OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& uppersendq) CXX11_OVERRIDE + { + // We don't need to implement this. + GetSendQ().moveall(uppersendq); + return 1; + } + + int OnStreamSocketRead(StreamSocket* sock, std::string& destrecvq) CXX11_OVERRIDE + { + switch (state) + { + case HPS_WAITING_FOR_HEADER: + return ReadProxyHeader(sock); + + case HPS_WAITING_FOR_ADDRESS: + return ReadProxyAddress(sock); + + case HPS_CONNECTED: + std::string& recvq = GetRecvQ(); + destrecvq.append(recvq); + recvq.clear(); + return 1; + } + + // We should never reach this point. + return -1; + } + + void OnStreamSocketClose(StreamSocket* sock) CXX11_OVERRIDE + { + // We don't need to implement this. + } +}; + +void HAProxyHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) +{ + new HAProxyHook(this, sock); +} + +class ModuleHAProxy : public Module +{ + private: + reference<HAProxyHookProvider> hookprov; + + public: + ModuleHAProxy() + : hookprov(new HAProxyHookProvider(this)) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for the HAProxy PROXY protocol", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHAProxy) diff --git a/src/modules/m_helpop.cpp b/src/modules/m_helpop.cpp index 4bbe8785e..f88298576 100644 --- a/src/modules/m_helpop.cpp +++ b/src/modules/m_helpop.cpp @@ -21,11 +21,17 @@ */ -/* $ModDesc: Provides the /HELPOP command for useful information */ - #include "inspircd.h" +#include "modules/whois.h" + +enum +{ + // From UnrealIRCd. + RPL_WHOISHELPOP = 310 +}; -static std::map<irc::string, std::string> helpop_map; +typedef std::map<std::string, std::string, irc::insensitive_swo> HelpopMap; +static HelpopMap helpop_map; /** Handles user mode +h */ @@ -42,41 +48,40 @@ class Helpop : public SimpleUserModeHandler */ class CommandHelpop : public Command { + const std::string startkey; public: - CommandHelpop(Module* Creator) : Command(Creator, "HELPOP", 0) + CommandHelpop(Module* Creator) + : Command(Creator, "HELPOP", 0) + , startkey("start") { syntax = "<any-text>"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - irc::string parameter("start"); - if (parameters.size() > 0) - parameter = parameters[0].c_str(); + const std::string& parameter = (!parameters.empty() ? parameters[0] : startkey); if (parameter == "index") { /* iterate over all helpop items */ - user->WriteServ("290 %s :HELPOP topic index", user->nick.c_str()); - for (std::map<irc::string, std::string>::iterator iter = helpop_map.begin(); iter != helpop_map.end(); iter++) - { - user->WriteServ("292 %s : %s", user->nick.c_str(), iter->first.c_str()); - } - user->WriteServ("292 %s :*** End of HELPOP topic index", user->nick.c_str()); + user->WriteNumeric(290, "HELPOP topic index"); + for (HelpopMap::const_iterator iter = helpop_map.begin(); iter != helpop_map.end(); iter++) + user->WriteNumeric(292, InspIRCd::Format(" %s", iter->first.c_str())); + user->WriteNumeric(292, "*** End of HELPOP topic index"); } else { - user->WriteServ("290 %s :*** HELPOP for %s", user->nick.c_str(), parameter.c_str()); - user->WriteServ("292 %s : -", user->nick.c_str()); + user->WriteNumeric(290, InspIRCd::Format("*** HELPOP for %s", parameter.c_str())); + user->WriteNumeric(292, " -"); - std::map<irc::string, std::string>::iterator iter = helpop_map.find(parameter); + HelpopMap::const_iterator iter = helpop_map.find(parameter); if (iter == helpop_map.end()) { iter = helpop_map.find("nohelp"); } - std::string value = iter->second; + const std::string& value = iter->second; irc::sepstream stream(value, '\n'); std::string token = "*"; @@ -84,48 +89,40 @@ class CommandHelpop : public Command { // Writing a blank line will not work with some clients if (token.empty()) - user->WriteServ("292 %s : ", user->nick.c_str()); + user->WriteNumeric(292, ' '); else - user->WriteServ("292 %s :%s", user->nick.c_str(), token.c_str()); + user->WriteNumeric(292, token); } - user->WriteServ("292 %s : -", user->nick.c_str()); - user->WriteServ("292 %s :*** End of HELPOP", user->nick.c_str()); + user->WriteNumeric(292, " -"); + user->WriteNumeric(292, "*** End of HELPOP"); } return CMD_SUCCESS; } }; -class ModuleHelpop : public Module +class ModuleHelpop : public Module, public Whois::EventListener { - private: CommandHelpop cmd; Helpop ho; public: ModuleHelpop() - : cmd(this), ho(this) + : Whois::EventListener(this) + , cmd(this) + , ho(this) { } - void init() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - ReadConfig(); - ServerInstance->Modules->AddService(ho); - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnRehash, I_OnWhois }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void ReadConfig() - { - std::map<irc::string, std::string> help; + HelpopMap help; ConfigTagList tags = ServerInstance->Config->ConfTags("helpop"); for(ConfigIter i = tags.first; i != tags.second; ++i) { ConfigTag* tag = i->second; - irc::string key = assign(tag->getString("key")); + std::string key = tag->getString("key"); std::string value; tag->readString("value", value, true); /* Linefeeds allowed */ @@ -151,20 +148,15 @@ class ModuleHelpop : public Module helpop_map.swap(help); } - void OnRehash(User* user) - { - ReadConfig(); - } - - void OnWhois(User* src, User* dst) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - if (dst->IsModeSet('h')) + if (whois.GetTarget()->IsModeSet(ho)) { - ServerInstance->SendWhoisLine(src, dst, 310, src->nick+" "+dst->nick+" :is available for help."); + whois.SendLine(RPL_WHOISHELPOP, "is available for help."); } } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the /HELPOP command for useful information", VF_VENDOR); } diff --git a/src/modules/m_hidechans.cpp b/src/modules/m_hidechans.cpp index 008c62208..1664582e0 100644 --- a/src/modules/m_hidechans.cpp +++ b/src/modules/m_hidechans.cpp @@ -19,8 +19,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides support for hiding channels with user mode +I */ +#include "modules/whois.h" /** Handles user mode +I */ @@ -30,49 +29,39 @@ class HideChans : public SimpleUserModeHandler HideChans(Module* Creator) : SimpleUserModeHandler(Creator, "hidechans", 'I') { } }; -class ModuleHideChans : public Module +class ModuleHideChans : public Module, public Whois::LineEventListener { bool AffectsOpers; HideChans hm; public: - ModuleHideChans() : hm(this) - { - } - - void init() + ModuleHideChans() + : Whois::LineEventListener(this) + , hm(this) { - ServerInstance->Modules->AddService(hm); - Implementation eventlist[] = { I_OnWhoisLine, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); } - virtual ~ModuleHideChans() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for hiding channels with user mode +I", VF_VENDOR); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { AffectsOpers = ServerInstance->Config->ConfValue("hidechans")->getBool("affectsopers"); } - ModResult OnWhoisLine(User* user, User* dest, int &numeric, std::string &text) + ModResult OnWhoisLine(Whois::Context& whois, Numeric::Numeric& numeric) CXX11_OVERRIDE { /* always show to self */ - if (user == dest) + if (whois.IsSelfWhois()) return MOD_RES_PASSTHRU; /* don't touch anything except 319 */ - if (numeric != 319) + if (numeric.GetNumeric() != 319) return MOD_RES_PASSTHRU; /* don't touch if -I */ - if (!dest->IsModeSet('I')) + if (!whois.GetTarget()->IsModeSet(hm)) return MOD_RES_PASSTHRU; /* if it affects opers, we don't care if they are opered */ @@ -80,7 +69,7 @@ class ModuleHideChans : public Module return MOD_RES_DENY; /* doesn't affect opers, sender is opered */ - if (user->HasPrivPermission("users/auspex")) + if (whois.GetSource()->HasPrivPermission("users/auspex")) return MOD_RES_PASSTHRU; /* user must be opered, boned. */ @@ -88,5 +77,4 @@ class ModuleHideChans : public Module } }; - MODULE_INIT(ModuleHideChans) diff --git a/src/modules/m_hidelist.cpp b/src/modules/m_hidelist.cpp new file mode 100644 index 000000000..2d3f0be7c --- /dev/null +++ b/src/modules/m_hidelist.cpp @@ -0,0 +1,87 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +class ListWatcher : public ModeWatcher +{ + // Minimum rank required to view the list + const unsigned int minrank; + + public: + ListWatcher(Module* mod, const std::string& modename, unsigned int rank) + : ModeWatcher(mod, modename, MODETYPE_CHANNEL) + , minrank(rank) + { + } + + bool BeforeMode(User* user, User* destuser, Channel* chan, std::string& param, bool adding) CXX11_OVERRIDE + { + // Only handle listmode list requests + if (!param.empty()) + return true; + + // If the user requesting the list is a member of the channel see if they have the + // rank required to view the list + Membership* memb = chan->GetUser(user); + if ((memb) && (memb->getRank() >= minrank)) + return true; + + if (user->HasPrivPermission("channels/auspex")) + return true; + + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, InspIRCd::Format("You do not have access to view the %s list", GetModeName().c_str())); + return false; + } +}; + +class ModuleHideList : public Module +{ + std::vector<ListWatcher*> watchers; + + public: + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + stdalgo::delete_all(watchers); + watchers.clear(); + + ConfigTagList tags = ServerInstance->Config->ConfTags("hidelist"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + std::string modename = tag->getString("mode"); + // If rank is set to 0 everyone inside the channel can view the list, + // but non-members may not + unsigned int rank = tag->getUInt("rank", HALFOP_VALUE); + watchers.push_back(new ListWatcher(this, modename, rank)); + } + } + + ~ModuleHideList() + { + stdalgo::delete_all(watchers); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for hiding the list of listmodes", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHideList) diff --git a/src/modules/m_hidemode.cpp b/src/modules/m_hidemode.cpp new file mode 100644 index 000000000..d6ae05801 --- /dev/null +++ b/src/modules/m_hidemode.cpp @@ -0,0 +1,198 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +namespace +{ +class Settings +{ + typedef insp::flat_map<std::string, unsigned int> RanksToSeeMap; + RanksToSeeMap rankstosee; + + public: + unsigned int GetRequiredRank(const ModeHandler& mh) const + { + RanksToSeeMap::const_iterator it = rankstosee.find(mh.name); + if (it != rankstosee.end()) + return it->second; + return 0; + } + + void Load() + { + rankstosee.clear(); + + ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + std::string modename = tag->getString("mode"); + unsigned int rank = tag->getInt("rank", 0, 0); + if (!modename.empty() && rank) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank); + rankstosee.insert(std::make_pair(modename, rank)); + } + } + } +}; + +class ModeHook : public ClientProtocol::EventHook +{ + typedef insp::flat_map<unsigned int, const ClientProtocol::MessageList*> FilteredModeMap; + + std::vector<Modes::ChangeList> modechangelists; + std::list<ClientProtocol::Messages::Mode> filteredmodelist; + std::list<ClientProtocol::MessageList> filteredmsgplists; + FilteredModeMap cache; + + static ModResult HandleResult(const ClientProtocol::MessageList* filteredmessagelist, ClientProtocol::MessageList& messagelist) + { + // Deny if member isn't allowed to see even a single mode change from this mode event + if (!filteredmessagelist) + return MOD_RES_DENY; + + // Member is allowed to see at least one mode change, replace list + if (filteredmessagelist != &messagelist) + messagelist = *filteredmessagelist; + + return MOD_RES_PASSTHRU; + } + + Modes::ChangeList* FilterModeChangeList(const ClientProtocol::Events::Mode& mode, unsigned int rank) + { + Modes::ChangeList* modechangelist = NULL; + for (Modes::ChangeList::List::const_iterator i = mode.GetChangeList().getlist().begin(); i != mode.GetChangeList().getlist().end(); ++i) + { + const Modes::Change& curr = *i; + if (settings.GetRequiredRank(*curr.mh) <= rank) + { + // No restriction on who can see this mode or there is one but the member's rank is sufficient + if (modechangelist) + modechangelist->push(curr); + + continue; + } + + // Member cannot see the current mode change + + if (!modechangelist) + { + // Create new mode change list or reuse the last one if it's empty + if ((modechangelists.empty()) || (!modechangelists.back().empty())) + modechangelists.push_back(Modes::ChangeList()); + + // Add all modes to it which we've accepted so far + modechangelists.back().push(mode.GetChangeList().getlist().begin(), i); + modechangelist = &modechangelists.back(); + } + } + return modechangelist; + } + + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE + { + cache.clear(); + filteredmsgplists.clear(); + filteredmodelist.clear(); + modechangelists.clear(); + + // Ensure no reallocations will happen + const size_t numprefixmodes = ServerInstance->Modes.GetPrefixModes().size(); + modechangelists.reserve(numprefixmodes); + } + + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE + { + const ClientProtocol::Events::Mode& mode = static_cast<const ClientProtocol::Events::Mode&>(ev); + Channel* const chan = mode.GetMessages().front().GetChanTarget(); + if (!chan) + return MOD_RES_PASSTHRU; + + Membership* const memb = chan->GetUser(user); + if (!memb) + return MOD_RES_PASSTHRU; + + // Check cache first + const FilteredModeMap::const_iterator it = cache.find(memb->getRank()); + if (it != cache.end()) + return HandleResult(it->second, messagelist); + + // Message for this rank isn't cached, generate it now + const Modes::ChangeList* const filteredchangelist = FilterModeChangeList(mode, memb->getRank()); + + // If no new change list was generated (above method returned NULL) it means the member and everyone else + // with the same rank can see everything in the original change list. + ClientProtocol::MessageList* finalmsgplist = &messagelist; + if (filteredchangelist) + { + if (filteredchangelist->empty()) + { + // This rank cannot see any mode changes in the original change list + finalmsgplist = NULL; + } + else + { + // This rank can see some of the mode changes in the filtered mode change list. + // Create and store a new protocol message from it. + filteredmsgplists.push_back(ClientProtocol::MessageList()); + ClientProtocol::Events::Mode::BuildMessages(mode.GetMessages().front().GetSourceUser(), chan, NULL, *filteredchangelist, filteredmodelist, filteredmsgplists.back()); + finalmsgplist = &filteredmsgplists.back(); + } + } + + // Cache the result in all cases so it can be reused for further members with the same rank + cache.insert(std::make_pair(memb->getRank(), finalmsgplist)); + return HandleResult(finalmsgplist, messagelist); + } + + public: + Settings settings; + + ModeHook(Module* mod) + : ClientProtocol::EventHook(mod, "MODE", 10) + { + } +}; +} + +class ModuleHideMode : public Module +{ + private: + ModeHook modehook; + + public: + ModuleHideMode() + : modehook(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + modehook.settings.Load(); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for hiding mode changes", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHideMode) diff --git a/src/modules/m_hideoper.cpp b/src/modules/m_hideoper.cpp index 32999d9f0..2bca0a156 100644 --- a/src/modules/m_hideoper.cpp +++ b/src/modules/m_hideoper.cpp @@ -20,8 +20,8 @@ #include "inspircd.h" - -/* $ModDesc: Provides support for hiding oper status with user mode +H */ +#include "modules/stats.h" +#include "modules/whois.h" /** Handles user mode +H */ @@ -36,7 +36,7 @@ class HideOper : public SimpleUserModeHandler oper = true; } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { if (SimpleUserModeHandler::OnModeChange(source, dest, channel, parameter, adding) == MODEACTION_DENY) return MODEACTION_DENY; @@ -50,43 +50,33 @@ class HideOper : public SimpleUserModeHandler } }; -class ModuleHideOper : public Module +class ModuleHideOper : public Module, public Stats::EventListener, public Whois::LineEventListener { HideOper hm; bool active; public: ModuleHideOper() - : hm(this) + : Stats::EventListener(this) + , Whois::LineEventListener(this) + , hm(this) , active(false) { } - void init() - { - ServerInstance->Modules->AddService(hm); - Implementation eventlist[] = { I_OnWhoisLine, I_OnSendWhoLine, I_OnStats, I_OnNumeric, I_OnUserQuit }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - - virtual ~ModuleHideOper() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for hiding oper status with user mode +H", VF_VENDOR); } - void OnUserQuit(User* user, const std::string&, const std::string&) + void OnUserQuit(User* user, const std::string&, const std::string&) CXX11_OVERRIDE { - if (user->IsModeSet('H')) + if (user->IsModeSet(hm)) hm.opercount--; } - ModResult OnNumeric(User* user, unsigned int numeric, const std::string& text) + ModResult OnNumeric(User* user, const Numeric::Numeric& numeric) CXX11_OVERRIDE { - if (numeric != 252 || active || user->HasPrivPermission("users/auspex")) + if (numeric.GetNumeric() != RPL_LUSEROP || active || user->HasPrivPermission("users/auspex")) return MOD_RES_PASSTHRU; // If there are no visible operators then we shouldn't send the numeric. @@ -94,68 +84,72 @@ class ModuleHideOper : public Module if (opercount) { active = true; - user->WriteNumeric(252, "%s %lu :operator(s) online", user->nick.c_str(), static_cast<unsigned long>(opercount)); + user->WriteNumeric(RPL_LUSEROP, opercount, "operator(s) online"); active = false; } return MOD_RES_DENY; } - ModResult OnWhoisLine(User* user, User* dest, int &numeric, std::string &text) + ModResult OnWhoisLine(Whois::Context& whois, Numeric::Numeric& numeric) CXX11_OVERRIDE { /* Dont display numeric 313 (RPL_WHOISOPER) if they have +H set and the * person doing the WHOIS is not an oper */ - if (numeric != 313) + if (numeric.GetNumeric() != 313) return MOD_RES_PASSTHRU; - if (!dest->IsModeSet('H')) + if (!whois.GetTarget()->IsModeSet(hm)) return MOD_RES_PASSTHRU; - if (!user->HasPrivPermission("users/auspex")) + if (!whois.GetSource()->HasPrivPermission("users/auspex")) return MOD_RES_DENY; return MOD_RES_PASSTHRU; } - void OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, std::string& line) + ModResult OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, Membership* memb, Numeric::Numeric& numeric) CXX11_OVERRIDE { - if (user->IsModeSet('H') && !source->HasPrivPermission("users/auspex")) + if (user->IsModeSet(hm) && !source->HasPrivPermission("users/auspex")) { + // Hide the line completely if doing a "/who * o" query + if ((params.size() > 1) && (params[1].find('o') != std::string::npos)) + return MOD_RES_DENY; + // hide the "*" that marks the user as an oper from the /WHO line - std::string::size_type spcolon = line.find(" :"); - if (spcolon == std::string::npos) - return; // Another module hid the user completely - std::string::size_type sp = line.rfind(' ', spcolon-1); - std::string::size_type pos = line.find('*', sp); + // #chan ident localhost insp22.test nick H@ :0 Attila + if (numeric.GetParams().size() < 6) + return MOD_RES_PASSTHRU; + + std::string& param = numeric.GetParams()[5]; + const std::string::size_type pos = param.find('*'); if (pos != std::string::npos) - line.erase(pos, 1); - // hide the line completely if doing a "/who * o" query - if (params.size() > 1 && params[1].find('o') != std::string::npos) - line.clear(); + param.erase(pos, 1); } + return MOD_RES_PASSTHRU; } - ModResult OnStats(char symbol, User* user, string_list &results) + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if (symbol != 'P') + if (stats.GetSymbol() != 'P') return MOD_RES_PASSTHRU; unsigned int count = 0; - for (std::list<User*>::const_iterator i = ServerInstance->Users->all_opers.begin(); i != ServerInstance->Users->all_opers.end(); ++i) + const UserManager::OperList& opers = ServerInstance->Users->all_opers; + for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i) { User* oper = *i; - if (!ServerInstance->ULine(oper->server) && (IS_OPER(user) || !oper->IsModeSet('H'))) + if (!oper->server->IsULine() && (stats.GetSource()->IsOper() || !oper->IsModeSet(hm))) { - results.push_back(ServerInstance->Config->ServerName+" 249 " + user->nick + " :" + oper->nick + " (" + oper->ident + "@" + oper->dhost + ") Idle: " + - (IS_LOCAL(oper) ? ConvToStr(ServerInstance->Time() - oper->idle_lastmsg) + " secs" : "unavailable")); + LocalUser* lu = IS_LOCAL(oper); + stats.AddRow(249, oper->nick + " (" + oper->ident + "@" + oper->GetDisplayedHost() + ") Idle: " + + (lu ? ConvToStr(ServerInstance->Time() - lu->idle_lastmsg) + " secs" : "unavailable")); count++; } } - results.push_back(ServerInstance->Config->ServerName+" 249 "+user->nick+" :"+ConvToStr(count)+" OPER(s)"); + stats.AddRow(249, ConvToStr(count)+" OPER(s)"); return MOD_RES_DENY; } }; - MODULE_INIT(ModuleHideOper) diff --git a/src/modules/m_hostchange.cpp b/src/modules/m_hostchange.cpp index 7433fccd3..201c4b59b 100644 --- a/src/modules/m_hostchange.cpp +++ b/src/modules/m_hostchange.cpp @@ -20,151 +20,216 @@ #include "inspircd.h" +#include "modules/account.h" -/* $ModDesc: Provides masking of user hostnames in a different way to m_cloaking */ - -/** Holds information on a host set by m_hostchange - */ -class Host +// Holds information about a <hostchange> rule. +class HostRule { public: enum HostChangeAction { - HCA_SET, - HCA_SUFFIX, - HCA_ADDNICK + // Add the user's account name to their hostname. + HCA_ADDACCOUNT, + + // Add the user's nickname to their hostname. + HCA_ADDNICK, + + // Set the user's hostname to the specific value. + HCA_SET }; + private: HostChangeAction action; - std::string newhost; - std::string ports; + std::string host; + std::string mask; + insp::flat_set<int> ports; + std::string prefix; + std::string suffix; + + public: + HostRule(const std::string& Host, const std::string& Mask, const insp::flat_set<int>& Ports) + : action(HCA_SET) + , host(Host) + , mask(Mask) + , ports(Ports) + { + } + + HostRule(HostChangeAction Action, const std::string& Mask, const insp::flat_set<int>& Ports, const std::string& Prefix, const std::string& Suffix) + : action(Action) + , mask(Mask) + , ports(Ports) + , prefix(Prefix) + , suffix(Suffix) + { + } + + HostChangeAction GetAction() const + { + return action; + } - Host(HostChangeAction Action, const std::string& Newhost, const std::string& Ports) : - action(Action), newhost(Newhost), ports(Ports) {} + const std::string& GetHost() const + { + return host; + } + + bool Matches(LocalUser* user) const + { + if (!ports.empty() && !ports.count(user->GetServerPort())) + return false; + + if (InspIRCd::MatchCIDR(user->MakeHost(), mask)) + return true; + + return InspIRCd::MatchCIDR(user->MakeHostIP(), mask); + } + + void Wrap(const std::string& value, std::string& out) const + { + if (!prefix.empty()) + out.append(prefix); + + out.append(value); + + if (!suffix.empty()) + out.append(suffix); + } }; -typedef std::vector<std::pair<std::string, Host> > hostchanges_t; +typedef std::vector<HostRule> HostRules; class ModuleHostChange : public Module { - private: - hostchanges_t hostchanges; - std::string MySuffix; - std::string MyPrefix; - std::string MySeparator; +private: + std::bitset<UCHAR_MAX> hostmap; + HostRules hostrules; - public: - void init() + std::string CleanName(const std::string& name) { - OnRehash(NULL); - Implementation eventlist[] = { I_OnRehash, I_OnUserConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + std::string buffer; + buffer.reserve(name.length()); + for (std::string::const_iterator iter = name.begin(); iter != name.end(); ++iter) + { + if (hostmap.test(static_cast<unsigned char>(*iter))) + { + buffer.push_back(*iter); + } + } + return buffer; } - virtual void OnRehash(User* user) + public: + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - ConfigTag* host = ServerInstance->Config->ConfValue("host"); - MySuffix = host->getString("suffix"); - MyPrefix = host->getString("prefix"); - MySeparator = host->getString("separator", "."); - hostchanges.clear(); + HostRules rules; - std::set<std::string> dupecheck; ConfigTagList tags = ServerInstance->Config->ConfTags("hostchange"); for (ConfigIter i = tags.first; i != tags.second; ++i) { ConfigTag* tag = i->second; - std::string mask = tag->getString("mask"); - if (!dupecheck.insert(mask).second) - throw ModuleException("Duplicate hostchange entry: " + mask); - Host::HostChangeAction act; - std::string newhost; - std::string action = tag->getString("action"); - if (!strcasecmp(action.c_str(), "set")) + // Ensure that we have the <hostchange:mask> parameter. + const std::string mask = tag->getString("mask"); + if (mask.empty()) + throw ModuleException("<hostchange:mask> is a mandatory field, at " + tag->getTagLocation()); + + insp::flat_set<int> ports; + const std::string portlist = tag->getString("ports"); + if (!ports.empty()) { - act = Host::HCA_SET; - newhost = tag->getString("value"); + irc::portparser portrange(portlist, false); + while (int port = portrange.GetToken()) + ports.insert(port); } - else if (!strcasecmp(action.c_str(), "suffix")) - act = Host::HCA_SUFFIX; - else if (!strcasecmp(action.c_str(), "addnick")) - act = Host::HCA_ADDNICK; - else - throw ModuleException("Invalid hostchange action: " + action); - hostchanges.push_back(std::make_pair(mask, Host(act, newhost, tag->getString("ports")))); + // Determine what type of host rule this is. + const std::string action = tag->getString("action"); + if (stdalgo::string::equalsci(action, "addaccount")) + { + // The hostname is in the format [prefix]<account>[suffix]. + rules.push_back(HostRule(HostRule::HCA_ADDACCOUNT, mask, ports, tag->getString("prefix"), tag->getString("suffix"))); + } + else if (stdalgo::string::equalsci(action, "addnick")) + { + // The hostname is in the format [prefix]<nick>[suffix]. + rules.push_back(HostRule(HostRule::HCA_ADDNICK, mask, ports, tag->getString("prefix"), tag->getString("suffix"))); + } + else if (stdalgo::string::equalsci(action, "set")) + { + // Ensure that we have the <hostchange:value> parameter. + const std::string value = tag->getString("value"); + if (value.empty()) + throw ModuleException("<hostchange:value> is a mandatory field when using the 'set' action, at " + tag->getTagLocation()); + + // The hostname is in the format <value>. + rules.push_back(HostRule(mask, value, ports)); + continue; + } + else + { + throw ModuleException(action + " is an invalid <hostchange:action> type, at " + tag->getTagLocation()); + } } + + const std::string hmap = ServerInstance->Config->ConfValue("hostname")->getString("charmap", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_/0123456789"); + hostmap.reset(); + for (std::string::const_iterator iter = hmap.begin(); iter != hmap.end(); ++iter) + hostmap.set(static_cast<unsigned char>(*iter)); + hostrules.swap(rules); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - // returns the version number of the module to be - // listed in /MODULES - return Version("Provides masking of user hostnames in a different way to m_cloaking", VF_VENDOR); + return Version("Provides rule-based masking of user hostnames", VF_VENDOR); } - virtual void OnUserConnect(LocalUser* user) + void OnUserConnect(LocalUser* user) CXX11_OVERRIDE { - for (hostchanges_t::iterator i = hostchanges.begin(); i != hostchanges.end(); i++) + for (HostRules::const_iterator iter = hostrules.begin(); iter != hostrules.end(); ++iter) { - if (((InspIRCd::MatchCIDR(user->MakeHost(), i->first)) || (InspIRCd::MatchCIDR(user->MakeHostIP(), i->first)))) + const HostRule& rule = *iter; + if (!rule.Matches(user)) + continue; + + std::string newhost; + if (rule.GetAction() == HostRule::HCA_ADDACCOUNT) + { + // Retrieve the account name. + const AccountExtItem* accountext = GetAccountExtItem(); + const std::string* accountptr = accountext ? accountext->get(user) : NULL; + if (!accountptr) + continue; + + // Remove invalid hostname characters. + std::string accountname = CleanName(*accountptr); + if (accountname.empty()) + continue; + + // Create the hostname. + rule.Wrap(accountname, newhost); + } + else if (rule.GetAction() == HostRule::HCA_ADDNICK) + { + // Remove invalid hostname characters. + const std::string nickname = CleanName(user->nick); + if (nickname.empty()) + continue; + + // Create the hostname. + rule.Wrap(nickname, newhost); + } + else if (rule.GetAction() == HostRule::HCA_SET) + { + newhost.assign(rule.GetHost()); + } + + if (!newhost.empty()) { - const Host& h = i->second; - - if (!h.ports.empty()) - { - irc::portparser portrange(h.ports, false); - long portno = -1; - bool foundany = false; - - while ((portno = portrange.GetToken())) - if (portno == user->GetServerPort()) - foundany = true; - - if (!foundany) - continue; - } - - // host of new user matches a hostchange tag's mask - std::string newhost; - if (h.action == Host::HCA_SET) - { - newhost = h.newhost; - } - else if (h.action == Host::HCA_SUFFIX) - { - newhost = MySuffix; - } - else if (h.action == Host::HCA_ADDNICK) - { - // first take their nick and strip out non-dns, leaving just [A-Z0-9\-] - std::string complete; - for (std::string::const_iterator j = user->nick.begin(); j != user->nick.end(); ++j) - { - if (((*j >= 'A') && (*j <= 'Z')) || - ((*j >= 'a') && (*j <= 'z')) || - ((*j >= '0') && (*j <= '9')) || - (*j == '-')) - { - complete = complete + *j; - } - } - if (complete.empty()) - complete = "i-have-a-lame-nick"; - - if (!MyPrefix.empty()) - newhost = MyPrefix + MySeparator + complete; - else - newhost = complete + MySeparator + MySuffix; - } - if (!newhost.empty()) - { - user->WriteServ("NOTICE "+user->nick+" :Setting your virtual host: " + newhost); - if (!user->ChangeDisplayedHost(newhost.c_str())) - user->WriteServ("NOTICE "+user->nick+" :Could not set your virtual host: " + newhost); - return; - } + user->WriteNotice("Setting your virtual host: " + newhost); + if (!user->ChangeDisplayedHost(newhost)) + user->WriteNotice("Could not set your virtual host: " + newhost); + return; } } } diff --git a/src/modules/m_hostcycle.cpp b/src/modules/m_hostcycle.cpp new file mode 100644 index 000000000..a3c81df6e --- /dev/null +++ b/src/modules/m_hostcycle.cpp @@ -0,0 +1,120 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" + +class ModuleHostCycle : public Module +{ + Cap::Reference chghostcap; + const std::string quitmsghost; + const std::string quitmsgident; + + /** Send fake quit/join/mode messages for host or ident cycle. + */ + void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const std::string& reason) + { + // The user has the original ident/host at the time this function is called + ClientProtocol::Messages::Quit quitmsg(user, reason); + ClientProtocol::Event quitevent(ServerInstance->GetRFCEvents().quit, quitmsg); + + already_sent_t silent_id = ServerInstance->Users.NextAlreadySentId(); + already_sent_t seen_id = ServerInstance->Users.NextAlreadySentId(); + + IncludeChanList include_chans(user->chans.begin(), user->chans.end()); + std::map<User*,bool> exceptions; + + FOREACH_MOD(OnBuildNeighborList, (user, include_chans, exceptions)); + + // Users shouldn't see themselves quitting when host cycling + exceptions.erase(user); + for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i) + { + LocalUser* u = IS_LOCAL(i->first); + if ((u) && (!u->quitting) && (!chghostcap.get(u))) + { + if (i->second) + { + u->already_sent = seen_id; + u->Send(quitevent); + } + else + { + u->already_sent = silent_id; + } + } + } + + std::string newfullhost = user->nick + "!" + newident + "@" + newhost; + + for (IncludeChanList::const_iterator i = include_chans.begin(); i != include_chans.end(); ++i) + { + Membership* memb = *i; + Channel* c = memb->chan; + + ClientProtocol::Events::Join joinevent(memb, newfullhost); + + const Channel::MemberMap& ulist = c->GetUsers(); + for (Channel::MemberMap::const_iterator j = ulist.begin(); j != ulist.end(); ++j) + { + LocalUser* u = IS_LOCAL(j->first); + if (u == NULL || u == user) + continue; + if (u->already_sent == silent_id) + continue; + if (chghostcap.get(u)) + continue; + + if (u->already_sent != seen_id) + { + u->Send(quitevent); + u->already_sent = seen_id; + } + + u->Send(joinevent); + } + } + } + + public: + ModuleHostCycle() + : chghostcap(this, "chghost") + , quitmsghost("Changing host") + , quitmsgident("Changing ident") + { + } + + void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE + { + DoHostCycle(user, newident, user->GetDisplayedHost(), quitmsgident); + } + + void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE + { + DoHostCycle(user, user->ident, newhost, quitmsghost); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Cycles users in all their channels when their host or ident changes", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHostCycle) diff --git a/src/modules/m_httpd.cpp b/src/modules/m_httpd.cpp index cb17a0383..28177a7a2 100644 --- a/src/modules/m_httpd.cpp +++ b/src/modules/m_httpd.cpp @@ -23,16 +23,15 @@ #include "inspircd.h" -#include "httpd.h" - -/* $ModDesc: Provides HTTP serving facilities to modules */ -/* $ModDep: httpd.h */ +#include "iohook.h" +#include "modules/httpd.h" class ModuleHttpServer; static ModuleHttpServer* HttpModule; -static bool claimed; -static std::set<HttpServerSocket*> sockets; +static insp::intrusive_list<HttpServerSocket> sockets; +static Events::ModuleEventProvider* aclevprov; +static Events::ModuleEventProvider* reqevprov; /** HTTP socket states */ @@ -45,7 +44,7 @@ enum HttpState /** A socket used for HTTP transport */ -class HttpServerSocket : public BufferedSocket +class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket> { HttpState InternalState; std::string ip; @@ -58,18 +57,37 @@ class HttpServerSocket : public BufferedSocket std::string uri; std::string http_version; - public: - const time_t createtime; + /** True if this object is in the cull list + */ + bool waitingcull; - HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) - : BufferedSocket(newfd), ip(IP), postsize(0) - , createtime(ServerInstance->Time()) + bool Tick(time_t currtime) CXX11_OVERRIDE { - InternalState = HTTP_SERVE_WAIT_REQUEST; + AddToCull(); + return false; + } - FOREACH_MOD(I_OnHookIO, OnHookIO(this, via)); - if (GetIOHook()) - GetIOHook()->OnStreamSocketAccept(this, client, server); + public: + HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec) + : BufferedSocket(newfd) + , Timer(timeoutsec) + , InternalState(HTTP_SERVE_WAIT_REQUEST) + , ip(IP) + , postsize(0) + , waitingcull(false) + { + if ((!via->iohookprovs.empty()) && (via->iohookprovs.back())) + { + via->iohookprovs.back()->OnAccept(this, client, server); + // IOHook may have errored + if (!getError().empty()) + { + AddToCull(); + return; + } + } + + ServerInstance->Timers.AddTimer(this); } ~HttpServerSocket() @@ -77,12 +95,12 @@ class HttpServerSocket : public BufferedSocket sockets.erase(this); } - virtual void OnError(BufferedSocketError) + void OnError(BufferedSocketError) CXX11_OVERRIDE { - ServerInstance->GlobalCulls.AddItem(this); + AddToCull(); } - std::string Response(int response) + std::string Response(unsigned int response) { switch (response) { @@ -173,7 +191,7 @@ class HttpServerSocket : public BufferedSocket } } - void SendHTTPError(int response) + void SendHTTPError(unsigned int response) { HTTPHeaders empty; std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+ @@ -183,18 +201,13 @@ class HttpServerSocket : public BufferedSocket WriteData(data); } - void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders) + void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders) { WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n"); - time_t local = ServerInstance->Time(); - struct tm *timeinfo = gmtime(&local); - char *date = asctime(timeinfo); - date[strlen(date) - 1] = '\0'; - rheaders.CreateHeader("Date", date); - - rheaders.CreateHeader("Server", BRANCH); + rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true)); + rheaders.CreateHeader("Server", INSPIRCD_BRANCH); rheaders.SetHeader("Content-Length", ConvToStr(size)); if (size) @@ -211,7 +224,7 @@ class HttpServerSocket : public BufferedSocket WriteData("\r\n"); } - void OnDataReady() + void OnDataReady() CXX11_OVERRIDE { if (InternalState == HTTP_SERVE_RECV_POSTDATA) { @@ -225,7 +238,7 @@ class HttpServerSocket : public BufferedSocket if (reqbuffer.length() >= 8192) { - ServerInstance->Logs->Log("m_httpd",DEBUG, "m_httpd dropped connection due to an oversized request buffer"); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "m_httpd dropped connection due to an oversized request buffer"); reqbuffer.clear(); SetError("Buffer"); } @@ -265,7 +278,7 @@ class HttpServerSocket : public BufferedSocket continue; } - std::string cheader = reqbuffer.substr(hbegin, hend - hbegin); + std::string cheader(reqbuffer, hbegin, hend - hbegin); std::string::size_type fieldsep = cheader.find(':'); if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1)) @@ -296,7 +309,7 @@ class HttpServerSocket : public BufferedSocket if (reqbuffer.length() >= postsize) { - postdata = reqbuffer.substr(0, postsize); + postdata.assign(reqbuffer, 0, postsize); reqbuffer.erase(0, postsize); } else if (!reqbuffer.empty()) @@ -318,93 +331,96 @@ class HttpServerSocket : public BufferedSocket { InternalState = HTTP_SERVE_SEND_DATA; - claimed = false; - HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata); - acl.Send(); - if (!claimed) + ModResult MOD_RESULT; + HTTPRequest acl(request_type, uri, &headers, this, ip, postdata); + FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl)); + if (MOD_RESULT != MOD_RES_DENY) { - HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata); - url.Send(); - if (!claimed) + HTTPRequest url(request_type, uri, &headers, this, ip, postdata); + FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url)); + if (MOD_RESULT == MOD_RES_PASSTHRU) { SendHTTPError(404); } } } - void Page(std::stringstream* n, int response, HTTPHeaders *hheaders) + void Page(std::stringstream* n, unsigned int response, HTTPHeaders *hheaders) { SendHeaders(n->str().length(), response, *hheaders); WriteData(n->str()); Close(); } + + void AddToCull() + { + if (waitingcull) + return; + + waitingcull = true; + Close(); + ServerInstance->GlobalCulls.AddItem(this); + } }; -class ModuleHttpServer : public Module +class HTTPdAPIImpl : public HTTPdAPIBase { - unsigned int timeoutsec; - public: + HTTPdAPIImpl(Module* parent) + : HTTPdAPIBase(parent) + { + } - void init() + void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE { - HttpModule = this; - Implementation eventlist[] = { I_OnAcceptConnection, I_OnBackgroundTimer, I_OnRehash, I_OnUnloadModule }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); + resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers); } +}; - void OnRehash(User* user) +class ModuleHttpServer : public Module +{ + HTTPdAPIImpl APIImpl; + unsigned int timeoutsec; + Events::ModuleEventProvider acleventprov; + Events::ModuleEventProvider reqeventprov; + + public: + ModuleHttpServer() + : APIImpl(this) + , acleventprov(this, "event/http-acl") + , reqeventprov(this, "event/http-request") { - ConfigTag* tag = ServerInstance->Config->ConfValue("httpd"); - timeoutsec = tag->getInt("timeout"); + aclevprov = &acleventprov; + reqevprov = &reqeventprov; } - void OnRequest(Request& request) + void init() CXX11_OVERRIDE { - if (strcmp(request.id, "HTTP-DOC") != 0) - return; - HTTPDocumentResponse& resp = static_cast<HTTPDocumentResponse&>(request); - claimed = true; - resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers); + HttpModule = this; } - ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - if (from->bind_tag->getString("type") != "httpd") - return MOD_RES_PASSTHRU; - int port; - std::string incomingip; - irc::sockets::satoap(*client, incomingip, port); - sockets.insert(new HttpServerSocket(nfd, incomingip, from, client, server)); - return MOD_RES_ALLOW; + ConfigTag* tag = ServerInstance->Config->ConfValue("httpd"); + timeoutsec = tag->getDuration("timeout", 10, 1); } - void OnBackgroundTimer(time_t curtime) + ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE { - if (!timeoutsec) - return; + if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd")) + return MOD_RES_PASSTHRU; - time_t oldest_allowed = curtime - timeoutsec; - for (std::set<HttpServerSocket*>::const_iterator i = sockets.begin(); i != sockets.end(); ) - { - HttpServerSocket* sock = *i; - ++i; - if (sock->createtime < oldest_allowed) - { - sock->cull(); - delete sock; - } - } + sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec)); + return MOD_RES_ALLOW; } - void OnUnloadModule(Module* mod) + void OnUnloadModule(Module* mod) CXX11_OVERRIDE { - for (std::set<HttpServerSocket*>::const_iterator i = sockets.begin(); i != sockets.end(); ) + for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ) { HttpServerSocket* sock = *i; ++i; - if (sock->GetIOHook() == mod) + if (sock->GetModHook(mod)) { sock->cull(); delete sock; @@ -412,20 +428,17 @@ class ModuleHttpServer : public Module } } - CullResult cull() + CullResult cull() CXX11_OVERRIDE { - std::set<HttpServerSocket*> local; - local.swap(sockets); - for (std::set<HttpServerSocket*>::const_iterator i = local.begin(); i != local.end(); ++i) + for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i) { HttpServerSocket* sock = *i; - sock->cull(); - delete sock; + sock->AddToCull(); } return Module::cull(); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides HTTP serving facilities to modules", VF_VENDOR); } diff --git a/src/modules/m_httpd_acl.cpp b/src/modules/m_httpd_acl.cpp index c25cabc0a..2dbc1be69 100644 --- a/src/modules/m_httpd_acl.cpp +++ b/src/modules/m_httpd_acl.cpp @@ -19,10 +19,7 @@ #include "inspircd.h" -#include "httpd.h" -#include "protocol.h" - -/* $ModDesc: Provides access control lists (passwording of resources, ip restrictions etc) to m_httpd.so dependent modules */ +#include "modules/httpd.h" class HTTPACL { @@ -37,19 +34,22 @@ class HTTPACL const std::string &set_whitelist, const std::string &set_blacklist) : path(set_path), username(set_username), password(set_password), whitelist(set_whitelist), blacklist(set_blacklist) { } - - ~HTTPACL() { } }; -class ModuleHTTPAccessList : public Module +class ModuleHTTPAccessList : public Module, public HTTPACLEventListener { - std::string stylesheet; std::vector<HTTPACL> acl_list; + HTTPdAPI API; public: + ModuleHTTPAccessList() + : HTTPACLEventListener(this) + , API(this) + { + } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { acl_list.clear(); ConfigTagList acls = ServerInstance->Config->ConfTags("httpdacl"); @@ -67,16 +67,16 @@ class ModuleHTTPAccessList : public Module while (sep.GetToken(type)) { - if (type == "password") + if (stdalgo::string::equalsci(type, "password")) { username = c->getString("username"); password = c->getString("password"); } - else if (type == "whitelist") + else if (stdalgo::string::equalsci(type, "whitelist")) { whitelist = c->getString("whitelist"); } - else if (type == "blacklist") + else if (stdalgo::string::equalsci(type, "blacklist")) { blacklist = c->getString("blacklist"); } @@ -86,38 +86,29 @@ class ModuleHTTPAccessList : public Module } } - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "Read ACL: path=%s pass=%s whitelist=%s blacklist=%s", path.c_str(), + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Read ACL: path=%s pass=%s whitelist=%s blacklist=%s", path.c_str(), password.c_str(), whitelist.c_str(), blacklist.c_str()); acl_list.push_back(HTTPACL(path, username, password, whitelist, blacklist)); } } - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnEvent, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void BlockAccess(HTTPRequest* http, int returnval, const std::string &extraheaderkey = "", const std::string &extraheaderval="") + void BlockAccess(HTTPRequest* http, unsigned int returnval, const std::string &extraheaderkey = "", const std::string &extraheaderval="") { - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "BlockAccess (%d)", returnval); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BlockAccess (%u)", returnval); std::stringstream data("Access to this resource is denied by an access control list. Please contact your IRC administrator."); HTTPDocumentResponse response(this, *http, &data, returnval); - response.headers.SetHeader("X-Powered-By", "m_httpd_acl.so"); + response.headers.SetHeader("X-Powered-By", MODNAME); if (!extraheaderkey.empty()) response.headers.SetHeader(extraheaderkey, extraheaderval); - response.Send(); + API->SendResponse(response); } - void OnEvent(Event& event) + bool IsAccessAllowed(HTTPRequest* http) { - if (event.id == "httpd_acl") { - ServerInstance->Logs->Log("m_http_stats", DEBUG,"Handling httpd acl event"); - HTTPRequest* http = (HTTPRequest*)&event; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Handling httpd acl event"); for (std::vector<HTTPACL>::const_iterator this_acl = acl_list.begin(); this_acl != acl_list.end(); ++this_acl) { @@ -133,10 +124,10 @@ class ModuleHTTPAccessList : public Module { if (InspIRCd::Match(http->GetIP(), entry, ascii_case_insensitive_map)) { - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "Denying access to blacklisted resource %s (matched by pattern %s) from ip %s (matched by entry %s)", + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Denying access to blacklisted resource %s (matched by pattern %s) from ip %s (matched by entry %s)", http->GetURI().c_str(), this_acl->path.c_str(), http->GetIP().c_str(), entry.c_str()); BlockAccess(http, 403); - return; + return false; } } } @@ -155,16 +146,16 @@ class ModuleHTTPAccessList : public Module if (!allow_access) { - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "Denying access to whitelisted resource %s (matched by pattern %s) from ip %s (Not in whitelist)", + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Denying access to whitelisted resource %s (matched by pattern %s) from ip %s (Not in whitelist)", http->GetURI().c_str(), this_acl->path.c_str(), http->GetIP().c_str()); BlockAccess(http, 403); - return; + return false; } } if (!this_acl->password.empty() && !this_acl->username.empty()) { /* Password auth, first look to see if we have a basic authentication header */ - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "Checking HTTP auth password for resource %s (matched by pattern %s) from ip %s, against username %s", + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Checking HTTP auth password for resource %s (matched by pattern %s) from ip %s, against username %s", http->GetURI().c_str(), this_acl->path.c_str(), http->GetIP().c_str(), this_acl->username.c_str()); if (http->headers->IsSet("Authorization")) @@ -183,7 +174,7 @@ class ModuleHTTPAccessList : public Module sep.GetToken(base64); std::string userpass = Base64ToBin(base64); - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "HTTP authorization: %s (%s)", userpass.c_str(), base64.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP authorization: %s (%s)", userpass.c_str(), base64.c_str()); irc::sepstream userpasspair(userpass, ':'); if (userpasspair.GetToken(user)) @@ -193,8 +184,8 @@ class ModuleHTTPAccessList : public Module /* Access granted if username and password are correct */ if (user == this_acl->username && pass == this_acl->password) { - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "HTTP authorization: password and username match"); - return; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP authorization: password and username match"); + return true; } else /* Invalid password */ @@ -213,22 +204,27 @@ class ModuleHTTPAccessList : public Module /* No password given at all, access denied */ BlockAccess(http, 401, "WWW-Authenticate", "Basic realm=\"Restricted Object\""); } + return false; } /* A path may only match one ACL (the first it finds in the config file) */ - return; + break; } } } + return true; } - virtual ~ModuleHTTPAccessList() + ModResult OnHTTPACLCheck(HTTPRequest& req) CXX11_OVERRIDE { + if (IsAccessAllowed(&req)) + return MOD_RES_PASSTHRU; + return MOD_RES_DENY; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Provides access control lists (passwording of resources, ip restrictions etc) to m_httpd.so dependent modules", VF_VENDOR); + return Version("Provides access control lists (passwording of resources, ip restrictions etc) to m_httpd dependent modules", VF_VENDOR); } }; diff --git a/src/modules/m_httpd_config.cpp b/src/modules/m_httpd_config.cpp index 62314cd7e..f729b2774 100644 --- a/src/modules/m_httpd_config.cpp +++ b/src/modules/m_httpd_config.cpp @@ -19,96 +19,58 @@ #include "inspircd.h" -#include "httpd.h" -#include "protocol.h" +#include "modules/httpd.h" -/* $ModDesc: Allows for the server configuration to be viewed over HTTP via m_httpd.so */ - -class ModuleHttpConfig : public Module +class ModuleHttpConfig : public Module, public HTTPRequestEventListener { + HTTPdAPI API; + public: - void init() + ModuleHttpConfig() + : HTTPRequestEventListener(this) + , API(this) { - Implementation eventlist[] = { I_OnEvent }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - std::string Sanitize(const std::string &str) + ModResult OnHTTPRequest(HTTPRequest& request) CXX11_OVERRIDE { - std::string ret; + if ((request.GetURI() != "/config") && (request.GetURI() != "/config/")) + return MOD_RES_PASSTHRU; - for (std::string::const_iterator x = str.begin(); x != str.end(); ++x) - { - switch (*x) - { - case '<': - ret += "<"; - break; - case '>': - ret += ">"; - break; - case '&': - ret += "&"; - break; - case '"': - ret += """; - break; - default: - if (*x < 32 || *x > 126) - { - int n = *x; - ret += ("&#" + ConvToStr(n) + ";"); - } - else - ret += *x; - break; - } - } - return ret; - } + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Handling request for the HTTP /config route"); + std::stringstream buffer; - void OnEvent(Event& event) - { - std::stringstream data(""); - - if (event.id == "httpd_url") + ConfigDataHash& config = ServerInstance->Config->config_data; + for (ConfigDataHash::const_iterator citer = config.begin(); citer != config.end(); ++citer) { - ServerInstance->Logs->Log("m_http_stats", DEBUG,"Handling httpd event"); - HTTPRequest* http = (HTTPRequest*)&event; + // Show the location of the tag in a comment. + ConfigTag* tag = citer->second; + buffer << "# " << tag->getTagLocation() << std::endl + << '<' << tag->tag << ' '; - if ((http->GetURI() == "/config") || (http->GetURI() == "/config/")) + // Print out the tag with all keys aligned vertically. + const std::string indent(tag->tag.length() + 2, ' '); + const ConfigItems& items = tag->getItems(); + for (ConfigItems::const_iterator kiter = items.begin(); kiter != items.end(); ) { - data << "<html><head><title>InspIRCd Configuration</title></head><body>"; - data << "<h1>InspIRCd Configuration</h1><p>"; - - for (ConfigDataHash::iterator x = ServerInstance->Config->config_data.begin(); x != ServerInstance->Config->config_data.end(); ++x) - { - data << "<" << x->first << " "; - ConfigTag* tag = x->second; - for (std::vector<KeyVal>::const_iterator j = tag->getItems().begin(); j != tag->getItems().end(); j++) - { - data << Sanitize(j->first) << "="" << Sanitize(j->second) << "" "; - } - data << "><br>"; - } - - data << "</body></html>"; - /* Send the document back to m_httpd */ - HTTPDocumentResponse response(this, *http, &data, 200); - response.headers.SetHeader("X-Powered-By", "m_httpd_config.so"); - response.headers.SetHeader("Content-Type", "text/html"); - response.Send(); + ConfigItems::const_iterator curr = kiter++; + buffer << curr->first << "=\"" << ServerConfig::Escape(curr->second) << '"'; + if (kiter != items.end()) + buffer << std::endl << indent; } + buffer << '>' << std::endl << std::endl; } - } - virtual ~ModuleHttpConfig() - { + HTTPDocumentResponse response(this, request, &buffer, 200); + response.headers.SetHeader("X-Powered-By", MODNAME); + response.headers.SetHeader("Content-Type", "text/plain"); + API->SendResponse(response); + return MOD_RES_DENY; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Allows for the server configuration to be viewed over HTTP via m_httpd.so", VF_VENDOR); + return Version("Allows for the server configuration to be viewed over HTTP via m_httpd", VF_VENDOR); } }; diff --git a/src/modules/m_httpd_stats.cpp b/src/modules/m_httpd_stats.cpp index e17bf514f..6db292eb3 100644 --- a/src/modules/m_httpd_stats.cpp +++ b/src/modules/m_httpd_stats.cpp @@ -22,22 +22,19 @@ #include "inspircd.h" -#include "httpd.h" +#include "modules/httpd.h" #include "xline.h" -#include "protocol.h" -/* $ModDesc: Provides statistics over HTTP via m_httpd.so */ - -class ModuleHttpStats : public Module +class ModuleHttpStats : public Module, public HTTPRequestEventListener { - static std::map<char, char const*> const &entities; + static const insp::flat_map<char, char const*>& entities; + HTTPdAPI API; public: - - void init() + ModuleHttpStats() + : HTTPRequestEventListener(this) + , API(this) { - Implementation eventlist[] = { I_OnEvent }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } std::string Sanitize(const std::string &str) @@ -47,7 +44,7 @@ class ModuleHttpStats : public Module for (std::string::const_iterator x = str.begin(); x != str.end(); ++x) { - std::map<char, char const*>::const_iterator it = entities.find(*x); + insp::flat_map<char, char const*>::const_iterator it = entities.find(*x); if (it != entities.end()) { @@ -91,35 +88,35 @@ class ModuleHttpStats : public Module data << "</metadata>"; } - void OnEvent(Event& event) + ModResult HandleRequest(HTTPRequest* http) { std::stringstream data(""); - if (event.id == "httpd_url") { - ServerInstance->Logs->Log("m_http_stats", DEBUG,"Handling httpd event"); - HTTPRequest* http = (HTTPRequest*)&event; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Handling httpd event"); if ((http->GetURI() == "/stats") || (http->GetURI() == "/stats/")) { - data << "<inspircdstats><server><name>" << ServerInstance->Config->ServerName << "</name><gecos>" - << Sanitize(ServerInstance->Config->ServerDesc) << "</gecos><version>" + data << "<inspircdstats><server><name>" << ServerInstance->Config->ServerName << "</name><description>" + << Sanitize(ServerInstance->Config->ServerDesc) << "</description><version>" << Sanitize(ServerInstance->GetVersionString()) << "</version></server>"; data << "<general>"; - data << "<usercount>" << ServerInstance->Users->clientlist->size() << "</usercount>"; - data << "<channelcount>" << ServerInstance->chanlist->size() << "</channelcount>"; + data << "<usercount>" << ServerInstance->Users->GetUsers().size() << "</usercount>"; + data << "<channelcount>" << ServerInstance->GetChans().size() << "</channelcount>"; data << "<opercount>" << ServerInstance->Users->all_opers.size() << "</opercount>"; - data << "<socketcount>" << (ServerInstance->SE->GetUsedFds()) << "</socketcount><socketmax>" << ServerInstance->SE->GetMaxFds() << "</socketmax><socketengine>" << ServerInstance->SE->GetName() << "</socketengine>"; - - time_t current_time = 0; - current_time = ServerInstance->Time(); - time_t server_uptime = current_time - ServerInstance->startup_time; - struct tm* stime; - stime = gmtime(&server_uptime); - data << "<uptime><days>" << stime->tm_yday << "</days><hours>" << stime->tm_hour << "</hours><mins>" << stime->tm_min << "</mins><secs>" << stime->tm_sec << "</secs><boot_time_t>" << ServerInstance->startup_time << "</boot_time_t></uptime>"; + data << "<socketcount>" << (SocketEngine::GetUsedFds()) << "</socketcount><socketmax>" << SocketEngine::GetMaxFds() << "</socketmax>"; + data << "<uptime><boot_time_t>" << ServerInstance->startup_time << "</boot_time_t></uptime>"; - data << "<isupport>" << Sanitize(ServerInstance->Config->data005) << "</isupport></general><xlines>"; + data << "<isupport>"; + const std::vector<Numeric::Numeric>& isupport = ServerInstance->ISupport.GetLines(); + for (std::vector<Numeric::Numeric>::const_iterator i = isupport.begin(); i != isupport.end(); ++i) + { + const Numeric::Numeric& num = *i; + for (std::vector<std::string>::const_iterator j = num.GetParams().begin(); j != num.GetParams().end()-1; ++j) + data << "<token>" << Sanitize(*j) << "</token>" << std::endl; + } + data << "</isupport></general><xlines>"; std::vector<std::string> xltypes = ServerInstance->XLines->GetAllTypes(); for (std::vector<std::string>::iterator it = xltypes.begin(); it != xltypes.end(); ++it) { @@ -138,35 +135,35 @@ class ModuleHttpStats : public Module } data << "</xlines><modulelist>"; - std::vector<std::string> module_names = ServerInstance->Modules->GetAllModuleNames(0); + const ModuleManager::ModuleMap& mods = ServerInstance->Modules->GetModules(); - for (std::vector<std::string>::iterator i = module_names.begin(); i != module_names.end(); ++i) + for (ModuleManager::ModuleMap::const_iterator i = mods.begin(); i != mods.end(); ++i) { - Module* m = ServerInstance->Modules->Find(i->c_str()); - Version v = m->GetVersion(); - data << "<module><name>" << *i << "</name><description>" << Sanitize(v.description) << "</description></module>"; + Version v = i->second->GetVersion(); + data << "<module><name>" << i->first << "</name><description>" << Sanitize(v.description) << "</description></module>"; } data << "</modulelist><channellist>"; - for (chan_hash::const_iterator a = ServerInstance->chanlist->begin(); a != ServerInstance->chanlist->end(); ++a) + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) { - Channel* c = a->second; + Channel* c = i->second; data << "<channel>"; - data << "<usercount>" << c->GetUsers()->size() << "</usercount><channelname>" << Sanitize(c->name) << "</channelname>"; + data << "<usercount>" << c->GetUsers().size() << "</usercount><channelname>" << Sanitize(c->name) << "</channelname>"; data << "<channeltopic>"; data << "<topictext>" << Sanitize(c->topic) << "</topictext>"; data << "<setby>" << Sanitize(c->setby) << "</setby>"; data << "<settime>" << c->topicset << "</settime>"; data << "</channeltopic>"; data << "<channelmodes>" << Sanitize(c->ChanModes(true)) << "</channelmodes>"; - const UserMembList* ulist = c->GetUsers(); - for (UserMembCIter x = ulist->begin(); x != ulist->end(); ++x) + const Channel::MemberMap& ulist = c->GetUsers(); + for (Channel::MemberMap::const_iterator x = ulist.begin(); x != ulist.end(); ++x) { Membership* memb = x->second; data << "<channelmember><uid>" << memb->user->uuid << "</uid><privs>" - << Sanitize(c->GetAllPrefixChars(x->first)) << "</privs><modes>" + << Sanitize(memb->GetAllPrefixChars()) << "</privs><modes>" << memb->modes << "</modes>"; DumpMeta(data, memb); data << "</channelmember>"; @@ -179,23 +176,24 @@ class ModuleHttpStats : public Module data << "</channellist><userlist>"; - for (user_hash::const_iterator a = ServerInstance->Users->clientlist->begin(); a != ServerInstance->Users->clientlist->end(); ++a) + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i) { - User* u = a->second; + User* u = i->second; data << "<user>"; data << "<nickname>" << u->nick << "</nickname><uuid>" << u->uuid << "</uuid><realhost>" - << u->host << "</realhost><displayhost>" << u->dhost << "</displayhost><gecos>" - << Sanitize(u->fullname) << "</gecos><server>" << u->server << "</server>"; - if (IS_AWAY(u)) + << u->GetRealHost() << "</realhost><displayhost>" << u->GetDisplayedHost() << "</displayhost><realname>" + << Sanitize(u->GetRealName()) << "</realname><server>" << u->server->GetName() << "</server>"; + if (u->IsAway()) data << "<away>" << Sanitize(u->awaymsg) << "</away><awaytime>" << u->awaytime << "</awaytime>"; - if (IS_OPER(u)) - data << "<opertype>" << Sanitize(u->oper->NameStr()) << "</opertype>"; - data << "<modes>" << u->FormatModes() << "</modes><ident>" << Sanitize(u->ident) << "</ident>"; + if (u->IsOper()) + data << "<opertype>" << Sanitize(u->oper->name) << "</opertype>"; + data << "<modes>" << u->GetModeLetters().substr(1) << "</modes><ident>" << Sanitize(u->ident) << "</ident>"; LocalUser* lu = IS_LOCAL(u); if (lu) data << "<port>" << lu->GetServerPort() << "</port><servaddr>" - << irc::sockets::satouser(lu->server_sa) << "</servaddr>"; + << lu->server_sa.str() << "</servaddr>"; data << "<ipaddress>" << u->GetIPString() << "</ipaddress>"; DumpMeta(data, u); @@ -205,15 +203,15 @@ class ModuleHttpStats : public Module data << "</userlist><serverlist>"; - ProtoServerList sl; + ProtocolInterface::ServerList sl; ServerInstance->PI->GetServerList(sl); - for (ProtoServerList::iterator b = sl.begin(); b != sl.end(); ++b) + for (ProtocolInterface::ServerList::const_iterator b = sl.begin(); b != sl.end(); ++b) { data << "<server>"; data << "<servername>" << b->servername << "</servername>"; data << "<parentname>" << b->parentname << "</parentname>"; - data << "<gecos>" << Sanitize(b->gecos) << "</gecos>"; + data << "<description>" << Sanitize(b->description) << "</description>"; data << "<usercount>" << b->usercount << "</usercount>"; // This is currently not implemented, so, commented out. // data << "<opercount>" << b->opercount << "</opercount>"; @@ -221,30 +219,41 @@ class ModuleHttpStats : public Module data << "</server>"; } - data << "</serverlist></inspircdstats>"; + data << "</serverlist><commandlist>"; + + const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands(); + for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i) + { + data << "<command><name>" << i->second->name << "</name><usecount>" << i->second->use_count << "</usecount></command>"; + } + + data << "</commandlist></inspircdstats>"; /* Send the document back to m_httpd */ HTTPDocumentResponse response(this, *http, &data, 200); - response.headers.SetHeader("X-Powered-By", "m_httpd_stats.so"); + response.headers.SetHeader("X-Powered-By", MODNAME); response.headers.SetHeader("Content-Type", "text/xml"); - response.Send(); + API->SendResponse(response); + return MOD_RES_DENY; // Handled } } + return MOD_RES_PASSTHRU; } - virtual ~ModuleHttpStats() + ModResult OnHTTPRequest(HTTPRequest& req) CXX11_OVERRIDE { + return HandleRequest(&req); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Provides statistics over HTTP via m_httpd.so", VF_VENDOR); + return Version("Provides statistics over HTTP via m_httpd", VF_VENDOR); } }; -static std::map<char, char const*> const &init_entities() +static const insp::flat_map<char, char const*>& init_entities() { - static std::map<char, char const*> entities; + static insp::flat_map<char, char const*> entities; entities['<'] = "lt"; entities['>'] = "gt"; entities['&'] = "amp"; @@ -252,6 +261,6 @@ static std::map<char, char const*> const &init_entities() return entities; } -std::map<char, char const*> const &ModuleHttpStats::entities = init_entities (); +const insp::flat_map<char, char const*>& ModuleHttpStats::entities = init_entities(); MODULE_INIT(ModuleHttpStats) diff --git a/src/modules/m_ident.cpp b/src/modules/m_ident.cpp index f0ced1db7..ca12a9ba3 100644 --- a/src/modules/m_ident.cpp +++ b/src/modules/m_ident.cpp @@ -24,8 +24,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for RFC1413 ident lookups */ - /* -------------------------------------------------------------- * Note that this is the third incarnation of m_ident. The first * two attempts were pretty crashy, mainly due to the fact we tried @@ -94,7 +92,7 @@ class IdentRequestSocket : public EventHandler { age = ServerInstance->Time(); - SetFd(socket(user->server_sa.sa.sa_family, SOCK_STREAM, 0)); + SetFd(socket(user->server_sa.family(), SOCK_STREAM, 0)); if (GetFd() == -1) throw ModuleException("Could not create socket"); @@ -107,7 +105,7 @@ class IdentRequestSocket : public EventHandler memcpy(&bindaddr, &user->server_sa, sizeof(bindaddr)); memcpy(&connaddr, &user->client_sa, sizeof(connaddr)); - if (connaddr.sa.sa_family == AF_INET6) + if (connaddr.family() == AF_INET6) { bindaddr.in6.sin6_port = 0; connaddr.in6.sin6_port = htons(113); @@ -119,39 +117,38 @@ class IdentRequestSocket : public EventHandler } /* Attempt to bind (ident requests must come from the ip the query is referring to */ - if (ServerInstance->SE->Bind(GetFd(), bindaddr) < 0) + if (SocketEngine::Bind(GetFd(), bindaddr) < 0) { this->Close(); throw ModuleException("failed to bind()"); } - ServerInstance->SE->NonBlocking(GetFd()); + SocketEngine::NonBlocking(GetFd()); /* Attempt connection (nonblocking) */ - if (ServerInstance->SE->Connect(this, &connaddr.sa, connaddr.sa_size()) == -1 && errno != EINPROGRESS) + if (SocketEngine::Connect(this, connaddr) == -1 && errno != EINPROGRESS) { this->Close(); throw ModuleException("connect() failed"); } /* Add fd to socket engine */ - if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_READ | FD_WANT_POLL_WRITE)) + if (!SocketEngine::AddFd(this, FD_WANT_NO_READ | FD_WANT_POLL_WRITE)) { this->Close(); throw ModuleException("out of fds"); } } - virtual void OnConnected() + void OnEventHandlerWrite() CXX11_OVERRIDE { - ServerInstance->Logs->Log("m_ident",DEBUG,"OnConnected()"); - ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); + SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); char req[32]; /* Build request in the form 'localport,remoteport\r\n' */ int req_size; - if (user->client_sa.sa.sa_family == AF_INET6) + if (user->client_sa.family() == AF_INET6) req_size = snprintf(req, sizeof(req), "%d,%d\r\n", ntohs(user->client_sa.in6.sin6_port), ntohs(user->server_sa.in6.sin6_port)); else @@ -161,34 +158,10 @@ class IdentRequestSocket : public EventHandler /* Send failed if we didnt write the whole ident request -- * might as well give up if this happens! */ - if (ServerInstance->SE->Send(this, req, req_size, 0) < req_size) + if (SocketEngine::Send(this, req, req_size, 0) < req_size) done = true; } - virtual void HandleEvent(EventType et, int errornum = 0) - { - switch (et) - { - case EVENT_READ: - /* fd readable event, received ident response */ - ReadResponse(); - break; - case EVENT_WRITE: - /* fd writeable event, successfully connected! */ - OnConnected(); - break; - case EVENT_ERROR: - /* fd error event, ohshi- */ - ServerInstance->Logs->Log("m_ident",DEBUG,"EVENT_ERROR"); - /* We *must* Close() here immediately or we get a - * huge storm of EVENT_ERROR events! - */ - Close(); - done = true; - break; - } - } - void Close() { /* Remove ident socket from engine, and close it, but dont detatch it @@ -196,10 +169,8 @@ class IdentRequestSocket : public EventHandler */ if (GetFd() > -1) { - ServerInstance->Logs->Log("m_ident",DEBUG,"Close ident socket %d", GetFd()); - ServerInstance->SE->DelFd(this); - ServerInstance->SE->Close(GetFd()); - this->SetFd(-1); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Close ident socket %d", GetFd()); + SocketEngine::Close(this); } } @@ -208,13 +179,13 @@ class IdentRequestSocket : public EventHandler return done; } - void ReadResponse() + void OnEventHandlerRead() CXX11_OVERRIDE { /* We don't really need to buffer for incomplete replies here, since IDENT replies are * extremely short - there is *no* sane reason it'd be in more than one packet */ - char ibuf[MAXBUF]; - int recvresult = ServerInstance->SE->Recv(this, ibuf, MAXBUF-1, 0); + char ibuf[256]; + int recvresult = SocketEngine::Recv(this, ibuf, sizeof(ibuf)-1, 0); /* Close (but don't delete from memory) our socket * and flag as done since the ident lookup has finished @@ -228,7 +199,7 @@ class IdentRequestSocket : public EventHandler if (recvresult < 3) return; - ServerInstance->Logs->Log("m_ident",DEBUG,"ReadResponse()"); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "ReadResponse()"); /* Truncate at the first null character, but first make sure * there is at least one null char (at the end of the buffer). @@ -260,67 +231,70 @@ class IdentRequestSocket : public EventHandler * we're done. */ result += *i; - if (!ServerInstance->IsIdent(result.c_str())) + if (!ServerInstance->IsIdent(result)) { result.erase(result.end()-1); break; } } } -}; -class ModuleIdent : public Module -{ - int RequestTimeout; - SimpleExtItem<IdentRequestSocket> ext; - public: - ModuleIdent() : ext("ident_socket", this) + void OnEventHandlerError(int errornum) CXX11_OVERRIDE { + Close(); + done = true; } - void init() + CullResult cull() CXX11_OVERRIDE { - ServerInstance->Modules->AddService(ext); - OnRehash(NULL); - Implementation eventlist[] = { - I_OnRehash, I_OnUserInit, I_OnCheckReady, - I_OnUserDisconnect, I_OnSetConnectClass - }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + Close(); + return EventHandler::cull(); } +}; - ~ModuleIdent() +class ModuleIdent : public Module +{ + int RequestTimeout; + bool NoLookupPrefix; + SimpleExtItem<IdentRequestSocket, stdalgo::culldeleter> ext; + public: + ModuleIdent() + : ext("ident_socket", ExtensionItem::EXT_USER, this) { } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for RFC1413 ident lookups", VF_VENDOR); } - virtual void OnRehash(User *user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - RequestTimeout = ServerInstance->Config->ConfValue("ident")->getInt("timeout", 5); - if (!RequestTimeout) - RequestTimeout = 5; + ConfigTag* tag = ServerInstance->Config->ConfValue("ident"); + RequestTimeout = tag->getDuration("timeout", 5, 1); + NoLookupPrefix = tag->getBool("nolookupprefix", false); } - void OnUserInit(LocalUser *user) + void OnUserInit(LocalUser *user) CXX11_OVERRIDE { + // The ident protocol requires that clients are connecting over a protocol with ports. + if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6) + return; + ConfigTag* tag = user->MyClass->config; if (!tag->getBool("useident", true)) return; - user->WriteServ("NOTICE Auth :*** Looking up your ident..."); + user->WriteNotice("*** Looking up your ident..."); try { - IdentRequestSocket *isock = new IdentRequestSocket(IS_LOCAL(user)); + IdentRequestSocket *isock = new IdentRequestSocket(user); ext.set(user, isock); } catch (ModuleException &e) { - ServerInstance->Logs->Log("m_ident",DEBUG,"Ident exception: %s", e.GetReason()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Ident exception: " + e.GetReason()); } } @@ -328,18 +302,17 @@ class ModuleIdent : public Module * creating a Timer object and especially better than creating a * Timer per ident lookup! */ - virtual ModResult OnCheckReady(LocalUser *user) + ModResult OnCheckReady(LocalUser *user) CXX11_OVERRIDE { /* Does user have an ident socket attached at all? */ IdentRequestSocket *isock = ext.get(user); if (!isock) { - ServerInstance->Logs->Log("m_ident",DEBUG, "No ident socket :("); + if ((NoLookupPrefix) && (user->ident[0] != '~')) + user->ident.insert(user->ident.begin(), 1, '~'); return MOD_RES_PASSTHRU; } - ServerInstance->Logs->Log("m_ident",DEBUG, "Has ident_socket"); - time_t compare = isock->age; compare += RequestTimeout; @@ -347,28 +320,24 @@ class ModuleIdent : public Module if (ServerInstance->Time() >= compare) { /* Ident timeout */ - user->WriteServ("NOTICE Auth :*** Ident request timed out."); - ServerInstance->Logs->Log("m_ident",DEBUG, "Timeout"); + user->WriteNotice("*** Ident request timed out."); } else if (!isock->HasResult()) { // time still good, no result yet... hold the registration - ServerInstance->Logs->Log("m_ident",DEBUG, "No result yet"); return MOD_RES_DENY; } - ServerInstance->Logs->Log("m_ident",DEBUG, "Yay, result!"); - /* wooo, got a result (it will be good, or bad) */ if (isock->result.empty()) { user->ident.insert(user->ident.begin(), 1, '~'); - user->WriteServ("NOTICE Auth :*** Could not find your ident, using %s instead.", user->ident.c_str()); + user->WriteNotice("*** Could not find your ident, using " + user->ident + " instead."); } else { user->ident = isock->result; - user->WriteServ("NOTICE Auth :*** Found your ident, '%s'", user->ident.c_str()); + user->WriteNotice("*** Found your ident, '" + user->ident + "'"); } user->InvalidateCache(); @@ -377,35 +346,12 @@ class ModuleIdent : public Module return MOD_RES_PASSTHRU; } - ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { if (myclass->config->getBool("requireident") && user->ident[0] == '~') return MOD_RES_DENY; return MOD_RES_PASSTHRU; } - - virtual void OnCleanup(int target_type, void *item) - { - /* Module unloading, tidy up users */ - if (target_type == TYPE_USER) - { - LocalUser* user = IS_LOCAL((User*) item); - if (user) - OnUserDisconnect(user); - } - } - - virtual void OnUserDisconnect(LocalUser *user) - { - /* User disconnect (generic socket detatch event) */ - IdentRequestSocket *isock = ext.get(user); - if (isock) - { - isock->Close(); - ext.unset(user); - } - } }; MODULE_INIT(ModuleIdent) - diff --git a/src/modules/m_inviteexception.cpp b/src/modules/m_inviteexception.cpp index 747a3b30a..1317e6e57 100644 --- a/src/modules/m_inviteexception.cpp +++ b/src/modules/m_inviteexception.cpp @@ -22,10 +22,7 @@ #include "inspircd.h" -#include "u_listmode.h" - -/* $ModDesc: Provides support for the +I channel mode */ -/* $ModDep: ../../include/u_listmode.h */ +#include "listmode.h" /* * Written by Om <om@inspircd.org>, April 2005. @@ -54,27 +51,17 @@ public: { } - void init() - { - ServerInstance->Modules->AddService(ie); - - OnRehash(NULL); - ie.DoImplements(this); - Implementation eventlist[] = { I_On005Numeric, I_OnCheckInvite, I_OnCheckKey, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" INVEX=I"); + tokens["INVEX"] = ConvToStr(ie.GetModeChar()); } - ModResult OnCheckInvite(User* user, Channel* chan) + ModResult OnCheckInvite(User* user, Channel* chan) CXX11_OVERRIDE { - modelist* list = ie.extItem.get(chan); + ListModeBase::ModeList* list = ie.GetList(chan); if (list) { - for (modelist::iterator it = list->begin(); it != list->end(); it++) + for (ListModeBase::ModeList::iterator it = list->begin(); it != list->end(); it++) { if (chan->CheckBan(user, it->mask)) { @@ -86,25 +73,20 @@ public: return MOD_RES_PASSTHRU; } - ModResult OnCheckKey(User* user, Channel* chan, const std::string& key) + ModResult OnCheckKey(User* user, Channel* chan, const std::string& key) CXX11_OVERRIDE { if (invite_bypass_key) return OnCheckInvite(user, chan); return MOD_RES_PASSTHRU; } - void OnSyncChannel(Channel* chan, Module* proto, void* opaque) - { - ie.DoSyncChannel(chan, proto, opaque); - } - - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { invite_bypass_key = ServerInstance->Config->ConfValue("inviteexception")->getBool("bypasskey", true); ie.DoRehash(); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the +I channel mode", VF_VENDOR); } diff --git a/src/modules/m_ircv3.cpp b/src/modules/m_ircv3.cpp index b7dd0e81b..14b1cf8a1 100644 --- a/src/modules/m_ircv3.cpp +++ b/src/modules/m_ircv3.cpp @@ -16,227 +16,161 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* $ModDesc: Provides support for extended-join, away-notify and account-notify CAP capabilities */ - #include "inspircd.h" -#include "account.h" -#include "m_cap.h" +#include "modules/account.h" +#include "modules/away.h" +#include "modules/cap.h" +#include "modules/ircv3.h" -class ModuleIRCv3 : public Module +class AwayMessage : public ClientProtocol::Message { - GenericCap cap_accountnotify; - GenericCap cap_awaynotify; - GenericCap cap_extendedjoin; - bool accountnotify; - bool awaynotify; - bool extendedjoin; - - CUList last_excepts; - - void WriteNeighboursWithExt(User* user, const std::string& line, const LocalIntExt& ext) + public: + AwayMessage(User* user) + : ClientProtocol::Message("AWAY", user) { - UserChanList chans(user->chans); - - std::map<User*, bool> exceptions; - FOREACH_MOD(I_OnBuildNeighborList, OnBuildNeighborList(user, chans, exceptions)); - - // Send it to all local users who were explicitly marked as neighbours by modules and have the required ext - for (std::map<User*, bool>::const_iterator i = exceptions.begin(); i != exceptions.end(); ++i) - { - LocalUser* u = IS_LOCAL(i->first); - if ((u) && (i->second) && (ext.get(u))) - u->Write(line); - } - - // Now consider sending it to all other users who has at least a common channel with the user - std::set<User*> already_sent; - for (UCListIter i = chans.begin(); i != chans.end(); ++i) - { - const UserMembList* userlist = (*i)->GetUsers(); - for (UserMembList::const_iterator m = userlist->begin(); m != userlist->end(); ++m) - { - /* - * Send the line if the channel member in question meets all of the following criteria: - * - local - * - not the user who is doing the action (i.e. whose channels we're iterating) - * - has the given extension - * - not on the except list built by modules - * - we haven't sent the line to the member yet - * - */ - LocalUser* member = IS_LOCAL(m->first); - if ((member) && (member != user) && (ext.get(member)) && (exceptions.find(member) == exceptions.end()) && (already_sent.insert(member).second)) - member->Write(line); - } - } + SetParams(user, user->awaymsg); } - public: - ModuleIRCv3() : cap_accountnotify(this, "account-notify"), - cap_awaynotify(this, "away-notify"), - cap_extendedjoin(this, "extended-join") + AwayMessage() + : ClientProtocol::Message("AWAY") { } - void init() + void SetParams(User* user, const std::string& awaymsg) { - OnRehash(NULL); - Implementation eventlist[] = { I_OnUserJoin, I_OnPostJoin, I_OnSetAway, I_OnEvent, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + // Going away: 1 parameter which is the away reason + // Back from away: no parameter + if (!awaymsg.empty()) + PushParam(awaymsg); } +}; + +class JoinHook : public ClientProtocol::EventHook +{ + ClientProtocol::Events::Join extendedjoinmsg; - void OnRehash(User* user) + public: + const std::string asterisk; + ClientProtocol::EventProvider awayprotoev; + AwayMessage awaymsg; + Cap::Capability extendedjoincap; + Cap::Capability awaycap; + + JoinHook(Module* mod) + : ClientProtocol::EventHook(mod, "JOIN") + , asterisk(1, '*') + , awayprotoev(mod, "AWAY") + , extendedjoincap(mod, "extended-join") + , awaycap(mod, "away-notify") { - ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3"); - accountnotify = conf->getBool("accountnotify", conf->getBool("accoutnotify", true)); - awaynotify = conf->getBool("awaynotify", true); - extendedjoin = conf->getBool("extendedjoin", true); } - void OnEvent(Event& ev) + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE { - if (awaynotify) - cap_awaynotify.HandleEvent(ev); - if (extendedjoin) - cap_extendedjoin.HandleEvent(ev); + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + + // An extended join has two extra parameters: + // First the account name of the joining user or an asterisk if the user is not logged in. + // The second parameter is the realname of the joining user. + + Membership* const memb = join.GetMember(); + const std::string* account = &asterisk; + const AccountExtItem* const accountext = GetAccountExtItem(); + if (accountext) + { + const std::string* accountname = accountext->get(memb->user); + if (accountname) + account = accountname; + } + + extendedjoinmsg.ClearParams(); + extendedjoinmsg.SetSource(join); + extendedjoinmsg.PushParamRef(memb->chan->name); + extendedjoinmsg.PushParamRef(*account); + extendedjoinmsg.PushParamRef(memb->user->GetRealName()); - if (accountnotify) + awaymsg.ClearParams(); + if ((memb->user->IsAway()) && (awaycap.IsActive())) { - cap_accountnotify.HandleEvent(ev); - - if (ev.id == "account_login") - { - AccountEvent* ae = static_cast<AccountEvent*>(&ev); - - // :nick!user@host ACCOUNT account - // or - // :nick!user@host ACCOUNT * - std::string line = ":" + ae->user->GetFullHost() + " ACCOUNT "; - if (ae->account.empty()) - line += "*"; - else - line += std::string(ae->account); - - WriteNeighboursWithExt(ae->user, line, cap_accountnotify.ext); - } + awaymsg.SetSource(join); + awaymsg.SetParams(memb->user, memb->user->awaymsg); } } - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE { - // Remember who is not going to see the JOIN because of other modules - if ((awaynotify) && (IS_AWAY(memb->user))) - last_excepts = excepts; + if (extendedjoincap.get(user)) + messagelist.front() = &extendedjoinmsg; - if (!extendedjoin) - return; + if ((!awaymsg.GetParams().empty()) && (awaycap.get(user))) + messagelist.push_back(&awaymsg); - /* - * Send extended joins to clients who have the extended-join capability. - * An extended join looks like this: - * - * :nick!user@host JOIN #chan account :realname - * - * account is the joining user's account if he's logged in, otherwise it's an asterisk (*). - */ + return MOD_RES_PASSTHRU; + } +}; - std::string line; - std::string mode; +class ModuleIRCv3 + : public Module + , public AccountEventListener + , public Away::EventListener +{ + Cap::Capability cap_accountnotify; + JoinHook joinhook; - const UserMembList* userlist = memb->chan->GetUsers(); - for (UserMembCIter it = userlist->begin(); it != userlist->end(); ++it) - { - // Send the extended join line if the current member is local, has the extended-join cap and isn't excepted - User* member = IS_LOCAL(it->first); - if ((member) && (cap_extendedjoin.ext.get(member)) && (excepts.find(member) == excepts.end())) - { - // Construct the lines we're going to send if we haven't constructed them already - if (line.empty()) - { - bool has_account = false; - line = ":" + memb->user->GetFullHost() + " JOIN " + memb->chan->name + " "; - const AccountExtItem* accountext = GetAccountExtItem(); - if (accountext) - { - std::string* accountname; - accountname = accountext->get(memb->user); - if (accountname) - { - line += *accountname; - has_account = true; - } - } - - if (!has_account) - line += "*"; - - line += " :" + memb->user->fullname; - - // If the joining user received privileges from another module then we must send them as well, - // since silencing the normal join means the MODE will be silenced as well - if (!memb->modes.empty()) - { - const std::string& modefrom = ServerInstance->Config->CycleHostsFromUser ? memb->user->GetFullHost() : ServerInstance->Config->ServerName; - mode = ":" + modefrom + " MODE " + memb->chan->name + " +" + memb->modes; - - for (unsigned int i = 0; i < memb->modes.length(); i++) - mode += " " + memb->user->nick; - } - } - - // Write the JOIN and the MODE, if any - member->Write(line); - if ((!mode.empty()) && (member != memb->user)) - member->Write(mode); - - // Prevent the core from sending the JOIN and MODE to this user - excepts.insert(it->first); - } - } + ClientProtocol::EventProvider accountprotoev; + + public: + ModuleIRCv3() + : AccountEventListener(this) + , Away::EventListener(this) + , cap_accountnotify(this, "account-notify") + , joinhook(this) + , accountprotoev(this, "ACCOUNT") + { } - ModResult OnSetAway(User* user, const std::string &awaymsg) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - if (awaynotify) - { - // Going away: n!u@h AWAY :reason - // Back from away: n!u@h AWAY - std::string line = ":" + user->GetFullHost() + " AWAY"; - if (!awaymsg.empty()) - line += " :" + awaymsg; + ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3"); + cap_accountnotify.SetActive(conf->getBool("accountnotify", true)); + joinhook.awaycap.SetActive(conf->getBool("awaynotify", true)); + joinhook.extendedjoincap.SetActive(conf->getBool("extendedjoin", true)); + } - WriteNeighboursWithExt(user, line, cap_awaynotify.ext); - } - return MOD_RES_PASSTHRU; + void OnAccountChange(User* user, const std::string& newaccount) CXX11_OVERRIDE + { + // Logged in: 1 parameter which is the account name + // Logged out: 1 parameter which is a "*" + ClientProtocol::Message msg("ACCOUNT", user); + const std::string& param = (newaccount.empty() ? joinhook.asterisk : newaccount); + msg.PushParamRef(param); + ClientProtocol::Event accountevent(accountprotoev, msg); + IRCv3::WriteNeighborsWithCap(user, accountevent, cap_accountnotify); } - void OnPostJoin(Membership *memb) + void OnUserAway(User* user) CXX11_OVERRIDE { - if ((!awaynotify) || (!IS_AWAY(memb->user))) + if (!joinhook.awaycap.IsActive()) return; - std::string line = ":" + memb->user->GetFullHost() + " AWAY :" + memb->user->awaymsg; - - const UserMembList* userlist = memb->chan->GetUsers(); - for (UserMembCIter it = userlist->begin(); it != userlist->end(); ++it) - { - // Send the away notify line if the current member is local, has the away-notify cap and isn't excepted - User* member = IS_LOCAL(it->first); - if ((member) && (cap_awaynotify.ext.get(member)) && (last_excepts.find(member) == last_excepts.end()) && (it->second != memb)) - { - member->Write(line); - } - } - - last_excepts.clear(); + // Going away: n!u@h AWAY :reason + AwayMessage msg(user); + ClientProtocol::Event awayevent(joinhook.awayprotoev, msg); + IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap); } - void Prioritize() + void OnUserBack(User* user) CXX11_OVERRIDE { - ServerInstance->Modules->SetPriority(this, I_OnUserJoin, PRIORITY_LAST); + if (!joinhook.awaycap.IsActive()) + return; + + // Back from away: n!u@h AWAY + AwayMessage msg(user); + ClientProtocol::Event awayevent(joinhook.awayprotoev, msg); + IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for extended-join, away-notify and account-notify CAP capabilities", VF_VENDOR); } diff --git a/src/modules/m_ircv3_accounttag.cpp b/src/modules/m_ircv3_accounttag.cpp new file mode 100644 index 000000000..45fcf8022 --- /dev/null +++ b/src/modules/m_ircv3_accounttag.cpp @@ -0,0 +1,62 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/ircv3.h" +#include "modules/account.h" + +class AccountTag : public IRCv3::CapTag<AccountTag> +{ + public: + const std::string* GetValue(const ClientProtocol::Message& msg) const + { + User* const user = msg.GetSourceUser(); + if (!user) + return NULL; + + AccountExtItem* const accextitem = GetAccountExtItem(); + if (!accextitem) + return NULL; + + return accextitem->get(user); + } + + AccountTag(Module* mod) + : IRCv3::CapTag<AccountTag>(mod, "account-tag", "account") + { + } +}; + +class ModuleIRCv3AccountTag : public Module +{ + AccountTag tag; + + public: + ModuleIRCv3AccountTag() + : tag(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the account-tag IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3AccountTag) diff --git a/src/modules/m_ircv3_batch.cpp b/src/modules/m_ircv3_batch.cpp new file mode 100644 index 000000000..df2b00f49 --- /dev/null +++ b/src/modules/m_ircv3_batch.cpp @@ -0,0 +1,216 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" +#include "modules/ircv3_batch.h" + +class BatchMessage : public ClientProtocol::Message +{ + public: + BatchMessage(const IRCv3::Batch::Batch& batch, bool start) + : ClientProtocol::Message("BATCH", ServerInstance->Config->ServerName) + { + char c = (start ? '+' : '-'); + PushParam(std::string(1, c) + batch.GetRefTagStr()); + if ((start) && (!batch.GetType().empty())) + PushParamRef(batch.GetType()); + } +}; + +/** Extra structure allocated only for running batches, containing objects only relevant for + * that specific run of the batch. + */ +struct IRCv3::Batch::BatchInfo +{ + /** List of users that have received the batch start message + */ + std::vector<LocalUser*> users; + BatchMessage startmsg; + ClientProtocol::Event startevent; + + BatchInfo(ClientProtocol::EventProvider& protoevprov, IRCv3::Batch::Batch& b) + : startmsg(b, true) + , startevent(protoevprov, startmsg) + { + } +}; + +class IRCv3::Batch::ManagerImpl : public Manager +{ + typedef std::vector<Batch*> BatchList; + + Cap::Capability cap; + ClientProtocol::EventProvider protoevprov; + LocalIntExt batchbits; + BatchList active_batches; + bool unloading; + + bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE + { + if (!cap.get(user)) + return false; + + Batch& batch = *static_cast<Batch*>(tagdata.provdata); + // Check if this is the first message the user is getting that is part of the batch + const intptr_t bits = batchbits.get(user); + if (!(bits & batch.GetBit())) + { + // Send the start batch command ("BATCH +reftag TYPE"), remember the user so we can send them a + // "BATCH -reftag" message later when the batch ends and set the flag we just checked so this is + // only done once per user per batch. + batchbits.set(user, (bits | batch.GetBit())); + batch.batchinfo->users.push_back(user); + user->Send(batch.batchinfo->startevent); + } + + return true; + } + + unsigned int NextFreeId() const + { + if (active_batches.empty()) + return 0; + return active_batches.back()->GetId()+1; + } + + public: + ManagerImpl(Module* mod) + : Manager(mod) + , cap(mod, "batch") + , protoevprov(mod, "BATCH") + , batchbits("batchbits", ExtensionItem::EXT_USER, mod) + , unloading(false) + { + } + + void Init() + { + // Set batchbits to 0 for all users in case we were reloaded and the previous, now meaningless, + // batchbits are set on users + const UserManager::LocalList& users = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = users.begin(); i != users.end(); ++i) + { + LocalUser* const user = *i; + batchbits.set(user, 0); + } + } + + void Shutdown() + { + unloading = true; + while (!active_batches.empty()) + ManagerImpl::End(*active_batches.back()); + } + + void RemoveFromAll(LocalUser* user) + { + const intptr_t bits = batchbits.get(user); + + // User is quitting, remove them from all lists + for (BatchList::iterator i = active_batches.begin(); i != active_batches.end(); ++i) + { + Batch& batch = **i; + // Check the bit first to avoid list scan in case they're not on the list + if ((bits & batch.GetBit()) != 0) + stdalgo::vector::swaperase(batch.batchinfo->users, user); + } + } + + void Start(Batch& batch) CXX11_OVERRIDE + { + if (unloading) + return; + + if (batch.IsRunning()) + return; // Already started, don't start again + + const size_t id = NextFreeId(); + if (id >= MAX_BATCHES) + return; + + batch.Setup(id); + // Set the manager field which Batch::IsRunning() checks and is also used by AddToBatch() + // to set the message tag + batch.manager = this; + batch.batchinfo = new IRCv3::Batch::BatchInfo(protoevprov, batch); + batch.batchstartmsg = &batch.batchinfo->startmsg; + active_batches.push_back(&batch); + } + + void End(Batch& batch) CXX11_OVERRIDE + { + if (!batch.IsRunning()) + return; + + // Mark batch as stopped + batch.manager = NULL; + + BatchInfo& batchinfo = *batch.batchinfo; + // Send end batch message to all users who got the batch start message and unset bit so it can be reused + BatchMessage endbatchmsg(batch, false); + ClientProtocol::Event endbatchevent(protoevprov, endbatchmsg); + for (std::vector<LocalUser*>::const_iterator i = batchinfo.users.begin(); i != batchinfo.users.end(); ++i) + { + LocalUser* const user = *i; + user->Send(endbatchevent); + batchbits.set(user, batchbits.get(user) & ~batch.GetBit()); + } + + // erase() not swaperase because the reftag generation logic depends on the order of the elements + stdalgo::erase(active_batches, &batch); + delete batch.batchinfo; + batch.batchinfo = NULL; + } +}; + +class ModuleIRCv3Batch : public Module +{ + IRCv3::Batch::ManagerImpl manager; + + public: + ModuleIRCv3Batch() + : manager(this) + { + } + + void init() CXX11_OVERRIDE + { + manager.Init(); + } + + void OnUnloadModule(Module* mod) CXX11_OVERRIDE + { + if (mod == this) + manager.Shutdown(); + } + + void OnUserDisconnect(LocalUser* user) CXX11_OVERRIDE + { + // Remove the user from all internal lists + manager.RemoveFromAll(user); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the batch IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3Batch) diff --git a/src/modules/m_ircv3_capnotify.cpp b/src/modules/m_ircv3_capnotify.cpp new file mode 100644 index 000000000..6190f15a8 --- /dev/null +++ b/src/modules/m_ircv3_capnotify.cpp @@ -0,0 +1,185 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" +#include "modules/reload.h" + +class CapNotify : public Cap::Capability +{ + bool OnRequest(LocalUser* user, bool add) CXX11_OVERRIDE + { + // Users using the negotiation protocol v3.2 or newer may not turn off cap-notify + if ((!add) && (GetProtocol(user) != Cap::CAP_LEGACY)) + return false; + return true; + } + + bool OnList(LocalUser* user) CXX11_OVERRIDE + { + // If the client supports 3.2 enable cap-notify for them + if (GetProtocol(user) != Cap::CAP_LEGACY) + set(user, true); + return true; + } + + public: + CapNotify(Module* mod) + : Cap::Capability(mod, "cap-notify") + { + } +}; + +class CapNotifyMessage : public Cap::MessageBase +{ + public: + CapNotifyMessage(bool add, const std::string& capname) + : Cap::MessageBase((add ? "NEW" : "DEL")) + { + PushParamRef(capname); + } +}; + +class CapNotifyValueMessage : public Cap::MessageBase +{ + std::string s; + const std::string::size_type pos; + + public: + CapNotifyValueMessage(const std::string& capname) + : Cap::MessageBase("NEW") + , s(capname) + , pos(s.size()+1) + { + s.push_back('='); + PushParamRef(s); + } + + void SetCapValue(const std::string& capvalue) + { + s.erase(pos); + s.append(capvalue); + InvalidateCache(); + } +}; + +class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public ReloadModule::EventListener +{ + CapNotify capnotify; + std::string reloadedmod; + std::vector<std::string> reloadedcaps; + ClientProtocol::EventProvider protoev; + + void Send(const std::string& capname, Cap::Capability* cap, bool add) + { + CapNotifyMessage msg(add, capname); + CapNotifyValueMessage msgwithval(capname); + + ClientProtocol::Event event(protoev, msg); + ClientProtocol::Event eventwithval(protoev, msgwithval); + + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* user = *i; + if (!capnotify.get(user)) + continue; + + // If the cap is being added and the client supports cap values then show the value, if any + if ((add) && (capnotify.GetProtocol(user) != Cap::CAP_LEGACY)) + { + const std::string* capvalue = cap->GetValue(user); + if ((capvalue) && (!capvalue->empty())) + { + msgwithval.SetUser(user); + msgwithval.SetCapValue(*capvalue); + user->Send(eventwithval); + continue; + } + } + msg.SetUser(user); + user->Send(event); + } + } + + public: + ModuleIRCv3CapNotify() + : Cap::EventListener(this) + , ReloadModule::EventListener(this) + , capnotify(this) + , protoev(this, "CAP_NOTIFY") + { + } + + void OnCapAddDel(Cap::Capability* cap, bool add) CXX11_OVERRIDE + { + if (cap->creator == this) + return; + + if (cap->creator->ModuleSourceFile == reloadedmod) + { + if (!add) + reloadedcaps.push_back(cap->GetName()); + return; + } + Send(cap->GetName(), cap, add); + } + + void OnCapValueChange(Cap::Capability* cap) CXX11_OVERRIDE + { + // The value of a cap has changed, send CAP DEL and CAP NEW with the new value + Send(cap->GetName(), cap, false); + Send(cap->GetName(), cap, true); + } + + void OnReloadModuleSave(Module* mod, ReloadModule::CustomData& cd) CXX11_OVERRIDE + { + if (mod == this) + return; + reloadedmod = mod->ModuleSourceFile; + // Request callback when reload is complete + cd.add(this, NULL); + } + + void OnReloadModuleRestore(Module* mod, void* data) CXX11_OVERRIDE + { + // Reloading can change the set of caps provided by a module so assuming that if the reload succeded all + // caps that the module previously provided are available or all were lost if the reload failed is wrong. + // Instead, we verify the availability of each cap individually. + dynamic_reference_nocheck<Cap::Manager> capmanager(this, "capmanager"); + if (capmanager) + { + for (std::vector<std::string>::const_iterator i = reloadedcaps.begin(); i != reloadedcaps.end(); ++i) + { + const std::string& capname = *i; + if (!capmanager->Find(capname)) + Send(capname, NULL, false); + } + } + reloadedmod.clear(); + reloadedcaps.clear(); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the cap-notify IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3CapNotify) diff --git a/src/modules/m_ircv3_chghost.cpp b/src/modules/m_ircv3_chghost.cpp new file mode 100644 index 000000000..fd142803f --- /dev/null +++ b/src/modules/m_ircv3_chghost.cpp @@ -0,0 +1,61 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" +#include "modules/ircv3.h" + +class ModuleIRCv3ChgHost : public Module +{ + Cap::Capability cap; + ClientProtocol::EventProvider protoevprov; + + void DoChgHost(User* user, const std::string& ident, const std::string& host) + { + ClientProtocol::Message msg("CHGHOST", user); + msg.PushParamRef(ident); + msg.PushParamRef(host); + ClientProtocol::Event protoev(protoevprov, msg); + IRCv3::WriteNeighborsWithCap(user, protoev, cap); + } + + public: + ModuleIRCv3ChgHost() + : cap(this, "chghost") + , protoevprov(this, "CHGHOST") + { + } + + void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE + { + DoChgHost(user, newident, user->GetDisplayedHost()); + } + + void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE + { + DoChgHost(user, user->ident, newhost); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the chghost IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3ChgHost) diff --git a/src/modules/m_ircv3_echomessage.cpp b/src/modules/m_ircv3_echomessage.cpp new file mode 100644 index 000000000..f6eae5a00 --- /dev/null +++ b/src/modules/m_ircv3_echomessage.cpp @@ -0,0 +1,79 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2013-2015 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" + +class ModuleIRCv3EchoMessage : public Module +{ + Cap::Capability cap; + + public: + ModuleIRCv3EchoMessage() + : cap(this, "echo-message") + { + } + + void OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE + { + if (!cap.get(user) || !details.echo) + return; + + // Caps are only set on local users + LocalUser* const localuser = static_cast<LocalUser*>(user); + + const std::string& text = details.echo_original ? details.original_text : details.text; + if (target.type == MessageTarget::TYPE_USER) + { + User* destuser = target.Get<User>(); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, destuser, text, details.type); + privmsg.AddTags(details.tags_in); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); + } + else if (target.type == MessageTarget::TYPE_CHANNEL) + { + Channel* chan = target.Get<Channel>(); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, text, details.type, target.status); + privmsg.AddTags(details.tags_in); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); + } + else + { + const std::string* servername = target.Get<std::string>(); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, *servername, text, details.type); + privmsg.AddTags(details.tags_in); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); + } + } + + void OnUserMessageBlocked(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE + { + // Prevent spammers from knowing that their spam was blocked. + if (details.echo_original) + OnUserPostMessage(user, target, details); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the echo-message IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3EchoMessage) diff --git a/src/modules/m_ircv3_invitenotify.cpp b/src/modules/m_ircv3_invitenotify.cpp new file mode 100644 index 000000000..8bd4d5642 --- /dev/null +++ b/src/modules/m_ircv3_invitenotify.cpp @@ -0,0 +1,70 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" + +class ModuleIRCv3InviteNotify : public Module +{ + Cap::Capability cap; + + public: + ModuleIRCv3InviteNotify() + : cap(this, "invite-notify") + { + } + + void OnUserInvite(User* source, User* dest, Channel* chan, time_t expiry, unsigned int notifyrank, CUList& notifyexcepts) CXX11_OVERRIDE + { + ClientProtocol::Messages::Invite invitemsg(source, dest, chan); + ClientProtocol::Event inviteevent(ServerInstance->GetRFCEvents().invite, invitemsg); + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) + { + User* user = i->first; + // Skip members who don't use this extension or were excluded by other modules + if ((!cap.get(user)) || (notifyexcepts.count(user))) + continue; + + Membership* memb = i->second; + // Check whether the member has a high enough rank to see the notification + if (memb->getRank() < notifyrank) + continue; + + // Caps are only set on local users + LocalUser* const localuser = static_cast<LocalUser*>(user); + // Send and add the user to the exceptions so they won't get the NOTICE invite announcement message + localuser->Send(inviteevent); + notifyexcepts.insert(user); + } + } + + void Prioritize() CXX11_OVERRIDE + { + // Prioritize after all modules to see all excepted users + ServerInstance->Modules.SetPriority(this, I_OnUserInvite, PRIORITY_LAST); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the invite-notify IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3InviteNotify) diff --git a/src/modules/m_ircv3_servertime.cpp b/src/modules/m_ircv3_servertime.cpp new file mode 100644 index 000000000..1c35c422b --- /dev/null +++ b/src/modules/m_ircv3_servertime.cpp @@ -0,0 +1,72 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/ircv3.h" +#include "modules/ircv3_servertime.h" + +class ServerTimeTag : public IRCv3::ServerTime::Manager, public IRCv3::CapTag<ServerTimeTag> +{ + time_t lasttime; + std::string lasttimestring; + + void RefreshTimeString() + { + const time_t currtime = ServerInstance->Time(); + if (currtime != lasttime) + { + lasttime = currtime; + // Cache the string so it's not recreated every time a message is sent + lasttimestring = IRCv3::ServerTime::FormatTime(currtime); + } + } + + public: + ServerTimeTag(Module* mod) + : IRCv3::ServerTime::Manager(mod) + , IRCv3::CapTag<ServerTimeTag>(mod, "server-time", "time") + , lasttime(0) + { + tagprov = this; + } + + const std::string* GetValue(const ClientProtocol::Message& msg) + { + RefreshTimeString(); + return &lasttimestring; + } +}; + +class ModuleIRCv3ServerTime : public Module +{ + ServerTimeTag tag; + + public: + ModuleIRCv3ServerTime() + : tag(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the server-time IRCv3 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3ServerTime) diff --git a/src/modules/m_ircv3_sts.cpp b/src/modules/m_ircv3_sts.cpp new file mode 100644 index 000000000..cbfdd1307 --- /dev/null +++ b/src/modules/m_ircv3_sts.cpp @@ -0,0 +1,181 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2017 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" +#include "modules/ssl.h" + +class STSCap : public Cap::Capability +{ + private: + std::string host; + std::string plaintextpolicy; + std::string securepolicy; + + bool OnList(LocalUser* user) CXX11_OVERRIDE + { + // Don't send the cap to clients that only support cap-3.1. + if (GetProtocol(user) == Cap::CAP_LEGACY) + return false; + + // Plaintext listeners have their own policy. + SSLIOHook* sslhook = SSLIOHook::IsSSL(&user->eh); + if (!sslhook) + return true; + + // If no hostname has been provided for the connection, an STS persistence policy SHOULD NOT be advertised. + std::string snihost; + if (!sslhook->GetServerName(snihost)) + return false; + + // Before advertising an STS persistence policy over a secure connection, servers SHOULD verify whether the + // hostname provided by clients, for example, via TLS Server Name Indication (SNI), has been whitelisted by + // administrators in the server configuration. + return InspIRCd::Match(snihost, host, ascii_case_insensitive_map); + } + + bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE + { + // Clients MUST NOT request this capability with CAP REQ. Servers MAY reply with a CAP NAK message if a + // client requests this capability. + return false; + } + + const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE + { + return SSLIOHook::IsSSL(&user->eh) ? &securepolicy : &plaintextpolicy; + } + + public: + STSCap(Module* mod) + : Cap::Capability(mod, "sts") + { + } + + ~STSCap() + { + // TODO: Send duration=0 when STS vanishes. + } + + void SetPolicy(const std::string& newhost, unsigned long duration, unsigned int port, bool preload) + { + // To enforce an STS upgrade policy, servers MUST send this key to insecurely connected clients. Servers + // MAY send this key to securely connected clients, but it will be ignored. + std::string newplaintextpolicy("port="); + newplaintextpolicy.append(ConvToStr(port)); + + // To enforce an STS persistence policy, servers MUST send this key to securely connected clients. Servers + // MAY send this key to all clients, but insecurely connected clients MUST ignore it. + std::string newsecurepolicy("duration="); + newsecurepolicy.append(ConvToStr(duration)); + + // Servers MAY send this key to all clients, but insecurely connected clients MUST ignore it. + if (preload) + newsecurepolicy.append(",preload"); + + // Apply the new policy. + bool changed = false; + if (!irc::equals(host, newhost)) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing STS SNI hostname from \"%s\" to \"%s\"", host.c_str(), newhost.c_str()); + host = newhost; + changed = true; + } + + if (plaintextpolicy != newplaintextpolicy) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing plaintext STS policy from \"%s\" to \"%s\"", plaintextpolicy.c_str(), newplaintextpolicy.c_str()); + plaintextpolicy.swap(newplaintextpolicy); + changed = true; + } + + if (securepolicy != newsecurepolicy) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing secure STS policy from \"%s\" to \"%s\"", securepolicy.c_str(), newsecurepolicy.c_str()); + securepolicy.swap(newsecurepolicy); + changed = true; + } + + // If the policy has changed then notify all clients via cap-notify. + if (changed) + NotifyValueChange(); + } +}; + +class ModuleIRCv3STS : public Module +{ + private: + STSCap cap; + + // The IRCv3 STS specification requires that the server is listening using SSL using a valid certificate. + bool HasValidSSLPort(unsigned int port) + { + for (std::vector<ListenSocket*>::const_iterator iter = ServerInstance->ports.begin(); iter != ServerInstance->ports.end(); ++iter) + { + ListenSocket* ls = *iter; + + // Is this listener on the right port? + unsigned int saport = ls->bind_sa.port(); + if (saport != port) + continue; + + // Is this listener using SSL? + if (ls->bind_tag->getString("ssl").empty()) + continue; + + // TODO: Add a way to check if a listener's TLS cert is CA-verified. + return true; + } + return false; + } + + public: + ModuleIRCv3STS() + : cap(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + // TODO: Multiple SNI profiles + ConfigTag* tag = ServerInstance->Config->ConfValue("sts"); + if (tag == ServerInstance->Config->EmptyTag) + throw ModuleException("You must define a STS policy!"); + + const std::string host = tag->getString("host"); + if (host.empty()) + throw ModuleException("<sts:host> must contain a hostname, at " + tag->getTagLocation()); + + unsigned int port = tag->getUInt("port", 0, 0, UINT16_MAX); + if (!HasValidSSLPort(port)) + throw ModuleException("<sts:port> must be a TLS port, at " + tag->getTagLocation()); + + unsigned long duration = tag->getDuration("duration", 60*60*24*30*2); + bool preload = tag->getBool("preload"); + cap.SetPolicy(host, duration, port, preload); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides IRCv3 Strict Transport Security policy advertisement", VF_OPTCOMMON|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3STS) diff --git a/src/modules/m_joinflood.cpp b/src/modules/m_joinflood.cpp index 63bcc38a4..aaf266216 100644 --- a/src/modules/m_joinflood.cpp +++ b/src/modules/m_joinflood.cpp @@ -23,35 +23,41 @@ #include "inspircd.h" -/* $ModDesc: Provides channel mode +j (join flood protection) */ +enum +{ + // From RFC 2182. + ERR_UNAVAILRESOURCE = 437 +}; + +// The number of seconds the channel will be closed for. +static unsigned int duration; /** Holds settings and state associated with channel mode +j */ class joinfloodsettings { public: - int secs; - int joins; + unsigned int secs; + unsigned int joins; time_t reset; time_t unlocktime; - int counter; - bool locked; + unsigned int counter; - joinfloodsettings(int b, int c) : secs(b), joins(c) + joinfloodsettings(unsigned int b, unsigned int c) + : secs(b), joins(c), unlocktime(0), counter(0) { reset = ServerInstance->Time() + secs; - counter = 0; - locked = false; - }; + } void addjoin() { - counter++; if (ServerInstance->Time() > reset) { - counter = 0; + counter = 1; reset = ServerInstance->Time() + secs; } + else + counter++; } bool shouldlock() @@ -66,155 +72,93 @@ class joinfloodsettings bool islocked() { - if (locked) - { - if (ServerInstance->Time() > unlocktime) - { - locked = false; - return false; - } - else - { - return true; - } - } - return false; + if (ServerInstance->Time() > unlocktime) + unlocktime = 0; + + return (unlocktime != 0); } void lock() { - locked = true; - unlocktime = ServerInstance->Time() + 60; + unlocktime = ServerInstance->Time() + duration; } + bool operator==(const joinfloodsettings& other) const + { + return ((this->secs == other.secs) && (this->joins == other.joins)); + } }; /** Handles channel mode +j */ -class JoinFlood : public ModeHandler +class JoinFlood : public ParamMode<JoinFlood, SimpleExtItem<joinfloodsettings> > { public: - SimpleExtItem<joinfloodsettings> ext; - JoinFlood(Module* Creator) : ModeHandler(Creator, "joinflood", 'j', PARAM_SETONLY, MODETYPE_CHANNEL), - ext("joinflood", Creator) { } + JoinFlood(Module* Creator) + : ParamMode<JoinFlood, SimpleExtItem<joinfloodsettings> >(Creator, "joinflood", 'j') + { + } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE { - if (adding) + std::string::size_type colon = parameter.find(':'); + if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) { - char ndata[MAXBUF]; - char* data = ndata; - strlcpy(ndata,parameter.c_str(),MAXBUF); - char* joins = data; - char* secs = NULL; - while (*data) - { - if (*data == ':') - { - *data = 0; - data++; - secs = data; - break; - } - else data++; - } - if (secs) - - { - /* Set up the flood parameters for this channel */ - int njoins = atoi(joins); - int nsecs = atoi(secs); - if ((njoins<1) || (nsecs<1)) - { - source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); - parameter.clear(); - return MODEACTION_DENY; - } - else - { - joinfloodsettings* f = ext.get(channel); - if (!f) - { - parameter = ConvToStr(njoins) + ":" +ConvToStr(nsecs); - f = new joinfloodsettings(nsecs, njoins); - ext.set(channel, f); - channel->SetModeParam('j', parameter); - return MODEACTION_ALLOW; - } - else - { - std::string cur_param = channel->GetModeParameter('j'); - parameter = ConvToStr(njoins) + ":" +ConvToStr(nsecs); - if (cur_param == parameter) - { - // mode params match - return MODEACTION_DENY; - } - else - { - // new mode param, replace old with new - f = new joinfloodsettings(nsecs, njoins); - ext.set(channel, f); - channel->SetModeParam('j', parameter); - return MODEACTION_ALLOW; - } - } - } - } - else - { - source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); - return MODEACTION_DENY; - } + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; } - else + + /* Set up the flood parameters for this channel */ + unsigned int njoins = ConvToInt(parameter.substr(0, colon)); + unsigned int nsecs = ConvToInt(parameter.substr(colon+1)); + if ((njoins<1) || (nsecs<1)) { - if (channel->IsModeSet('j')) - { - ext.unset(channel); - channel->SetModeParam('j', ""); - return MODEACTION_ALLOW; - } + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; } - return MODEACTION_DENY; + + ext.set(channel, new joinfloodsettings(nsecs, njoins)); + return MODEACTION_ALLOW; + } + + void SerializeParam(Channel* chan, const joinfloodsettings* jfs, std::string& out) + { + out.append(ConvToStr(jfs->joins)).push_back(':'); + out.append(ConvToStr(jfs->secs)); } }; class ModuleJoinFlood : public Module { - JoinFlood jf; public: - ModuleJoinFlood() : jf(this) { } - void init() + void ReadConfig(ConfigStatus&) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(jf); - ServerInstance->Modules->AddService(jf.ext); - Implementation eventlist[] = { I_OnUserPreJoin, I_OnUserJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + ConfigTag* tag = ServerInstance->Config->ConfValue("joinflood"); + duration = tag->getDuration("duration", 60, 10, 600); } - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { if (chan) { joinfloodsettings *f = jf.ext.get(chan); if (f && f->islocked()) { - user->WriteNumeric(609, "%s %s :This channel is temporarily unavailable (+j). Please try again later.",user->nick.c_str(),chan->name.c_str()); + user->WriteNumeric(ERR_UNAVAILRESOURCE, chan->name, "This channel is temporarily unavailable (+j). Please try again later."); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) + void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE { /* We arent interested in JOIN events caused by a network burst */ if (sync) @@ -230,16 +174,12 @@ class ModuleJoinFlood : public Module { f->clear(); f->lock(); - memb->chan->WriteChannelWithServ((char*)ServerInstance->Config->ServerName.c_str(), "NOTICE %s :This channel has been closed to new users for 60 seconds because there have been more than %d joins in %d seconds.", memb->chan->name.c_str(), f->joins, f->secs); + memb->chan->WriteNotice(InspIRCd::Format("This channel has been closed to new users for %u seconds because there have been more than %d joins in %d seconds.", duration, f->joins, f->secs)); } } } - ~ModuleJoinFlood() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +j (join flood protection)", VF_VENDOR); } diff --git a/src/modules/m_jumpserver.cpp b/src/modules/m_jumpserver.cpp index 44c6fc0d9..682e353d2 100644 --- a/src/modules/m_jumpserver.cpp +++ b/src/modules/m_jumpserver.cpp @@ -20,8 +20,13 @@ #include "inspircd.h" +#include "modules/ssl.h" -/* $ModDesc: Provides support for the RPL_REDIR numeric and the /JUMPSERVER command. */ +enum +{ + // From ircd-ratbox. + RPL_REDIR = 10 +}; /** Handle /JUMPSERVER */ @@ -32,15 +37,18 @@ class CommandJumpserver : public Command std::string redirect_to; std::string reason; int port; + int sslport; CommandJumpserver(Module* Creator) : Command(Creator, "JUMPSERVER", 0, 4) { - flags_needed = 'o'; syntax = "[<server> <port> <+/-an> <reason>]"; + flags_needed = 'o'; + syntax = "[<server> <port>[:<sslport>] <+/-an> <reason>]"; port = 0; + sslport = 0; redirect_new_users = false; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { int n_done = 0; reason = (parameters.size() < 4) ? "Please use this server/port instead" : parameters[3]; @@ -50,14 +58,15 @@ class CommandJumpserver : public Command std::string n_done_s; /* No parameters: jumpserver disabled */ - if (!parameters.size()) + if (parameters.empty()) { if (port) - user->WriteServ("NOTICE %s :*** Disabled jumpserver (previously set to '%s:%d')", user->nick.c_str(), redirect_to.c_str(), port); + user->WriteNotice("*** Disabled jumpserver (previously set to '" + redirect_to + ":" + ConvToStr(port) + "')"); else - user->WriteServ("NOTICE %s :*** Jumpserver was not enabled.", user->nick.c_str()); + user->WriteNotice("*** Jumpserver was not enabled."); port = 0; + sslport = 0; redirect_to.clear(); return CMD_SUCCESS; } @@ -84,27 +93,36 @@ class CommandJumpserver : public Command redirect_new_users = direction; break; default: - user->WriteServ("NOTICE %s :*** Invalid JUMPSERVER flag: %c", user->nick.c_str(), *n); + user->WriteNotice("*** Invalid JUMPSERVER flag: " + ConvToStr(*n)); return CMD_FAILURE; break; } } - if (!atoi(parameters[1].c_str())) + size_t delimpos = parameters[1].find(':'); + port = ConvToInt(parameters[1].substr(0, delimpos ? delimpos : std::string::npos)); + sslport = (delimpos == std::string::npos ? 0 : ConvToInt(parameters[1].substr(delimpos + 1))); + + if (parameters[1].find_first_not_of("0123456789:") != std::string::npos + || parameters[1].rfind(':') != delimpos + || port > 65535 || sslport > 65535) { - user->WriteServ("NOTICE %s :*** Invalid port number", user->nick.c_str()); + user->WriteNotice("*** Invalid port number"); return CMD_FAILURE; } if (redirect_all_immediately) { /* Redirect everyone but the oper sending the command */ - for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); ++i) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ) { - User* t = *i; - if (!IS_OPER(t)) + // Quitting the user removes it from the list + LocalUser* t = *i; + ++i; + if (!t->IsOper()) { - t->WriteNumeric(10, "%s %s %s :Please use this Server/Port instead", t->nick.c_str(), parameters[0].c_str(), parameters[1].c_str()); + t->WriteNumeric(RPL_REDIR, parameters[0], GetPort(t), "Please use this Server/Port instead"); ServerInstance->Users->QuitUser(t, reason); n_done++; } @@ -116,24 +134,24 @@ class CommandJumpserver : public Command } if (redirect_new_users) - { redirect_to = parameters[0]; - port = atoi(parameters[1].c_str()); - } - user->WriteServ("NOTICE %s :*** Set jumpserver to server '%s' port '%s', flags '+%s%s'%s%s%s: %s", user->nick.c_str(), parameters[0].c_str(), parameters[1].c_str(), - redirect_all_immediately ? "a" : "", - redirect_new_users ? "n" : "", - n_done ? " (" : "", - n_done ? n_done_s.c_str() : "", - n_done ? " user(s) redirected)" : "", - reason.c_str()); + user->WriteNotice("*** Set jumpserver to server '" + parameters[0] + "' port '" + (port ? ConvToStr(port) : "Auto") + ", SSL " + (sslport ? ConvToStr(sslport) : "Auto") + "', flags '+" + + (redirect_all_immediately ? "a" : "") + (redirect_new_users ? "n'" : "'") + + (n_done ? " (" + n_done_s + "user(s) redirected): " : ": ") + reason); } return CMD_SUCCESS; } -}; + int GetPort(LocalUser* user) + { + int p = (SSLIOHook::IsSSL(&user->eh) ? sslport : port); + if (p == 0) + p = user->GetServerPort(); + return p; + } +}; class ModuleJumpServer : public Module { @@ -143,40 +161,35 @@ class ModuleJumpServer : public Module { } - void init() - { - ServerInstance->Modules->AddService(js); - Implementation eventlist[] = { I_OnUserRegister, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleJumpServer() + void OnModuleRehash(User* user, const std::string& param) CXX11_OVERRIDE { + if (irc::equals(param, "jumpserver") && js.redirect_new_users) + js.redirect_new_users = false; } - virtual ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { - if (js.port && js.redirect_new_users) + if (js.redirect_new_users) { - user->WriteNumeric(10, "%s %s %d :Please use this Server/Port instead", - user->nick.c_str(), js.redirect_to.c_str(), js.port); + int port = js.GetPort(user); + user->WriteNumeric(RPL_REDIR, js.redirect_to, port, "Please use this Server/Port instead"); ServerInstance->Users->QuitUser(user, js.reason); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { // Emergency way to unlock - if (!user) js.redirect_new_users = false; + if (!status.srcuser) + js.redirect_new_users = false; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the RPL_REDIR numeric and the /JUMPSERVER command.", VF_VENDOR); } - }; MODULE_INIT(ModuleJumpServer) diff --git a/src/modules/m_kicknorejoin.cpp b/src/modules/m_kicknorejoin.cpp index a914f3869..4d911e78b 100644 --- a/src/modules/m_kicknorejoin.cpp +++ b/src/modules/m_kicknorejoin.cpp @@ -24,136 +24,150 @@ #include "inspircd.h" +#include "modules/invite.h" -/* $ModDesc: Provides channel mode +J (delay rejoin after kick) */ +enum +{ + // From RFC 2182. + ERR_UNAVAILRESOURCE = 437 +}; + + +class KickRejoinData +{ + struct KickedUser + { + std::string uuid; + time_t expire; + + KickedUser(User* user, unsigned int Delay) + : uuid(user->uuid) + , expire(ServerInstance->Time() + Delay) + { + } + }; + + typedef std::vector<KickedUser> KickedList; + + mutable KickedList kicked; + + public: + const unsigned int delay; -typedef std::map<std::string, time_t> delaylist; + KickRejoinData(unsigned int Delay) : delay(Delay) { } + + bool canjoin(LocalUser* user) const + { + for (KickedList::iterator i = kicked.begin(); i != kicked.end(); ) + { + KickedUser& rec = *i; + if (rec.expire > ServerInstance->Time()) + { + if (rec.uuid == user->uuid) + return false; + ++i; + } + else + { + // Expired record, remove. + stdalgo::vector::swaperase(kicked, i); + if (kicked.empty()) + break; + } + } + return true; + } + + void add(User* user) + { + // One user can be in the list multiple times if the user gets kicked, force joins + // (skipping OnUserPreJoin) and gets kicked again, but that's okay because canjoin() + // works correctly in this case as well + kicked.push_back(KickedUser(user, delay)); + } +}; /** Handles channel mode +J */ -class KickRejoin : public ModeHandler +class KickRejoin : public ParamMode<KickRejoin, SimpleExtItem<KickRejoinData> > { + const unsigned int max; public: - unsigned int max; - SimpleExtItem<delaylist> ext; KickRejoin(Module* Creator) - : ModeHandler(Creator, "kicknorejoin", 'J', PARAM_SETONLY, MODETYPE_CHANNEL) - , ext("norejoinusers", Creator) + : ParamMode<KickRejoin, SimpleExtItem<KickRejoinData> >(Creator, "kicknorejoin", 'J') + , max(60) { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE { - if (adding) + int v = ConvToInt(parameter); + if (v <= 0) { - int v = ConvToInt(parameter); - if (v <= 0) - return MODEACTION_DENY; - if (parameter == channel->GetModeParameter(this)) - return MODEACTION_DENY; - - if ((IS_LOCAL(source) && ((unsigned int)v > max))) - v = max; - - parameter = ConvToStr(v); - channel->SetModeParam(this, parameter); + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; } - else - { - if (!channel->IsModeSet(this)) - return MODEACTION_DENY; - ext.unset(channel); - channel->SetModeParam(this, ""); - } + if ((IS_LOCAL(source) && ((unsigned int)v > max))) + v = max; + + ext.set(channel, new KickRejoinData(v)); return MODEACTION_ALLOW; } + + void SerializeParam(Channel* chan, const KickRejoinData* krd, std::string& out) + { + out.append(ConvToStr(krd->delay)); + } + + std::string GetModuleSettings() const + { + return ConvToStr(max); + } }; class ModuleKickNoRejoin : public Module { KickRejoin kr; + Invite::API invapi; public: - ModuleKickNoRejoin() : kr(this) + , invapi(this) { } - void init() - { - ServerInstance->Modules->AddService(kr); - ServerInstance->Modules->AddService(kr.ext); - Implementation eventlist[] = { I_OnUserPreJoin, I_OnUserKick, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } - - void OnRehash(User* user) - { - kr.max = ServerInstance->Duration(ServerInstance->Config->ConfValue("kicknorejoin")->getString("maxtime")); - if (!kr.max) - kr.max = 30*60; - } - - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { if (chan) { - delaylist* dl = kr.ext.get(chan); - if (dl) + const KickRejoinData* data = kr.ext.get(chan); + if ((data) && !invapi->IsInvited(user, chan) && (!data->canjoin(user))) { - for (delaylist::iterator iter = dl->begin(); iter != dl->end(); ) - { - if (iter->second > ServerInstance->Time()) - { - if (iter->first == user->uuid) - { - std::string modeparam = chan->GetModeParameter(&kr); - user->WriteNumeric(ERR_DELAYREJOIN, "%s %s :You must wait %s seconds after being kicked to rejoin (+J)", - user->nick.c_str(), chan->name.c_str(), modeparam.c_str()); - return MOD_RES_DENY; - } - ++iter; - } - else - { - // Expired record, remove. - dl->erase(iter++); - } - } - - if (dl->empty()) - kr.ext.unset(chan); + user->WriteNumeric(ERR_UNAVAILRESOURCE, chan, InspIRCd::Format("You must wait %u seconds after being kicked to rejoin (+J)", data->delay)); + return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) + void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) CXX11_OVERRIDE { - if (memb->chan->IsModeSet(&kr) && (IS_LOCAL(memb->user)) && (source != memb->user)) + if ((!IS_LOCAL(memb->user)) || (source == memb->user)) + return; + + KickRejoinData* data = kr.ext.get(memb->chan); + if (data) { - delaylist* dl = kr.ext.get(memb->chan); - if (!dl) - { - dl = new delaylist; - kr.ext.set(memb->chan, dl); - } - (*dl)[memb->user->uuid] = ServerInstance->Time() + ConvToInt(memb->chan->GetModeParameter(&kr)); + data->add(memb->user); } } - ~ModuleKickNoRejoin() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Channel mode to delay rejoin after kick", VF_VENDOR); + return Version("Channel mode to delay rejoin after kick", VF_VENDOR | VF_COMMON, kr.GetModuleSettings()); } }; - MODULE_INIT(ModuleKickNoRejoin) diff --git a/src/modules/m_knock.cpp b/src/modules/m_knock.cpp index 8d2aa4543..1b66e01a4 100644 --- a/src/modules/m_knock.cpp +++ b/src/modules/m_knock.cpp @@ -21,102 +21,105 @@ #include "inspircd.h" -/* $ModDesc: Provides support for /KNOCK and channel mode +K */ +enum +{ + // From UnrealIRCd. + ERR_CANNOTKNOCK = 480, + + // From ircd-ratbox. + ERR_CHANOPEN = 713, + ERR_KNOCKONCHAN = 714 +}; /** Handles the /KNOCK command */ class CommandKnock : public Command { + SimpleChannelModeHandler& noknockmode; + ChanModeReference inviteonlymode; + public: bool sendnotice; bool sendnumeric; - CommandKnock(Module* Creator) : Command(Creator,"KNOCK", 2, 2) + CommandKnock(Module* Creator, SimpleChannelModeHandler& Noknockmode) + : Command(Creator,"KNOCK", 2, 2) + , noknockmode(Noknockmode) + , inviteonlymode(Creator, "inviteonly") { syntax = "<channel> <reason>"; Penalty = 5; - TRANSLATE3(TR_TEXT, TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { Channel* c = ServerInstance->FindChan(parameters[0]); if (!c) { - user->WriteNumeric(401, "%s %s :No such channel",user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); return CMD_FAILURE; } if (c->HasUser(user)) { - user->WriteNumeric(480, "%s :Can't KNOCK on %s, you are already on that channel.", user->nick.c_str(), c->name.c_str()); + user->WriteNumeric(ERR_KNOCKONCHAN, c->name, InspIRCd::Format("Can't KNOCK on %s, you are already on that channel.", c->name.c_str())); return CMD_FAILURE; } - if (c->IsModeSet('K')) + if (c->IsModeSet(noknockmode)) { - user->WriteNumeric(480, "%s :Can't KNOCK on %s, +K is set.",user->nick.c_str(), c->name.c_str()); + user->WriteNumeric(ERR_CANNOTKNOCK, InspIRCd::Format("Can't KNOCK on %s, +K is set.", c->name.c_str())); return CMD_FAILURE; } - if (!c->IsModeSet('i')) + if (!c->IsModeSet(inviteonlymode)) { - user->WriteNumeric(480, "%s :Can't KNOCK on %s, channel is not invite only so knocking is pointless!",user->nick.c_str(), c->name.c_str()); + user->WriteNumeric(ERR_CHANOPEN, c->name, InspIRCd::Format("Can't KNOCK on %s, channel is not invite only so knocking is pointless!", c->name.c_str())); return CMD_FAILURE; } if (sendnotice) - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :User %s is KNOCKing on %s (%s)", c->name.c_str(), user->nick.c_str(), c->name.c_str(), parameters[1].c_str()); + c->WriteNotice(InspIRCd::Format("User %s is KNOCKing on %s (%s)", user->nick.c_str(), c->name.c_str(), parameters[1].c_str())); if (sendnumeric) - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "710 %s %s %s :is KNOCKing: %s", c->name.c_str(), c->name.c_str(), user->GetFullHost().c_str(), parameters[1].c_str()); + { + Numeric::Numeric numeric(710); + numeric.push(c->name).push(user->GetFullHost()).push("is KNOCKing: " + parameters[1]); - user->WriteServ("NOTICE %s :KNOCKing on %s", user->nick.c_str(), c->name.c_str()); + ClientProtocol::Messages::Numeric numericmsg(numeric, c->name); + c->Write(ServerInstance->GetRFCEvents().numeric, numericmsg); + } + + user->WriteNotice("KNOCKing on " + c->name); return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_OPT_BCAST; } }; -/** Handles channel mode +K - */ -class Knock : public SimpleChannelModeHandler -{ - public: - Knock(Module* Creator) : SimpleChannelModeHandler(Creator, "noknock", 'K') { } -}; - class ModuleKnock : public Module { + SimpleChannelModeHandler kn; CommandKnock cmd; - Knock kn; - public: - ModuleKnock() : cmd(this), kn(this) - { - } - void init() + public: + ModuleKnock() + : kn(this, "noknock", 'K') + , cmd(this, kn) { - ServerInstance->Modules->AddService(kn); - ServerInstance->Modules->AddService(cmd); - - ServerInstance->Modules->Attach(I_OnRehash, this); - OnRehash(NULL); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { std::string knocknotify = ServerInstance->Config->ConfValue("knock")->getString("notify"); - irc::string notify(knocknotify.c_str()); - - if (notify == "numeric") + if (stdalgo::string::equalsci(knocknotify, "numeric")) { cmd.sendnotice = false; cmd.sendnumeric = true; } - else if (notify == "both") + else if (stdalgo::string::equalsci(knocknotify, "both")) { cmd.sendnotice = true; cmd.sendnumeric = true; @@ -128,7 +131,7 @@ class ModuleKnock : public Module } } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for /KNOCK and channel mode +K", VF_OPTCOMMON | VF_VENDOR); } diff --git a/src/modules/m_ldapauth.cpp b/src/modules/m_ldapauth.cpp new file mode 100644 index 000000000..fedf02b4d --- /dev/null +++ b/src/modules/m_ldapauth.cpp @@ -0,0 +1,447 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Adam <Adam@anope.org> + * Copyright (C) 2011 Pierre Carrier <pierre@spotify.com> + * Copyright (C) 2009-2010 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Dennis Friis <peavey@inspircd.org> + * Copyright (C) 2007 Carsten Valdemar Munk <carsten.munk+inspircd@gmail.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/ldap.h" + +namespace +{ + Module* me; + std::string killreason; + LocalIntExt* authed; + bool verbose; + std::string vhost; + LocalStringExt* vhosts; + std::vector<std::pair<std::string, std::string> > requiredattributes; +} + +class BindInterface : public LDAPInterface +{ + const std::string provider; + const std::string uid; + std::string DN; + bool checkingAttributes; + bool passed; + int attrCount; + + static std::string SafeReplace(const std::string& text, std::map<std::string, std::string>& replacements) + { + std::string result; + result.reserve(text.length()); + + for (unsigned int i = 0; i < text.length(); ++i) + { + char c = text[i]; + if (c == '$') + { + // find the first nonalpha + i++; + unsigned int start = i; + + while (i < text.length() - 1 && isalpha(text[i + 1])) + ++i; + + std::string key(text, start, (i - start) + 1); + result.append(replacements[key]); + } + else + result.push_back(c); + } + + return result; + } + + static void SetVHost(User* user, const std::string& DN) + { + if (!vhost.empty()) + { + irc::commasepstream stream(DN); + + // mashed map of key:value parts of the DN + std::map<std::string, std::string> dnParts; + + std::string dnPart; + while (stream.GetToken(dnPart)) + { + std::string::size_type pos = dnPart.find('='); + if (pos == std::string::npos) // malformed + continue; + + std::string key(dnPart, 0, pos); + std::string value(dnPart, pos + 1, dnPart.length() - pos + 1); // +1s to skip the = itself + dnParts[key] = value; + } + + // change host according to config key + vhosts->set(user, SafeReplace(vhost, dnParts)); + } + } + + public: + BindInterface(Module* c, const std::string& p, const std::string& u, const std::string& dn) + : LDAPInterface(c) + , provider(p), uid(u), DN(dn), checkingAttributes(false), passed(false), attrCount(0) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + User* user = ServerInstance->FindUUID(uid); + dynamic_reference<LDAPProvider> LDAP(me, provider); + + if (!user || !LDAP) + { + if (!checkingAttributes || !--attrCount) + delete this; + return; + } + + if (!checkingAttributes && requiredattributes.empty()) + { + // We're done, there are no attributes to check + SetVHost(user, DN); + authed->set(user, 1); + + delete this; + return; + } + + // Already checked attributes? + if (checkingAttributes) + { + if (!passed) + { + // Only one has to pass + passed = true; + + SetVHost(user, DN); + authed->set(user, 1); + } + + // Delete this if this is the last ref + if (!--attrCount) + delete this; + + return; + } + + // check required attributes + checkingAttributes = true; + + for (std::vector<std::pair<std::string, std::string> >::const_iterator it = requiredattributes.begin(); it != requiredattributes.end(); ++it) + { + // Note that only one of these has to match for it to be success + const std::string& attr = it->first; + const std::string& val = it->second; + + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "LDAP compare: %s=%s", attr.c_str(), val.c_str()); + try + { + LDAP->Compare(this, DN, attr, val); + ++attrCount; + } + catch (LDAPException &ex) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Unable to compare attributes %s=%s: %s", attr.c_str(), val.c_str(), ex.GetReason().c_str()); + } + } + + // Nothing done + if (!attrCount) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (unable to validate attributes)", user->GetFullRealHost().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + delete this; + } + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + if (checkingAttributes && --attrCount) + return; + + if (passed) + { + delete this; + return; + } + + User* user = ServerInstance->FindUUID(uid); + if (user) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (%s)", user->GetFullRealHost().c_str(), err.getError().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + } + + delete this; + } +}; + +class SearchInterface : public LDAPInterface +{ + const std::string provider; + const std::string uid; + + public: + SearchInterface(Module* c, const std::string& p, const std::string& u) + : LDAPInterface(c), provider(p), uid(u) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + LocalUser* user = static_cast<LocalUser*>(ServerInstance->FindUUID(uid)); + dynamic_reference<LDAPProvider> LDAP(me, provider); + if (!LDAP || r.empty() || !user) + { + if (user) + ServerInstance->Users->QuitUser(user, killreason); + delete this; + return; + } + + try + { + const LDAPAttributes& a = r.get(0); + std::string bindDn = a.get("dn"); + if (bindDn.empty()) + { + ServerInstance->Users->QuitUser(user, killreason); + delete this; + return; + } + + LDAP->Bind(new BindInterface(this->creator, provider, uid, bindDn), bindDn, user->password); + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason()); + } + delete this; + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str()); + User* user = ServerInstance->FindUUID(uid); + if (user) + ServerInstance->Users->QuitUser(user, killreason); + delete this; + } +}; + +class AdminBindInterface : public LDAPInterface +{ + const std::string provider; + const std::string uuid; + const std::string base; + const std::string what; + + public: + AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& b, const std::string& w) + : LDAPInterface(c), provider(p), uuid(u), base(b), what(w) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + dynamic_reference<LDAPProvider> LDAP(me, provider); + if (LDAP) + { + try + { + LDAP->Search(new SearchInterface(this->creator, provider, uuid), base, what); + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason()); + } + } + delete this; + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError()); + delete this; + } +}; + +class ModuleLDAPAuth : public Module +{ + dynamic_reference<LDAPProvider> LDAP; + LocalIntExt ldapAuthed; + LocalStringExt ldapVhost; + std::string base; + std::string attribute; + std::vector<std::string> allowpatterns; + std::vector<std::string> whitelistedcidrs; + bool useusername; + +public: + ModuleLDAPAuth() + : LDAP(this, "LDAP") + , ldapAuthed("ldapauth", ExtensionItem::EXT_USER, this) + , ldapVhost("ldapauth_vhost", ExtensionItem::EXT_USER, this) + { + me = this; + authed = &ldapAuthed; + vhosts = &ldapVhost; + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("ldapauth"); + whitelistedcidrs.clear(); + requiredattributes.clear(); + + base = tag->getString("baserdn"); + attribute = tag->getString("attribute"); + killreason = tag->getString("killreason"); + vhost = tag->getString("host"); + // Set to true if failed connects should be reported to operators + verbose = tag->getBool("verbose"); + useusername = tag->getBool("userfield"); + + LDAP.SetProvider("LDAP/" + tag->getString("dbid")); + + ConfigTagList whitelisttags = ServerInstance->Config->ConfTags("ldapwhitelist"); + + for (ConfigIter i = whitelisttags.first; i != whitelisttags.second; ++i) + { + std::string cidr = i->second->getString("cidr"); + if (!cidr.empty()) { + whitelistedcidrs.push_back(cidr); + } + } + + ConfigTagList attributetags = ServerInstance->Config->ConfTags("ldaprequire"); + + for (ConfigIter i = attributetags.first; i != attributetags.second; ++i) + { + const std::string attr = i->second->getString("attribute"); + const std::string val = i->second->getString("value"); + + if (!attr.empty() && !val.empty()) + requiredattributes.push_back(make_pair(attr, val)); + } + + std::string allowpattern = tag->getString("allowpattern"); + irc::spacesepstream ss(allowpattern); + for (std::string more; ss.GetToken(more); ) + { + allowpatterns.push_back(more); + } + } + + void OnUserConnect(LocalUser *user) CXX11_OVERRIDE + { + std::string* cc = ldapVhost.get(user); + if (cc) + { + user->ChangeDisplayedHost(*cc); + ldapVhost.unset(user); + } + } + + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE + { + for (std::vector<std::string>::const_iterator i = allowpatterns.begin(); i != allowpatterns.end(); ++i) + { + if (InspIRCd::Match(user->nick, *i)) + { + ldapAuthed.set(user,1); + return MOD_RES_PASSTHRU; + } + } + + for (std::vector<std::string>::iterator i = whitelistedcidrs.begin(); i != whitelistedcidrs.end(); i++) + { + if (InspIRCd::MatchCIDR(user->GetIPString(), *i, ascii_case_insensitive_map)) + { + ldapAuthed.set(user,1); + return MOD_RES_PASSTHRU; + } + } + + if (user->password.empty()) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (No password provided)", user->GetFullRealHost().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + return MOD_RES_DENY; + } + + if (!LDAP) + { + if (verbose) + ServerInstance->SNO->WriteToSnoMask('c', "Forbidden connection from %s (Unable to find LDAP provider)", user->GetFullRealHost().c_str()); + ServerInstance->Users->QuitUser(user, killreason); + return MOD_RES_DENY; + } + + std::string what; + std::string::size_type pos = user->password.find(':'); + if (pos != std::string::npos) + { + what = attribute + "=" + user->password.substr(0, pos); + + // Trim the user: prefix, leaving just 'pass' for later password check + user->password = user->password.substr(pos + 1); + } + else + { + what = attribute + "=" + (useusername ? user->ident : user->nick); + } + + try + { + LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, base, what)); + } + catch (LDAPException &ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason()); + ServerInstance->Users->QuitUser(user, killreason); + } + + return MOD_RES_DENY; + } + + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE + { + return ldapAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Allow/Deny connections based upon answer from LDAP server", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleLDAPAuth) diff --git a/src/modules/m_ldapoper.cpp b/src/modules/m_ldapoper.cpp new file mode 100644 index 000000000..cde5b00d7 --- /dev/null +++ b/src/modules/m_ldapoper.cpp @@ -0,0 +1,249 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Adam <Adam@anope.org> + * Copyright (C) 2009 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> + * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2007 Carsten Valdemar Munk <carsten.munk+inspircd@gmail.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/ldap.h" + +namespace +{ + Module* me; +} + +class LDAPOperBase : public LDAPInterface +{ + protected: + const std::string uid; + const std::string opername; + const std::string password; + + void Fallback(User* user) + { + if (!user) + return; + + Command* oper_command = ServerInstance->Parser.GetHandler("OPER"); + if (!oper_command) + return; + + CommandBase::Params params; + params.push_back(opername); + params.push_back(password); + ClientProtocol::TagMap tags; + oper_command->Handle(user, CommandBase::Params(params, tags)); + } + + void Fallback() + { + User* user = ServerInstance->FindUUID(uid); + Fallback(user); + } + + public: + LDAPOperBase(Module* mod, const std::string& uuid, const std::string& oper, const std::string& pass) + : LDAPInterface(mod) + , uid(uuid), opername(oper), password(pass) + { + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: %s", err.getError().c_str()); + Fallback(); + delete this; + } +}; + +class BindInterface : public LDAPOperBase +{ + public: + BindInterface(Module* mod, const std::string& uuid, const std::string& oper, const std::string& pass) + : LDAPOperBase(mod, uuid, oper, pass) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + User* user = ServerInstance->FindUUID(uid); + ServerConfig::OperIndex::const_iterator iter = ServerInstance->Config->oper_blocks.find(opername); + + if (!user || iter == ServerInstance->Config->oper_blocks.end()) + { + Fallback(); + delete this; + return; + } + + OperInfo* ifo = iter->second; + user->Oper(ifo); + delete this; + } +}; + +class SearchInterface : public LDAPOperBase +{ + const std::string provider; + + bool HandleResult(const LDAPResult& result) + { + dynamic_reference<LDAPProvider> LDAP(me, provider); + if (!LDAP || result.empty()) + return false; + + try + { + const LDAPAttributes& attr = result.get(0); + std::string bindDn = attr.get("dn"); + if (bindDn.empty()) + return false; + + LDAP->Bind(new BindInterface(this->creator, uid, opername, password), bindDn, password); + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason()); + } + + return true; + } + + public: + SearchInterface(Module* mod, const std::string& prov, const std::string &uuid, const std::string& oper, const std::string& pass) + : LDAPOperBase(mod, uuid, oper, pass) + , provider(prov) + { + } + + void OnResult(const LDAPResult& result) CXX11_OVERRIDE + { + if (!HandleResult(result)) + Fallback(); + delete this; + } +}; + +class AdminBindInterface : public LDAPInterface +{ + const std::string provider; + const std::string user; + const std::string opername; + const std::string password; + const std::string base; + const std::string what; + + public: + AdminBindInterface(Module* c, const std::string& p, const std::string& u, const std::string& o, const std::string& pa, const std::string& b, const std::string& w) + : LDAPInterface(c), provider(p), user(u), opername(p), password(pa), base(b), what(w) + { + } + + void OnResult(const LDAPResult& r) CXX11_OVERRIDE + { + dynamic_reference<LDAPProvider> LDAP(me, provider); + if (LDAP) + { + try + { + LDAP->Search(new SearchInterface(this->creator, provider, user, opername, password), base, what); + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "Error searching LDAP server: " + ex.GetReason()); + } + } + delete this; + } + + void OnError(const LDAPResult& err) CXX11_OVERRIDE + { + ServerInstance->SNO->WriteToSnoMask('a', "Error binding as manager to LDAP server: " + err.getError()); + delete this; + } +}; + +class ModuleLDAPAuth : public Module +{ + dynamic_reference<LDAPProvider> LDAP; + std::string base; + std::string attribute; + + public: + ModuleLDAPAuth() + : LDAP(this, "LDAP") + { + me = this; + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("ldapoper"); + + LDAP.SetProvider("LDAP/" + tag->getString("dbid")); + base = tag->getString("baserdn"); + attribute = tag->getString("attribute"); + } + + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE + { + if (validated && command == "OPER" && parameters.size() >= 2) + { + const std::string& opername = parameters[0]; + const std::string& password = parameters[1]; + + ServerConfig::OperIndex::const_iterator it = ServerInstance->Config->oper_blocks.find(opername); + if (it == ServerInstance->Config->oper_blocks.end()) + return MOD_RES_PASSTHRU; + + ConfigTag* tag = it->second->oper_block; + if (!tag) + return MOD_RES_PASSTHRU; + + std::string acceptedhosts = tag->getString("host"); + std::string hostname = user->ident + "@" + user->GetRealHost(); + if (!InspIRCd::MatchMask(acceptedhosts, hostname, user->GetIPString())) + return MOD_RES_PASSTHRU; + + if (!LDAP) + return MOD_RES_PASSTHRU; + + try + { + std::string what = attribute + "=" + opername; + LDAP->BindAsManager(new AdminBindInterface(this, LDAP.GetProvider(), user->uuid, opername, password, base, what)); + return MOD_RES_DENY; + } + catch (LDAPException& ex) + { + ServerInstance->SNO->WriteToSnoMask('a', "LDAP exception: " + ex.GetReason()); + } + } + + return MOD_RES_PASSTHRU; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Adds the ability to authenticate opers via LDAP", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleLDAPAuth) diff --git a/src/modules/m_lockserv.cpp b/src/modules/m_lockserv.cpp index 4983ae16a..2db01b99a 100644 --- a/src/modules/m_lockserv.cpp +++ b/src/modules/m_lockserv.cpp @@ -20,33 +20,38 @@ #include "inspircd.h" -/* $ModDesc: Allows locking of the server to stop all incoming connections till unlocked again */ - /** Adds numerics * 988 <nick> <servername> :Closed for new connections * 989 <nick> <servername> :Open for new connections -*/ - + */ +enum +{ + // InspIRCd-specific. + RPL_SERVLOCKON = 988, + RPL_SERVLOCKOFF = 989 +}; class CommandLockserv : public Command { - bool& locked; -public: - CommandLockserv(Module* Creator, bool& lock) : Command(Creator, "LOCKSERV", 0), locked(lock) + std::string& locked; + + public: + CommandLockserv(Module* Creator, std::string& lock) : Command(Creator, "LOCKSERV", 0, 1), locked(lock) { + allow_empty_last_param = false; flags_needed = 'o'; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (locked) + if (!locked.empty()) { - user->WriteServ("NOTICE %s :The server is already locked.", user->nick.c_str()); + user->WriteNotice("The server is already locked."); return CMD_FAILURE; } - locked = true; - user->WriteNumeric(988, "%s %s :Closed for new connections", user->nick.c_str(), user->server.c_str()); + locked = parameters.empty() ? "Server is temporarily closed. Please try again later." : parameters[0]; + user->WriteNumeric(RPL_SERVLOCKON, user->server->GetName(), "Closed for new connections"); ServerInstance->SNO->WriteGlobalSno('a', "Oper %s used LOCKSERV to temporarily disallow new connections", user->nick.c_str()); return CMD_SUCCESS; } @@ -54,25 +59,24 @@ public: class CommandUnlockserv : public Command { -private: - bool& locked; + std::string& locked; -public: - CommandUnlockserv(Module* Creator, bool &lock) : Command(Creator, "UNLOCKSERV", 0), locked(lock) + public: + CommandUnlockserv(Module* Creator, std::string& lock) : Command(Creator, "UNLOCKSERV", 0), locked(lock) { flags_needed = 'o'; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (!locked) + if (locked.empty()) { - user->WriteServ("NOTICE %s :The server isn't locked.", user->nick.c_str()); + user->WriteNotice("The server isn't locked."); return CMD_FAILURE; } - locked = false; - user->WriteNumeric(989, "%s %s :Open for new connections", user->nick.c_str(), user->server.c_str()); + locked.clear(); + user->WriteNumeric(RPL_SERVLOCKOFF, user->server->GetName(), "Open for new connections"); ServerInstance->SNO->WriteGlobalSno('a', "Oper %s used UNLOCKSERV to allow new connections", user->nick.c_str()); return CMD_SUCCESS; } @@ -80,52 +84,44 @@ public: class ModuleLockserv : public Module { -private: - bool locked; + std::string locked; CommandLockserv lockcommand; CommandUnlockserv unlockcommand; -public: + public: ModuleLockserv() : lockcommand(this, locked), unlockcommand(this, locked) { } - void init() - { - locked = false; - ServerInstance->Modules->AddService(lockcommand); - ServerInstance->Modules->AddService(unlockcommand); - Implementation eventlist[] = { I_OnUserRegister, I_OnRehash, I_OnCheckReady }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleLockserv() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { + // Emergency way to unlock + if (!status.srcuser) + locked.clear(); } - - virtual void OnRehash(User* user) + void OnModuleRehash(User* user, const std::string& param) CXX11_OVERRIDE { - // Emergency way to unlock - if (!user) locked = false; + if (irc::equals(param, "lockserv") && !locked.empty()) + locked.clear(); } - virtual ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { - if (locked) + if (!locked.empty()) { - ServerInstance->Users->QuitUser(user, "Server is temporarily closed. Please try again later."); + ServerInstance->Users->QuitUser(user, locked); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - virtual ModResult OnCheckReady(LocalUser* user) + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { - return locked ? MOD_RES_DENY : MOD_RES_PASSTHRU; + return !locked.empty() ? MOD_RES_DENY : MOD_RES_PASSTHRU; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows locking of the server to stop all incoming connections until unlocked again", VF_VENDOR); } diff --git a/src/modules/m_maphide.cpp b/src/modules/m_maphide.cpp index 546e342ae..7a1ee39e1 100644 --- a/src/modules/m_maphide.cpp +++ b/src/modules/m_maphide.cpp @@ -19,44 +19,30 @@ #include "inspircd.h" -/* $ModDesc: Hide /MAP and /LINKS in the same form as ircu (mostly useless) */ - class ModuleMapHide : public Module { std::string url; public: - void init() - { - Implementation eventlist[] = { I_OnPreCommand, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } - - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { url = ServerInstance->Config->ConfValue("security")->getString("maphide"); } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { - if (validated && !IS_OPER(user) && !url.empty() && (command == "MAP" || command == "LINKS")) + if (validated && !user->IsOper() && !url.empty() && (command == "MAP" || command == "LINKS")) { - user->WriteServ("NOTICE %s :/%s has been disabled; visit %s", user->nick.c_str(), command.c_str(), url.c_str()); + user->WriteNotice("/" + command + " has been disabled; visit " + url); return MOD_RES_DENY; } else return MOD_RES_PASSTHRU; } - virtual ~ModuleMapHide() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Hide /MAP and /LINKS in the same form as ircu (mostly useless)", VF_VENDOR); } }; MODULE_INIT(ModuleMapHide) - diff --git a/src/modules/m_md5.cpp b/src/modules/m_md5.cpp index c902ee3cb..8de70872f 100644 --- a/src/modules/m_md5.cpp +++ b/src/modules/m_md5.cpp @@ -21,13 +21,8 @@ */ -/* $ModDesc: Allows for MD5 encrypted oper passwords */ - #include "inspircd.h" -#ifdef HAS_STDINT -#include <stdint.h> -#endif -#include "hash.h" +#include "modules/hash.h" /* The four core functions - F1 is optimized somewhat */ #define F1(x, y, z) (z ^ (x & (y ^ z))) @@ -39,10 +34,6 @@ #define MD5STEP(f,w,x,y,z,in,s) \ (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x) -#ifndef HAS_STDINT -typedef unsigned int uint32_t; -#endif - typedef uint32_t word32; /* NOT unsigned long. We don't support 16 bit platforms, anyway. */ typedef unsigned char byte; @@ -70,23 +61,13 @@ class MD5Provider : public HashProvider } while (--words); } - void MD5Init(MD5Context *ctx, unsigned int* ikey = NULL) + void MD5Init(MD5Context *ctx) { /* These are the defaults for md5 */ - if (!ikey) - { - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - } - else - { - ctx->buf[0] = ikey[0]; - ctx->buf[1] = ikey[1]; - ctx->buf[2] = ikey[2]; - ctx->buf[3] = ikey[3]; - } + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; ctx->bytes[0] = 0; ctx->bytes[1] = 0; @@ -245,44 +226,23 @@ class MD5Provider : public HashProvider } - void MyMD5(void *dest, void *orig, int len, unsigned int* ikey) + void MyMD5(void *dest, void *orig, int len) { MD5Context context; - MD5Init(&context, ikey); + MD5Init(&context); MD5Update(&context, (const unsigned char*)orig, len); MD5Final((unsigned char*)dest, &context); } - - void GenHash(const char* src, char* dest, const char* xtab, unsigned int* ikey, size_t srclen) - { - unsigned char bytes[16]; - - MyMD5((char*)bytes, (void*)src, srclen, ikey); - - for (int i = 0; i < 16; i++) - { - *dest++ = xtab[bytes[i] / 16]; - *dest++ = xtab[bytes[i] % 16]; - } - *dest++ = 0; - } public: - std::string sum(const std::string& data) + std::string GenerateRaw(const std::string& data) CXX11_OVERRIDE { char res[16]; - MyMD5(res, (void*)data.data(), data.length(), NULL); + MyMD5(res, (void*)data.data(), data.length()); return std::string(res, 16); } - std::string sumIV(unsigned int* IV, const char* HexMap, const std::string &sdata) - { - char res[33]; - GenHash(sdata.data(), res, HexMap, IV, sdata.length()); - return res; - } - - MD5Provider(Module* parent) : HashProvider(parent, "hash/md5", 16, 64) {} + MD5Provider(Module* parent) : HashProvider(parent, "md5", 16, 64) {} }; class ModuleMD5 : public Module @@ -291,10 +251,9 @@ class ModuleMD5 : public Module public: ModuleMD5() : md5(this) { - ServerInstance->Modules->AddService(md5); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements MD5 hashing",VF_VENDOR); } diff --git a/src/modules/m_messageflood.cpp b/src/modules/m_messageflood.cpp index 9ff17924d..0707e6ae4 100644 --- a/src/modules/m_messageflood.cpp +++ b/src/modules/m_messageflood.cpp @@ -24,8 +24,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel mode +f (message flood protection) */ +#include "modules/exemption.h" /** Holds flood settings and state for mode +f */ @@ -36,9 +35,12 @@ class floodsettings unsigned int secs; unsigned int lines; time_t reset; - std::map<User*, unsigned int> counters; + insp::flat_map<User*, unsigned int> counters; - floodsettings(bool a, int b, int c) : ban(a), secs(b), lines(c) + floodsettings(bool a, unsigned int b, unsigned int c) + : ban(a) + , secs(b) + , lines(c) { reset = ServerInstance->Time() + secs; } @@ -56,92 +58,77 @@ class floodsettings void clear(User* who) { - std::map<User*, unsigned int>::iterator iter = counters.find(who); - if (iter != counters.end()) - { - counters.erase(iter); - } + counters.erase(who); } }; /** Handles channel mode +f */ -class MsgFlood : public ModeHandler +class MsgFlood : public ParamMode<MsgFlood, SimpleExtItem<floodsettings> > { public: - SimpleExtItem<floodsettings> ext; - MsgFlood(Module* Creator) : ModeHandler(Creator, "flood", 'f', PARAM_SETONLY, MODETYPE_CHANNEL), - ext("messageflood", Creator) { } + MsgFlood(Module* Creator) + : ParamMode<MsgFlood, SimpleExtItem<floodsettings> >(Creator, "flood", 'f') + { + } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE { - if (adding) + std::string::size_type colon = parameter.find(':'); + if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) { - std::string::size_type colon = parameter.find(':'); - if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) - { - source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); - return MODEACTION_DENY; - } - - /* Set up the flood parameters for this channel */ - bool ban = (parameter[0] == '*'); - unsigned int nlines = ConvToInt(parameter.substr(ban ? 1 : 0, ban ? colon-1 : colon)); - unsigned int nsecs = ConvToInt(parameter.substr(colon+1)); - - if ((nlines<2) || (nsecs<1)) - { - source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); - return MODEACTION_DENY; - } + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; + } - floodsettings* f = ext.get(channel); - if ((f) && (nlines == f->lines) && (nsecs == f->secs) && (ban == f->ban)) - // mode params match - return MODEACTION_DENY; + /* Set up the flood parameters for this channel */ + bool ban = (parameter[0] == '*'); + unsigned int nlines = ConvToInt(parameter.substr(ban ? 1 : 0, ban ? colon-1 : colon)); + unsigned int nsecs = ConvToInt(parameter.substr(colon+1)); - ext.set(channel, new floodsettings(ban, nsecs, nlines)); - parameter = std::string(ban ? "*" : "") + ConvToStr(nlines) + ":" + ConvToStr(nsecs); - channel->SetModeParam('f', parameter); - return MODEACTION_ALLOW; - } - else + if ((nlines<2) || (nsecs<1)) { - if (!channel->IsModeSet('f')) - return MODEACTION_DENY; - - ext.unset(channel); - channel->SetModeParam('f', ""); - return MODEACTION_ALLOW; + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; } + + ext.set(channel, new floodsettings(ban, nsecs, nlines)); + return MODEACTION_ALLOW; + } + + void SerializeParam(Channel* chan, const floodsettings* fs, std::string& out) + { + if (fs->ban) + out.push_back('*'); + out.append(ConvToStr(fs->lines)).push_back(':'); + out.append(ConvToStr(fs->secs)); } }; class ModuleMsgFlood : public Module { + CheckExemption::EventProvider exemptionprov; MsgFlood mf; public: ModuleMsgFlood() - : mf(this) + : exemptionprov(this) + , mf(this) { } - void init() + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(mf); - ServerInstance->Modules->AddService(mf.ext); - Implementation eventlist[] = { I_OnUserPreNotice, I_OnUserPreMessage }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } + if (target.type != MessageTarget::TYPE_CHANNEL) + return MOD_RES_PASSTHRU; - ModResult ProcessMessages(User* user,Channel* dest, const std::string &text) - { - if ((!IS_LOCAL(user)) || !dest->IsModeSet('f')) + Channel* dest = target.Get<Channel>(); + if ((!IS_LOCAL(user)) || !dest->IsModeSet(mf)) return MOD_RES_PASSTHRU; - if (ServerInstance->OnCheckExemption(user,dest,"flood") == MOD_RES_ALLOW) + ModResult res = CheckExemption::Call(exemptionprov, user, dest, "flood"); + if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; floodsettings *f = mf.ext.get(dest); @@ -153,17 +140,15 @@ class ModuleMsgFlood : public Module f->clear(user); if (f->ban) { - std::vector<std::string> parameters; - parameters.push_back(dest->name); - parameters.push_back("+b"); - parameters.push_back(user->MakeWildHost()); - ServerInstance->SendGlobalMode(parameters, ServerInstance->FakeClient); + Modes::ChangeList changelist; + changelist.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), "*!*@" + user->GetDisplayedHost()); + ServerInstance->Modes->Process(ServerInstance->FakeClient, dest, NULL, changelist); } - char kickmessage[MAXBUF]; - snprintf(kickmessage, MAXBUF, "Channel flood triggered (limit is %u lines in %u secs)", f->lines, f->secs); + const std::string kickMessage = "Channel flood triggered (trigger is " + ConvToStr(f->lines) + + " lines in " + ConvToStr(f->secs) + " secs)"; - dest->KickUser(ServerInstance->FakeClient, user, kickmessage); + dest->KickUser(ServerInstance->FakeClient, user, kickMessage); return MOD_RES_DENY; } @@ -172,30 +157,13 @@ class ModuleMsgFlood : public Module return MOD_RES_PASSTHRU; } - ModResult OnUserPreMessage(User *user, void *dest, int target_type, std::string &text, char status, CUList &exempt_list) - { - if (target_type == TYPE_CHANNEL) - return ProcessMessages(user,(Channel*)dest,text); - - return MOD_RES_PASSTHRU; - } - - ModResult OnUserPreNotice(User *user, void *dest, int target_type, std::string &text, char status, CUList &exempt_list) - { - if (target_type == TYPE_CHANNEL) - return ProcessMessages(user,(Channel*)dest,text); - - return MOD_RES_PASSTHRU; - } - - void Prioritize() + void Prioritize() CXX11_OVERRIDE { // we want to be after all modules that might deny the message (e.g. m_muteban, m_noctcp, m_blockcolor, etc.) ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST); - ServerInstance->Modules->SetPriority(this, I_OnUserPreNotice, PRIORITY_LAST); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +f (message flood protection)", VF_VENDOR); } diff --git a/src/modules/m_mlock.cpp b/src/modules/m_mlock.cpp index d1df81354..85787ae96 100644 --- a/src/modules/m_mlock.cpp +++ b/src/modules/m_mlock.cpp @@ -17,30 +17,30 @@ */ -/* $ModDesc: Implements the ability to have server-side MLOCK enforcement. */ - #include "inspircd.h" +enum +{ + // From Charybdis. + ERR_MLOCKRESTRICTED = 742 +}; + class ModuleMLock : public Module { -private: StringExtItem mlock; -public: - ModuleMLock() : mlock("mlock", this) {}; - - void init() + public: + ModuleMLock() + : mlock("mlock", ExtensionItem::EXT_CHANNEL, this) { - ServerInstance->Modules->Attach(I_OnRawMode, this); - ServerInstance->Modules->AddService(this->mlock); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements the ability to have server-side MLOCK enforcement.", VF_VENDOR); } - ModResult OnRawMode(User* source, Channel* channel, const char mode, const std::string& parameter, bool adding, int pcnt) + ModResult OnRawMode(User* source, Channel* channel, ModeHandler* mh, const std::string& parameter, bool adding) CXX11_OVERRIDE { if (!channel) return MOD_RES_PASSTHRU; @@ -52,17 +52,16 @@ public: if (!mlock_str) return MOD_RES_PASSTHRU; + const char mode = mh->GetModeChar(); std::string::size_type p = mlock_str->find(mode); if (p != std::string::npos) { - source->WriteNumeric(742, "%s %c %s :MODE cannot be set due to channel having an active MLOCK restriction policy", - channel->name.c_str(), mode, mlock_str->c_str()); + source->WriteNumeric(ERR_MLOCKRESTRICTED, channel->name, mode, *mlock_str, "MODE cannot be set due to channel having an active MLOCK restriction policy"); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - }; MODULE_INIT(ModuleMLock) diff --git a/src/commands/cmd_modenotice.cpp b/src/modules/m_modenotice.cpp index 852b72f6a..5311015b7 100644 --- a/src/commands/cmd_modenotice.cpp +++ b/src/modules/m_modenotice.cpp @@ -28,10 +28,12 @@ class CommandModeNotice : public Command flags_needed = 'o'; } - CmdResult Handle(const std::vector<std::string>& parameters, User *src) + CmdResult Handle(User* src, const Params& parameters) CXX11_OVERRIDE { + std::string msg = "*** From " + src->nick + ": " + parameters[1]; int mlen = parameters[0].length(); - for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); i++) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { User* user = *i; for (int n = 0; n < mlen; n++) @@ -39,17 +41,32 @@ class CommandModeNotice : public Command if (!user->IsModeSet(parameters[0][n])) goto next_user; } - user->Write(":%s NOTICE %s :*** From %s: %s", ServerInstance->Config->ServerName.c_str(), - user->nick.c_str(), src->nick.c_str(), parameters[1].c_str()); + user->WriteNotice(msg); next_user: ; } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - return ROUTE_BROADCAST; + return ROUTE_OPT_BCAST; } }; -COMMAND_INIT(CommandModeNotice) +class ModuleModeNotice : public Module +{ + CommandModeNotice cmd; + + public: + ModuleModeNotice() + : cmd(this) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the /MODENOTICE command", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleModeNotice) diff --git a/src/modules/m_monitor.cpp b/src/modules/m_monitor.cpp new file mode 100644 index 000000000..fd72c7320 --- /dev/null +++ b/src/modules/m_monitor.cpp @@ -0,0 +1,444 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +namespace IRCv3 +{ + namespace Monitor + { + class ExtItem; + struct Entry; + class Manager; + class ManagerInternal; + + typedef std::vector<Entry*> WatchedList; + typedef std::vector<LocalUser*> WatcherList; + } +} + +struct IRCv3::Monitor::Entry +{ + WatcherList watchers; + std::string nick; + + void SetNick(const std::string& Nick) + { + nick.clear(); + // We may show this string to other users so do not leak the casing + std::transform(Nick.begin(), Nick.end(), std::back_inserter(nick), ::tolower); + } + + const std::string& GetNick() const { return nick; } +}; + +class IRCv3::Monitor::Manager +{ + struct ExtData + { + WatchedList list; + }; + + class ExtItem : public ExtensionItem + { + Manager& manager; + + public: + ExtItem(Module* mod, const std::string& extname, Manager& managerref) + : ExtensionItem(extname, ExtensionItem::EXT_USER, mod) + , manager(managerref) + { + } + + ExtData* get(Extensible* container, bool create = false) + { + ExtData* extdata = static_cast<ExtData*>(get_raw(container)); + if ((!extdata) && (create)) + { + extdata = new ExtData; + set_raw(container, extdata); + } + return extdata; + } + + void unset(Extensible* container) + { + free(container, unset_raw(container)); + } + + std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE + { + std::string ret; + if (format == FORMAT_NETWORK) + return ret; + + const ExtData* extdata = static_cast<ExtData*>(item); + for (WatchedList::const_iterator i = extdata->list.begin(); i != extdata->list.end(); ++i) + { + const Entry* entry = *i; + ret.append(entry->GetNick()).push_back(' '); + } + if (!ret.empty()) + ret.erase(ret.size()-1); + return ret; + } + + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE; + + void free(Extensible* container, void* item) CXX11_OVERRIDE + { + delete static_cast<ExtData*>(item); + } + }; + + public: + Manager(Module* mod, const std::string& extname) + : ext(mod, extname, *this) + { + } + + enum WatchResult + { + WR_OK, + WR_TOOMANY, + WR_ALREADYWATCHING, + WR_INVALIDNICK + }; + + WatchResult Watch(LocalUser* user, const std::string& nick, unsigned int maxwatch) + { + if (!ServerInstance->IsNick(nick)) + return WR_INVALIDNICK; + + WatchedList* watched = GetWatchedPriv(user, true); + if (watched->size() >= maxwatch) + return WR_TOOMANY; + + Entry* entry = AddWatcher(nick, user); + if (stdalgo::isin(*watched, entry)) + return WR_ALREADYWATCHING; + + entry->watchers.push_back(user); + watched->push_back(entry); + return WR_OK; + } + + bool Unwatch(LocalUser* user, const std::string& nick) + { + WatchedList* list = GetWatchedPriv(user); + if (!list) + return false; + + bool ret = RemoveWatcher(nick, user, *list); + // If no longer watching any nick unset ext + if (list->empty()) + ext.unset(user); + return ret; + } + + const WatchedList& GetWatched(LocalUser* user) + { + WatchedList* list = GetWatchedPriv(user); + if (list) + return *list; + return emptywatchedlist; + } + + void UnwatchAll(LocalUser* user) + { + WatchedList* list = GetWatchedPriv(user); + if (!list) + return; + + while (!list->empty()) + { + Entry* entry = list->front(); + RemoveWatcher(entry->GetNick(), user, *list); + } + ext.unset(user); + } + + WatcherList* GetWatcherList(const std::string& nick) + { + Entry* entry = Find(nick); + if (entry) + return &entry->watchers; + return NULL; + } + + static User* FindNick(const std::string& nick) + { + User* user = ServerInstance->FindNickOnly(nick); + if ((user) && (user->registered == REG_ALL)) + return user; + return NULL; + } + + private: + typedef TR1NS::unordered_map<std::string, Entry, irc::insensitive, irc::StrHashComp> NickHash; + + Entry* Find(const std::string& nick) + { + NickHash::iterator it = nicks.find(nick); + if (it != nicks.end()) + return &it->second; + return NULL; + } + + Entry* AddWatcher(const std::string& nick, LocalUser* user) + { + std::pair<NickHash::iterator, bool> ret = nicks.insert(std::make_pair(nick, Entry())); + Entry& entry = ret.first->second; + if (ret.second) + entry.SetNick(nick); + return &entry; + } + + bool RemoveWatcher(const std::string& nick, LocalUser* user, WatchedList& watchedlist) + { + NickHash::iterator it = nicks.find(nick); + // If nobody is watching this nick the user trying to remove it isn't watching it for sure + if (it == nicks.end()) + return false; + + Entry& entry = it->second; + // Erase from the user's list of watched nicks + if (!stdalgo::vector::swaperase(watchedlist, &entry)) + return false; // User is not watching this nick + + // Erase from the nick's list of watching users + stdalgo::vector::swaperase(entry.watchers, user); + + // If nobody else is watching the nick remove map entry + if (entry.watchers.empty()) + nicks.erase(it); + + return true; + } + + WatchedList* GetWatchedPriv(LocalUser* user, bool create = false) + { + ExtData* extdata = ext.get(user, create); + if (!extdata) + return NULL; + return &extdata->list; + } + + NickHash nicks; + ExtItem ext; + WatchedList emptywatchedlist; +}; + +// inline is needed in static builds to support m_watch including the Manager code from this file +inline void IRCv3::Monitor::Manager::ExtItem::unserialize(SerializeFormat format, Extensible* container, const std::string& value) +{ + if (format == FORMAT_NETWORK) + return; + + irc::spacesepstream ss(value); + for (std::string nick; ss.GetToken(nick); ) + manager.Watch(static_cast<LocalUser*>(container), nick, UINT_MAX); +} + +#ifndef INSPIRCD_MONITOR_MANAGER_ONLY + +enum +{ + RPL_MONONLINE = 730, + RPL_MONOFFLINE = 731, + RPL_MONLIST = 732, + RPL_ENDOFMONLIST = 733, + ERR_MONLISTFULL = 734 +}; + +class CommandMonitor : public SplitCommand +{ + typedef Numeric::Builder<> ReplyBuilder; + // Additional penalty for the /MONITOR L and /MONITOR S commands that request a list from the server + static const unsigned int ListPenalty = 3000; + + IRCv3::Monitor::Manager& manager; + + void HandlePlus(LocalUser* user, const std::string& input) + { + ReplyBuilder online(user, RPL_MONONLINE); + ReplyBuilder offline(user, RPL_MONOFFLINE); + irc::commasepstream ss(input); + for (std::string nick; ss.GetToken(nick); ) + { + IRCv3::Monitor::Manager::WatchResult result = manager.Watch(user, nick, maxmonitor); + if (result == IRCv3::Monitor::Manager::WR_TOOMANY) + { + // List is full, send error which includes the remaining nicks that were not processed + user->WriteNumeric(ERR_MONLISTFULL, maxmonitor, InspIRCd::Format("%s%s%s", nick.c_str(), (ss.StreamEnd() ? "" : ","), ss.GetRemaining().c_str()), "Monitor list is full"); + break; + } + else if (result != IRCv3::Monitor::Manager::WR_OK) + continue; // Already added or invalid nick + + ReplyBuilder& out = (IRCv3::Monitor::Manager::FindNick(nick) ? online : offline); + out.Add(nick); + } + + online.Flush(); + offline.Flush(); + } + + void HandleMinus(LocalUser* user, const std::string& input) + { + irc::commasepstream ss(input); + for (std::string nick; ss.GetToken(nick); ) + manager.Unwatch(user, nick); + } + + public: + unsigned int maxmonitor; + + CommandMonitor(Module* mod, IRCv3::Monitor::Manager& managerref) + : SplitCommand(mod, "MONITOR", 1) + , manager(managerref) + { + Penalty = 2; + allow_empty_last_param = false; + syntax = "[C|L|S|+ <nick1>[,<nick2>]|- <nick1>[,<nick2>]"; + } + + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE + { + char subcmd = toupper(parameters[0][0]); + if (subcmd == '+') + { + if (parameters.size() > 1) + HandlePlus(user, parameters[1]); + } + else if (subcmd == '-') + { + if (parameters.size() > 1) + HandleMinus(user, parameters[1]); + } + else if (subcmd == 'C') + { + manager.UnwatchAll(user); + } + else if (subcmd == 'L') + { + user->CommandFloodPenalty += ListPenalty; + const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user); + ReplyBuilder out(user, RPL_MONLIST); + for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i) + { + IRCv3::Monitor::Entry* entry = *i; + out.Add(entry->GetNick()); + } + out.Flush(); + user->WriteNumeric(RPL_ENDOFMONLIST, "End of MONITOR list"); + } + else if (subcmd == 'S') + { + user->CommandFloodPenalty += ListPenalty; + + ReplyBuilder online(user, RPL_MONONLINE); + ReplyBuilder offline(user, RPL_MONOFFLINE); + + const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user); + for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i) + { + IRCv3::Monitor::Entry* entry = *i; + ReplyBuilder& out = (IRCv3::Monitor::Manager::FindNick(entry->GetNick()) ? online : offline); + out.Add(entry->GetNick()); + } + + online.Flush(); + offline.Flush(); + } + else + return CMD_FAILURE; + + return CMD_SUCCESS; + } +}; + +class ModuleMonitor : public Module +{ + IRCv3::Monitor::Manager manager; + CommandMonitor cmd; + + void SendAlert(unsigned int numeric, const std::string& nick) + { + const IRCv3::Monitor::WatcherList* list = manager.GetWatcherList(nick); + if (!list) + return; + + for (IRCv3::Monitor::WatcherList::const_iterator i = list->begin(); i != list->end(); ++i) + { + LocalUser* curr = *i; + curr->WriteNumeric(numeric, nick); + } + } + + public: + ModuleMonitor() + : manager(this, "monitor") + , cmd(this, manager) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* tag = ServerInstance->Config->ConfValue("monitor"); + cmd.maxmonitor = tag->getUInt("maxentries", 30, 1); + } + + void OnPostConnect(User* user) CXX11_OVERRIDE + { + SendAlert(RPL_MONONLINE, user->nick); + } + + void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE + { + // Detect and ignore nickname case change + if (ServerInstance->FindNickOnly(oldnick) == user) + return; + + SendAlert(RPL_MONOFFLINE, oldnick); + SendAlert(RPL_MONONLINE, user->nick); + } + + void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE + { + LocalUser* localuser = IS_LOCAL(user); + if (localuser) + manager.UnwatchAll(localuser); + SendAlert(RPL_MONOFFLINE, user->nick); + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + tokens["MONITOR"] = ConvToStr(cmd.maxmonitor); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides MONITOR support", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleMonitor) + +#endif diff --git a/src/modules/m_muteban.cpp b/src/modules/m_muteban.cpp index 767af2901..7698835b1 100644 --- a/src/modules/m_muteban.cpp +++ b/src/modules/m_muteban.cpp @@ -20,53 +20,40 @@ #include "inspircd.h" -/* $ModDesc: Implements extban +b m: - mute bans */ - class ModuleQuietBan : public Module { - private: public: - void init() - { - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleQuietBan() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements extban +b m: - mute bans",VF_OPTCOMMON|VF_VENDOR); } - virtual ModResult OnUserPreMessage(User *user, void *dest, int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (!IS_LOCAL(user) || target_type != TYPE_CHANNEL) + if (!IS_LOCAL(user) || target.type != MessageTarget::TYPE_CHANNEL) return MOD_RES_PASSTHRU; - Channel* chan = static_cast<Channel*>(dest); + Channel* chan = target.Get<Channel>(); if (chan->GetExtBanStatus(user, 'm') == MOD_RES_DENY && chan->GetPrefixValue(user) < VOICE_VALUE) { - user->WriteNumeric(404, "%s %s :Cannot send to channel (you're muted)", user->nick.c_str(), chan->name.c_str()); + bool notifyuser = ServerInstance->Config->ConfValue("muteban")->getBool("notifyuser", true); + if (!notifyuser) + { + details.echo_original = true; + return MOD_RES_DENY; + } + + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're muted)"); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreNotice(User *user, void *dest, int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreMessage(user, dest, target_type, text, status, exempt_list); - } - - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('m'); + tokens["EXTBAN"].push_back('m'); } }; - MODULE_INIT(ModuleQuietBan) - diff --git a/src/modules/m_namedmodes.cpp b/src/modules/m_namedmodes.cpp index 46710946b..8872ba629 100644 --- a/src/modules/m_namedmodes.cpp +++ b/src/modules/m_namedmodes.cpp @@ -17,56 +17,64 @@ */ -/* $ModDesc: Provides the ability to manipulate modes via long names. */ - #include "inspircd.h" -static void DisplayList(User* user, Channel* channel) +enum { - std::stringstream items; - for(char letter = 'A'; letter <= 'z'; letter++) + // InspIRCd-specific. + RPL_ENDOFPROPLIST = 960, + RPL_PROPLIST = 961 +}; + +static void DisplayList(LocalUser* user, Channel* channel) +{ + Numeric::ParamBuilder<1> numeric(user, RPL_PROPLIST); + numeric.AddStatic(channel->name); + + const ModeParser::ModeHandlerMap& mhs = ServerInstance->Modes->GetModes(MODETYPE_CHANNEL); + for (ModeParser::ModeHandlerMap::const_iterator i = mhs.begin(); i != mhs.end(); ++i) { - ModeHandler* mh = ServerInstance->Modes->FindMode(letter, MODETYPE_CHANNEL); - if (!mh || mh->IsListMode()) + ModeHandler* mh = i->second; + if (!channel->IsModeSet(mh)) continue; - if (!channel->IsModeSet(letter)) - continue; - items << " +" << mh->name; - if (mh->GetNumParams(true)) + numeric.Add("+" + mh->name); + if (mh->NeedsParam(true)) { - if ((letter == 'k') && (!channel->HasUser(user)) && (!user->HasPrivPermission("channels/auspex"))) - items << " <key>"; + if ((mh->name == "key") && (!channel->HasUser(user)) && (!user->HasPrivPermission("channels/auspex"))) + numeric.Add("<key>"); else - items << " " << channel->GetModeParameter(letter); + numeric.Add(channel->GetModeParameter(mh)); } } - char pfx[MAXBUF]; - snprintf(pfx, MAXBUF, ":%s 961 %s %s", ServerInstance->Config->ServerName.c_str(), user->nick.c_str(), channel->name.c_str()); - user->SendText(std::string(pfx), items); - user->WriteNumeric(960, "%s %s :End of mode list", user->nick.c_str(), channel->name.c_str()); + numeric.Flush(); + user->WriteNumeric(RPL_ENDOFPROPLIST, channel->name, "End of mode list"); } -class CommandProp : public Command +class CommandProp : public SplitCommand { public: - CommandProp(Module* parent) : Command(parent, "PROP", 1) + CommandProp(Module* parent) + : SplitCommand(parent, "PROP", 1) { syntax = "<user|channel> {[+-]<mode> [<value>]}*"; } - CmdResult Handle(const std::vector<std::string> ¶meters, User *src) + CmdResult HandleLocal(LocalUser* src, const Params& parameters) CXX11_OVERRIDE { + Channel* const chan = ServerInstance->FindChan(parameters[0]); + if (!chan) + { + src->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); + return CMD_FAILURE; + } + if (parameters.size() == 1) { - Channel* chan = ServerInstance->FindChan(parameters[0]); - if (chan) - DisplayList(src, chan); + DisplayList(src, chan); return CMD_SUCCESS; } unsigned int i = 1; - std::vector<std::string> modes; - modes.push_back(parameters[0]); - modes.push_back(""); + Modes::ChangeList modes; while (i < parameters.size()) { std::string prop = parameters[i++]; @@ -76,21 +84,19 @@ class CommandProp : public Command if (prop[0] == '+' || prop[0] == '-') prop.erase(prop.begin()); - for(char letter = 'A'; letter <= 'z'; letter++) + ModeHandler* mh = ServerInstance->Modes->FindMode(prop, MODETYPE_CHANNEL); + if (mh) { - ModeHandler* mh = ServerInstance->Modes->FindMode(letter, MODETYPE_CHANNEL); - if (mh && mh->name == prop) + if (mh->NeedsParam(plus)) { - modes[1].append((plus ? "+" : "-") + std::string(1, letter)); - if (mh->GetNumParams(plus)) - { - if (i != parameters.size()) - modes.push_back(parameters[i++]); - } + if (i != parameters.size()) + modes.push(mh, plus, parameters[i++]); } + else + modes.push(mh, plus); } } - ServerInstance->SendGlobalMode(modes, src); + ServerInstance->Modes->ProcessSingle(src, chan, NULL, modes, ModeParser::MODE_CHECKACCESS); return CMD_SUCCESS; } }; @@ -102,6 +108,13 @@ class DummyZ : public ModeHandler { list = true; } + + // Handle /MODE #chan Z + void DisplayList(User* user, Channel* chan) CXX11_OVERRIDE + { + if (IS_LOCAL(user)) + ::DisplayList(static_cast<LocalUser*>(user), chan); + } }; class ModuleNamedModes : public Module @@ -113,99 +126,69 @@ class ModuleNamedModes : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(dummyZ); - - Implementation eventlist[] = { I_OnPreMode }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the ability to manipulate modes via long names.",VF_VENDOR); } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { ServerInstance->Modules->SetPriority(this, I_OnPreMode, PRIORITY_FIRST); } - ModResult OnPreMode(User* source, User* dest, Channel* channel, const std::vector<std::string>& parameters) + ModResult OnPreMode(User* source, User* dest, Channel* channel, Modes::ChangeList& modes) CXX11_OVERRIDE { if (!channel) return MOD_RES_PASSTHRU; - if (parameters[1].find('Z') == std::string::npos) - return MOD_RES_PASSTHRU; - if (parameters.size() <= 2) - { - DisplayList(source, channel); - return MOD_RES_DENY; - } - std::vector<std::string> newparms; - newparms.push_back(parameters[0]); - newparms.push_back(parameters[1]); - - std::string modelist = newparms[1]; - bool adding = true; - unsigned int param_at = 2; - for(unsigned int i = 0; i < modelist.length(); i++) + Modes::ChangeList::List& list = modes.getlist(); + for (Modes::ChangeList::List::iterator i = list.begin(); i != list.end(); ) { - unsigned char modechar = modelist[i]; - if (modechar == '+' || modechar == '-') + Modes::Change& curr = *i; + // Replace all namebase (dummyZ) modes being changed with the actual + // mode handler and parameter. The parameter format of the namebase mode is + // <modename>[=<parameter>]. + if (curr.mh == &dummyZ) { - adding = (modechar == '+'); - continue; - } - ModeHandler *mh = ServerInstance->Modes->FindMode(modechar, MODETYPE_CHANNEL); - if (modechar == 'Z') - { - modechar = 0; - std::string name, value; - if (param_at < parameters.size()) - name = parameters[param_at++]; + std::string name = curr.param; + std::string value; std::string::size_type eq = name.find('='); if (eq != std::string::npos) { - value = name.substr(eq + 1); - name = name.substr(0, eq); + value.assign(name, eq + 1, std::string::npos); + name.erase(eq); + } + + ModeHandler* mh = ServerInstance->Modes->FindMode(name, MODETYPE_CHANNEL); + if (!mh) + { + // Mode handler not found + i = list.erase(i); + continue; } - for(char letter = 'A'; modechar == 0 && letter <= 'z'; letter++) + + curr.param.clear(); + if (mh->NeedsParam(curr.adding)) { - mh = ServerInstance->Modes->FindMode(letter, MODETYPE_CHANNEL); - if (mh && mh->name == name) + if (value.empty()) { - if (mh->GetNumParams(adding)) - { - if (!value.empty()) - { - newparms.push_back(value); - modechar = letter; - break; - } - } - else - { - modechar = letter; - break; - } + // Mode needs a parameter but there wasn't one + i = list.erase(i); + continue; } + + // Change parameter to the text after the '=' + curr.param = value; } - if (modechar) - modelist[i] = modechar; - else - modelist.erase(i--, 1); - } - else if (mh && mh->GetNumParams(adding) && param_at < parameters.size()) - { - newparms.push_back(parameters[param_at++]); + + // Put the actual ModeHandler in place of the namebase handler + curr.mh = mh; } + + ++i; } - newparms[1] = modelist; - ServerInstance->Modes->Process(newparms, source, false); - return MOD_RES_DENY; + + return MOD_RES_PASSTHRU; } }; diff --git a/src/modules/m_namesx.cpp b/src/modules/m_namesx.cpp index 82d311773..708b98e56 100644 --- a/src/modules/m_namesx.cpp +++ b/src/modules/m_namesx.cpp @@ -21,40 +21,27 @@ #include "inspircd.h" -#include "m_cap.h" - -/* $ModDesc: Provides the NAMESX (CAP multi-prefix) capability. */ +#include "modules/cap.h" class ModuleNamesX : public Module { + Cap::Capability cap; public: - GenericCap cap; ModuleNamesX() : cap(this, "multi-prefix") { } - void init() - { - Implementation eventlist[] = { I_OnPreCommand, I_OnNamesListItem, I_On005Numeric, I_OnEvent, I_OnSendWhoLine }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - - ~ModuleNamesX() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the NAMESX (CAP multi-prefix) capability.",VF_VENDOR); } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" NAMESX"); + tokens["NAMESX"]; } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { /* We don't actually create a proper command handler class for PROTOCTL, * because other modules might want to have PROTOCTL hooks too. @@ -65,66 +52,37 @@ class ModuleNamesX : public Module { if ((parameters.size()) && (!strcasecmp(parameters[0].c_str(),"NAMESX"))) { - cap.ext.set(user, 1); + cap.set(user, true); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - void OnNamesListItem(User* issuer, Membership* memb, std::string &prefixes, std::string &nick) + ModResult OnNamesListItem(User* issuer, Membership* memb, std::string& prefixes, std::string& nick) CXX11_OVERRIDE { - if (!cap.ext.get(issuer)) - return; + if (cap.get(issuer)) + prefixes = memb->GetAllPrefixChars(); - /* Some module hid this from being displayed, dont bother */ - if (nick.empty()) - return; - - prefixes = memb->chan->GetAllPrefixChars(memb->user); + return MOD_RES_PASSTHRU; } - void OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, std::string& line) + ModResult OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, Membership* memb, Numeric::Numeric& numeric) CXX11_OVERRIDE { - if (!cap.ext.get(source)) - return; - - // Channel names can contain ":", and ":" as a 'start-of-token' delimiter is - // only ever valid after whitespace, so... find the actual delimiter first! - // Thanks to FxChiP for pointing this out. - std::string::size_type pos = line.find(" :"); - if (pos == std::string::npos || pos == 0) - return; - pos--; - // Don't do anything if the user has no prefixes - if ((line[pos] == 'H') || (line[pos] == 'G') || (line[pos] == '*')) - return; - - // 352 21DAAAAAB #chan ident localhost insp21.test 21DAAAAAB H@ :0 a - // a b pos - std::string::size_type a = 4 + source->nick.length() + 1; - std::string::size_type b = line.find(' ', a); - if (b == std::string::npos) - return; - - // Try to find this channel - std::string channame = line.substr(a, b-a); - Channel* chan = ServerInstance->FindChan(channame); - if (!chan) - return; + if ((!memb) || (!cap.get(source))) + return MOD_RES_PASSTHRU; // Don't do anything if the user has only one prefix - std::string prefixes = chan->GetAllPrefixChars(user); + std::string prefixes = memb->GetAllPrefixChars(); if (prefixes.length() <= 1) - return; + return MOD_RES_PASSTHRU; - line.erase(pos, 1); - line.insert(pos, prefixes); - } + // #chan ident localhost insp22.test nick H@ :0 Attila + if (numeric.GetParams().size() < 6) + return MOD_RES_PASSTHRU; - void OnEvent(Event& ev) - { - cap.HandleEvent(ev); + numeric.GetParams()[5].append(prefixes, 1, std::string::npos); + return MOD_RES_PASSTHRU; } }; diff --git a/src/modules/m_nationalchars.cpp b/src/modules/m_nationalchars.cpp index bc90c9fad..81c2d2959 100644 --- a/src/modules/m_nationalchars.cpp +++ b/src/modules/m_nationalchars.cpp @@ -26,17 +26,12 @@ by Chernov-Phoenix Alexey (Phoenix@RusNet) mailto:phoenix /email address separator/ pravmail.ru */ #include "inspircd.h" -#include "caller.h" #include <fstream> -/* $ModDesc: Provides an ability to have non-RFC1459 nicks & support for national CASEMAPPING */ - -class lwbNickHandler : public HandlerBase2<bool, const char*, size_t> +class lwbNickHandler { public: - lwbNickHandler() { } - virtual ~lwbNickHandler() { } - virtual bool Call(const char*, size_t); + static bool Call(const std::string&); }; /*,m_reverse_additionalUp[256];*/ @@ -71,11 +66,12 @@ char utf8size(unsigned char * mb) /* Conditions added */ -bool lwbNickHandler::Call(const char* n, size_t max) +bool lwbNickHandler::Call(const std::string& nick) { - if (!n || !*n) + if (nick.empty()) return false; + const char* n = nick.c_str(); unsigned int p = 0; for (const char* i = n; *i; i++, p++) { @@ -215,21 +211,28 @@ bool lwbNickHandler::Call(const char* n, size_t max) } /* too long? or not -- pointer arithmetic rocks */ - return (p < max); + return (p < ServerInstance->Config->Limits.NickMax); } class ModuleNationalChars : public Module { - private: - lwbNickHandler myhandler; - std::string charset, casemapping; + std::string charset; unsigned char m_additional[256], m_additionalUp[256], m_lower[256], m_upper[256]; - caller2<bool, const char*, size_t> rememberer; + TR1NS::function<bool(const std::string&)> rememberer; bool forcequit; const unsigned char * lowermap_rememberer; unsigned char prev_map[256]; + template <typename T> + void RehashHashmap(T& hashmap) + { + T newhash(hashmap.bucket_count()); + for (typename T::const_iterator i = hashmap.begin(); i != hashmap.end(); ++i) + newhash.insert(std::make_pair(i->first, i->second)); + hashmap.swap(newhash); + } + void CheckRehash() { // See if anything changed @@ -238,20 +241,9 @@ class ModuleNationalChars : public Module memcpy(prev_map, national_case_insensitive_map, sizeof(prev_map)); - ServerInstance->RehashUsersAndChans(); - - // The OnGarbageCollect() method in m_watch rebuilds the hashmap used by it - Module* mod = ServerInstance->Modules->Find("m_watch.so"); - if (mod) - mod->OnGarbageCollect(); - - // Send a Request to m_spanningtree asking it to rebuild its hashmaps - mod = ServerInstance->Modules->Find("m_spanningtree.so"); - if (mod) - { - Request req(this, mod, "rehash"); - req.Send(); - } + RehashHashmap(ServerInstance->Users.clientlist); + RehashHashmap(ServerInstance->Users.uuidlist); + RehashHashmap(ServerInstance->chanlist); } public: @@ -261,34 +253,24 @@ class ModuleNationalChars : public Module memcpy(prev_map, national_case_insensitive_map, sizeof(prev_map)); } - void init() + void init() CXX11_OVERRIDE { memcpy(m_lower, rfc_case_insensitive_map, 256); national_case_insensitive_map = m_lower; - ServerInstance->IsNick = &myhandler; - - Implementation eventlist[] = { I_OnRehash, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); - } - - virtual void On005Numeric(std::string &output) - { - std::string tmp(casemapping); - tmp.insert(0, "CASEMAPPING="); - SearchAndReplace(output, std::string("CASEMAPPING=rfc1459"), tmp); + ServerInstance->IsNick = &lwbNickHandler::Call; } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("nationalchars"); charset = tag->getString("file"); - casemapping = tag->getString("casemapping", ServerConfig::CleanFilename(charset.c_str())); + std::string casemapping = tag->getString("casemapping", FileSystem::GetFileName(charset)); if (casemapping.find(' ') != std::string::npos) throw ModuleException("<nationalchars:casemapping> must not contain any spaces!"); + ServerInstance->Config->CaseMapping = casemapping; #if defined _WIN32 - if (!ServerInstance->Config->StartsWithWindowsDriveLetter(charset)) + if (!FileSystem::StartsWithWindowsDriveLetter(charset)) charset.insert(0, "./locales/"); #else if(charset[0] != '/') @@ -307,16 +289,19 @@ class ModuleNationalChars : public Module if (!forcequit) return; - for (LocalUserList::const_iterator iter = ServerInstance->Users->local_users.begin(); iter != ServerInstance->Users->local_users.end(); ++iter) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ) { /* Fix by Brain: Dont quit UID users */ + // Quitting the user removes it from the list User* n = *iter; - if (!isdigit(n->nick[0]) && !ServerInstance->IsNick(n->nick.c_str(), ServerInstance->Config->Limits.NickMax)) + ++iter; + if (!isdigit(n->nick[0]) && !ServerInstance->IsNick(n->nick)) ServerInstance->Users->QuitUser(n, message); } } - virtual ~ModuleNationalChars() + ~ModuleNationalChars() { ServerInstance->IsNick = rememberer; national_case_insensitive_map = lowermap_rememberer; @@ -324,7 +309,7 @@ class ModuleNationalChars : public Module CheckRehash(); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides an ability to have non-RFC1459 nicks & support for national CASEMAPPING", VF_VENDOR | VF_COMMON, charset); } @@ -340,10 +325,10 @@ class ModuleNationalChars : public Module /*so Bynets Unreal distribution stuff*/ bool loadtables(std::string filename, unsigned char ** tables, unsigned char cnt, char faillimit) { - std::ifstream ifs(filename.c_str()); + std::ifstream ifs(ServerInstance->Config->Paths.PrependConfig(filename).c_str()); if (ifs.fail()) { - ServerInstance->Logs->Log("m_nationalchars",DEFAULT,"loadtables() called for missing file: %s", filename.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "loadtables() called for missing file: %s", filename.c_str()); return false; } @@ -358,7 +343,7 @@ class ModuleNationalChars : public Module { if (loadtable(ifs, tables[n], 255) && (n < faillimit)) { - ServerInstance->Logs->Log("m_nationalchars",DEFAULT,"loadtables() called for illegal file: %s (line %d)", filename.c_str(), n+1); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "loadtables() called for illegal file: %s (line %d)", filename.c_str(), n+1); return false; } } diff --git a/src/modules/m_nickflood.cpp b/src/modules/m_nickflood.cpp index 04d7c8b5e..50e2b3d93 100644 --- a/src/modules/m_nickflood.cpp +++ b/src/modules/m_nickflood.cpp @@ -19,8 +19,10 @@ #include "inspircd.h" +#include "modules/exemption.h" -/* $ModDesc: Provides channel mode +F (nick flood protection) */ +// The number of seconds nickname changing will be blocked for. +static unsigned int duration; /** Holds settings and state associated with channel mode +F */ @@ -74,101 +76,85 @@ class nickfloodsettings void lock() { - unlocktime = ServerInstance->Time() + 60; + unlocktime = ServerInstance->Time() + duration; } }; /** Handles channel mode +F */ -class NickFlood : public ModeHandler +class NickFlood : public ParamMode<NickFlood, SimpleExtItem<nickfloodsettings> > { public: - SimpleExtItem<nickfloodsettings> ext; - NickFlood(Module* Creator) : ModeHandler(Creator, "nickflood", 'F', PARAM_SETONLY, MODETYPE_CHANNEL), - ext("nickflood", Creator) { } + NickFlood(Module* Creator) + : ParamMode<NickFlood, SimpleExtItem<nickfloodsettings> >(Creator, "nickflood", 'F') + { + } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE { - if (adding) + std::string::size_type colon = parameter.find(':'); + if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) { - std::string::size_type colon = parameter.find(':'); - if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) - { - source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); - return MODEACTION_DENY; - } - - /* Set up the flood parameters for this channel */ - unsigned int nnicks = ConvToInt(parameter.substr(0, colon)); - unsigned int nsecs = ConvToInt(parameter.substr(colon+1)); - - if ((nnicks<1) || (nsecs<1)) - { - source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); - return MODEACTION_DENY; - } + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; + } - nickfloodsettings* f = ext.get(channel); - if ((f) && (nnicks == f->nicks) && (nsecs == f->secs)) - // mode params match - return MODEACTION_DENY; + /* Set up the flood parameters for this channel */ + unsigned int nnicks = ConvToInt(parameter.substr(0, colon)); + unsigned int nsecs = ConvToInt(parameter.substr(colon+1)); - ext.set(channel, new nickfloodsettings(nsecs, nnicks)); - parameter = ConvToStr(nnicks) + ":" + ConvToStr(nsecs); - channel->SetModeParam('F', parameter); - return MODEACTION_ALLOW; - } - else + if ((nnicks<1) || (nsecs<1)) { - if (!channel->IsModeSet('F')) - return MODEACTION_DENY; - - ext.unset(channel); - channel->SetModeParam('F', ""); - return MODEACTION_ALLOW; + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return MODEACTION_DENY; } + + ext.set(channel, new nickfloodsettings(nsecs, nnicks)); + return MODEACTION_ALLOW; + } + + void SerializeParam(Channel* chan, const nickfloodsettings* nfs, std::string& out) + { + out.append(ConvToStr(nfs->nicks)).push_back(':'); + out.append(ConvToStr(nfs->secs)); } }; class ModuleNickFlood : public Module { + CheckExemption::EventProvider exemptionprov; NickFlood nf; public: - ModuleNickFlood() - : nf(this) + : exemptionprov(this) + , nf(this) { } - void init() + void ReadConfig(ConfigStatus&) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(nf); - ServerInstance->Modules->AddService(nf.ext); - Implementation eventlist[] = { I_OnUserPreNick, I_OnUserPostNick }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + ConfigTag* tag = ServerInstance->Config->ConfValue("nickflood"); + duration = tag->getDuration("duration", 60, 10, 600); } - ModResult OnUserPreNick(User* user, const std::string &newnick) + ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) CXX11_OVERRIDE { - if (ServerInstance->NICKForced.get(user)) /* Allow forced nick changes */ - return MOD_RES_PASSTHRU; - - for (UCListIter i = user->chans.begin(); i != user->chans.end(); i++) + for (User::ChanList::iterator i = user->chans.begin(); i != user->chans.end(); i++) { - Channel *channel = *i; + Channel* channel = (*i)->chan; ModResult res; nickfloodsettings *f = nf.ext.get(channel); if (f) { - res = ServerInstance->OnCheckExemption(user,channel,"nickflood"); + res = CheckExemption::Call(exemptionprov, user, channel, "nickflood"); if (res == MOD_RES_ALLOW) continue; if (f->islocked()) { - user->WriteNumeric(447, "%s :%s has been locked for nickchanges for 60 seconds because there have been more than %u nick changes in %u seconds", user->nick.c_str(), channel->name.c_str(), f->nicks, f->secs); + user->WriteNumeric(ERR_CANTCHANGENICK, InspIRCd::Format("%s has been locked for nickchanges for %u seconds because there have been more than %u nick changes in %u seconds", channel->name.c_str(), duration, f->nicks, f->secs)); return MOD_RES_DENY; } @@ -176,7 +162,7 @@ class ModuleNickFlood : public Module { f->clear(); f->lock(); - channel->WriteChannelWithServ((char*)ServerInstance->Config->ServerName.c_str(), "NOTICE %s :No nick changes are allowed for 60 seconds because there have been more than %u nick changes in %u seconds.", channel->name.c_str(), f->nicks, f->secs); + channel->WriteNotice(InspIRCd::Format("No nick changes are allowed for %u seconds because there have been more than %u nick changes in %u seconds.", duration, f->nicks, f->secs)); return MOD_RES_DENY; } } @@ -188,20 +174,20 @@ class ModuleNickFlood : public Module /* * XXX: HACK: We do the increment on the *POST* event here (instead of all together) because we have no way of knowing whether other modules would block a nickchange. */ - void OnUserPostNick(User* user, const std::string &oldnick) + void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE { if (isdigit(user->nick[0])) /* allow switches to UID */ return; - for (UCListIter i = user->chans.begin(); i != user->chans.end(); ++i) + for (User::ChanList::iterator i = user->chans.begin(); i != user->chans.end(); ++i) { - Channel *channel = *i; + Channel* channel = (*i)->chan; ModResult res; nickfloodsettings *f = nf.ext.get(channel); if (f) { - res = ServerInstance->OnCheckExemption(user,channel,"nickflood"); + res = CheckExemption::Call(exemptionprov, user, channel, "nickflood"); if (res == MOD_RES_ALLOW) return; @@ -214,11 +200,7 @@ class ModuleNickFlood : public Module } } - ~ModuleNickFlood() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Channel mode F - nick flood protection", VF_VENDOR); } diff --git a/src/modules/m_nicklock.cpp b/src/modules/m_nicklock.cpp index 4f5e5941c..964ed9780 100644 --- a/src/modules/m_nicklock.cpp +++ b/src/modules/m_nicklock.cpp @@ -22,7 +22,13 @@ #include "inspircd.h" -/* $ModDesc: Provides the NICKLOCK command, allows an oper to change a users nick and lock them to it until they quit */ +enum +{ + // InspIRCd-specific. + ERR_NICKNOTLOCKED = 946, + RPL_NICKLOCKON = 947, + RPL_NICKLOCKOFF = 945 +}; /** Handle /NICKLOCK */ @@ -35,29 +41,29 @@ class CommandNicklock : public Command { flags_needed = 'o'; syntax = "<oldnick> <newnick>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle(const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* target = ServerInstance->FindNick(parameters[0]); if ((!target) || (target->registered != REG_ALL)) { - user->WriteServ("NOTICE %s :*** No such nickname: '%s'", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** No such nickname: '" + parameters[0] + "'"); return CMD_FAILURE; } /* Do local sanity checks and bails */ if (IS_LOCAL(user)) { - if (!ServerInstance->IsNick(parameters[1].c_str(), ServerInstance->Config->Limits.NickMax)) + if (!ServerInstance->IsNick(parameters[1])) { - user->WriteServ("NOTICE %s :*** Invalid nickname '%s'", user->nick.c_str(), parameters[1].c_str()); + user->WriteNotice("*** Invalid nickname '" + parameters[1] + "'"); return CMD_FAILURE; } - user->WriteServ("947 %s %s :Nickname now locked.", user->nick.c_str(), parameters[1].c_str()); + user->WriteNumeric(RPL_NICKLOCKON, parameters[1], "Nickname now locked."); } /* If we made it this far, extend the user */ @@ -66,7 +72,7 @@ class CommandNicklock : public Command locked.set(target, 1); std::string oldnick = target->nick; - if (target->ForceNickChange(parameters[1].c_str())) + if (target->ChangeNick(parameters[1])) ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used NICKLOCK to change and hold "+oldnick+" to "+parameters[1]); else { @@ -78,12 +84,9 @@ class CommandNicklock : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -98,16 +101,16 @@ class CommandNickunlock : public Command { flags_needed = 'o'; syntax = "<locked-nick>"; - TRANSLATE2(TR_NICK, TR_END); + TRANSLATE1(TR_NICK); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* target = ServerInstance->FindNick(parameters[0]); if (!target) { - user->WriteServ("NOTICE %s :*** No such nickname: '%s'", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** No such nickname: '" + parameters[0] + "'"); return CMD_FAILURE; } @@ -116,13 +119,11 @@ class CommandNickunlock : public Command if (locked.set(target, 0)) { ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used NICKUNLOCK on "+target->nick); - user->SendText(":%s 945 %s %s :Nickname now unlocked.", - ServerInstance->Config->ServerName.c_str(),user->nick.c_str(),target->nick.c_str()); + user->WriteRemoteNumeric(RPL_NICKLOCKOFF, target->nick, "Nickname now unlocked."); } else { - user->SendText(":%s 946 %s %s :This user's nickname is not locked.", - ServerInstance->Config->ServerName.c_str(),user->nick.c_str(),target->nick.c_str()); + user->WriteRemoteNumeric(ERR_NICKNOTLOCKED, target->nick, "This user's nickname is not locked."); return CMD_FAILURE; } } @@ -130,16 +131,12 @@ class CommandNickunlock : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; - class ModuleNickLock : public Module { LocalIntExt locked; @@ -147,47 +144,31 @@ class ModuleNickLock : public Module CommandNickunlock cmd2; public: ModuleNickLock() - : locked("nick_locked", this), cmd1(this, locked), cmd2(this, locked) - { - } - - void init() + : locked("nick_locked", ExtensionItem::EXT_USER, this) + , cmd1(this, locked) + , cmd2(this, locked) { - ServerInstance->Modules->AddService(cmd1); - ServerInstance->Modules->AddService(cmd2); - ServerInstance->Modules->AddService(locked); - ServerInstance->Modules->Attach(I_OnUserPreNick, this); } - ~ModuleNickLock() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the NICKLOCK command, allows an oper to change a users nick and lock them to it until they quit", VF_OPTCOMMON | VF_VENDOR); } - ModResult OnUserPreNick(User* user, const std::string &newnick) + ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) CXX11_OVERRIDE { - if (!IS_LOCAL(user)) - return MOD_RES_PASSTHRU; - - if (ServerInstance->NICKForced.get(user)) /* Allow forced nick changes */ - return MOD_RES_PASSTHRU; - if (locked.get(user)) { - user->WriteNumeric(447, "%s :You cannot change your nickname (your nick is locked)",user->nick.c_str()); + user->WriteNumeric(ERR_CANTCHANGENICK, "You cannot change your nickname (your nick is locked)"); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { Module *nflood = ServerInstance->Modules->Find("m_nickflood.so"); - ServerInstance->Modules->SetPriority(this, I_OnUserPreNick, PRIORITY_BEFORE, &nflood); + ServerInstance->Modules->SetPriority(this, I_OnUserPreNick, PRIORITY_BEFORE, nflood); } }; diff --git a/src/modules/m_noctcp.cpp b/src/modules/m_noctcp.cpp index c934a05c6..af14c73ec 100644 --- a/src/modules/m_noctcp.cpp +++ b/src/modules/m_noctcp.cpp @@ -20,72 +20,75 @@ #include "inspircd.h" +#include "modules/exemption.h" -/* $ModDesc: Provides channel mode +C to block CTCPs */ - -class NoCTCP : public SimpleChannelModeHandler +class NoCTCPUser : public SimpleUserModeHandler { - public: - NoCTCP(Module* Creator) : SimpleChannelModeHandler(Creator, "noctcp", 'C') { } +public: + NoCTCPUser(Module* Creator) + : SimpleUserModeHandler(Creator, "u_noctcp", 'T') + { + if (!ServerInstance->Config->ConfValue("noctcp")->getBool("enableumode")) + DisableAutoRegister(); + } }; class ModuleNoCTCP : public Module { - - NoCTCP nc; + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler nc; + NoCTCPUser ncu; public: - ModuleNoCTCP() - : nc(this) + : exemptionprov(this) + , nc(this, "noctcp", 'C') + , ncu(this) { } - void init() + Version GetVersion() CXX11_OVERRIDE { - ServerInstance->Modules->AddService(nc); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + return Version("Provides user mode +T and channel mode +C to block CTCPs", VF_VENDOR); } - virtual ~ModuleNoCTCP() + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - } - - virtual Version GetVersion() - { - return Version("Provides channel mode +C to block CTCPs", VF_VENDOR); - } + if (!IS_LOCAL(user)) + return MOD_RES_PASSTHRU; - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return OnUserPreNotice(user,dest,target_type,text,status,exempt_list); - } + std::string ctcpname; + if (!details.IsCTCP(ctcpname) || irc::equals(ctcpname, "ACTION")) + return MOD_RES_PASSTHRU; - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) + if (target.type == MessageTarget::TYPE_CHANNEL) { - Channel* c = (Channel*)dest; - if ((text.empty()) || (text[0] != '\001') || (!strncmp(text.c_str(),"\1ACTION ", 8)) || (text == "\1ACTION\1") || (text == "\1ACTION")) - return MOD_RES_PASSTHRU; - - ModResult res = ServerInstance->OnCheckExemption(user,c,"noctcp"); + Channel* c = target.Get<Channel>(); + ModResult res = CheckExemption::Call(exemptionprov, user, c, "noctcp"); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; - if (!c->GetExtBanStatus(user, 'C').check(!c->IsModeSet('C'))) + if (!c->GetExtBanStatus(user, 'C').check(!c->IsModeSet(nc))) { - user->WriteNumeric(ERR_NOCTCPALLOWED, "%s %s :Can't send CTCP to channel (+C set)",user->nick.c_str(), c->name.c_str()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, c->name, "Can't send CTCP to channel (+C set)"); return MOD_RES_DENY; } } + else if (target.type == MessageTarget::TYPE_USER) + { + User* u = target.Get<User>(); + if (u->IsModeSet(ncu)) + { + user->WriteNumeric(ERR_CANTSENDTOUSER, u->nick, "Can't send CTCP to user (+T set)"); + return MOD_RES_PASSTHRU; + } + } return MOD_RES_PASSTHRU; } - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('C'); + tokens["EXTBAN"].push_back('C'); } }; diff --git a/src/modules/m_nokicks.cpp b/src/modules/m_nokicks.cpp index 1f58a2e08..3155dcb6c 100644 --- a/src/modules/m_nokicks.cpp +++ b/src/modules/m_nokicks.cpp @@ -22,56 +22,36 @@ #include "inspircd.h" -/* $ModDesc: Provides channel mode +Q to prevent kicks on the channel. */ - -class NoKicks : public SimpleChannelModeHandler -{ - public: - NoKicks(Module* Creator) : SimpleChannelModeHandler(Creator, "nokick", 'Q') { } -}; - class ModuleNoKicks : public Module { - NoKicks nk; + SimpleChannelModeHandler nk; public: ModuleNoKicks() - : nk(this) - { - } - - void init() + : nk(this, "nokick", 'Q') { - ServerInstance->Modules->AddService(nk); - Implementation eventlist[] = { I_OnUserPreKick, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('Q'); + tokens["EXTBAN"].push_back('Q'); } - ModResult OnUserPreKick(User* source, Membership* memb, const std::string &reason) + ModResult OnUserPreKick(User* source, Membership* memb, const std::string &reason) CXX11_OVERRIDE { - if (!memb->chan->GetExtBanStatus(source, 'Q').check(!memb->chan->IsModeSet('Q'))) + if (!memb->chan->GetExtBanStatus(source, 'Q').check(!memb->chan->IsModeSet(nk))) { // Can't kick with Q in place, not even opers with override, and founders - source->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :Can't kick user %s from channel (+Q set)",source->nick.c_str(), memb->chan->name.c_str(), memb->user->nick.c_str()); + source->WriteNumeric(ERR_CHANOPRIVSNEEDED, memb->chan->name, InspIRCd::Format("Can't kick user %s from channel (+Q set)", memb->user->nick.c_str())); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - ~ModuleNoKicks() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +Q to prevent kicks on the channel.", VF_VENDOR); } }; - MODULE_INIT(ModuleNoKicks) diff --git a/src/modules/m_nonicks.cpp b/src/modules/m_nonicks.cpp index 672a48f8d..998662c3c 100644 --- a/src/modules/m_nonicks.cpp +++ b/src/modules/m_nonicks.cpp @@ -20,83 +20,53 @@ #include "inspircd.h" - -/* $ModDesc: Provides support for channel mode +N & extban +b N: which prevents nick changes on channel */ - -class NoNicks : public SimpleChannelModeHandler -{ - public: - NoNicks(Module* Creator) : SimpleChannelModeHandler(Creator, "nonick", 'N') { } -}; +#include "modules/exemption.h" class ModuleNoNickChange : public Module { - NoNicks nn; - bool override; + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler nn; public: - ModuleNoNickChange() : nn(this) + ModuleNoNickChange() + : exemptionprov(this) + , nn(this, "nonick", 'N') { } - void init() - { - OnRehash(NULL); - ServerInstance->Modules->AddService(nn); - Implementation eventlist[] = { I_OnUserPreNick, I_On005Numeric, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleNoNickChange() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for channel mode +N & extban +b N: which prevents nick changes on channel", VF_VENDOR); } - - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('N'); + tokens["EXTBAN"].push_back('N'); } - virtual ModResult OnUserPreNick(User* user, const std::string &newnick) + ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) CXX11_OVERRIDE { - if (!IS_LOCAL(user)) - return MOD_RES_PASSTHRU; - - // Allow forced nick changes. - if (ServerInstance->NICKForced.get(user)) - return MOD_RES_PASSTHRU; - - for (UCListIter i = user->chans.begin(); i != user->chans.end(); i++) + for (User::ChanList::iterator i = user->chans.begin(); i != user->chans.end(); i++) { - Channel* curr = *i; + Channel* curr = (*i)->chan; - ModResult res = ServerInstance->OnCheckExemption(user,curr,"nonick"); + ModResult res = CheckExemption::Call(exemptionprov, user, curr, "nonick"); if (res == MOD_RES_ALLOW) continue; - if (override && IS_OPER(user)) + if (user->HasPrivPermission("channels/ignore-nonicks")) continue; - if (!curr->GetExtBanStatus(user, 'N').check(!curr->IsModeSet('N'))) + if (!curr->GetExtBanStatus(user, 'N').check(!curr->IsModeSet(nn))) { - user->WriteNumeric(ERR_CANTCHANGENICK, "%s :Can't change nickname while on %s (+N is set)", - user->nick.c_str(), curr->name.c_str()); + user->WriteNumeric(ERR_CANTCHANGENICK, InspIRCd::Format("Cannot change nickname while on %s (+N is set)", + curr->name.c_str())); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - - virtual void OnRehash(User* user) - { - override = ServerInstance->Config->ConfValue("nonicks")->getBool("operoverride", false); - } }; MODULE_INIT(ModuleNoNickChange) diff --git a/src/modules/m_nonotice.cpp b/src/modules/m_nonotice.cpp index c5b9f3a1c..77b0c9aa3 100644 --- a/src/modules/m_nonotice.cpp +++ b/src/modules/m_nonotice.cpp @@ -20,51 +20,39 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel mode +T to block notices to the channel */ - -class NoNotice : public SimpleChannelModeHandler -{ - public: - NoNotice(Module* Creator) : SimpleChannelModeHandler(Creator, "nonotice", 'T') { } -}; +#include "modules/exemption.h" class ModuleNoNotice : public Module { - NoNotice nt; + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler nt; public: ModuleNoNotice() - : nt(this) + : exemptionprov(this) + , nt(this, "nonotice", 'T') { } - void init() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(nt); - Implementation eventlist[] = { I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + tokens["EXTBAN"].push_back('T'); } - virtual void On005Numeric(std::string &output) - { - ServerInstance->AddExtBanChar('T'); - } - - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { ModResult res; - if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) + if ((details.type == MSG_NOTICE) && (target.type == MessageTarget::TYPE_CHANNEL) && (IS_LOCAL(user))) { - Channel* c = (Channel*)dest; - if (!c->GetExtBanStatus(user, 'T').check(!c->IsModeSet('T'))) + Channel* c = target.Get<Channel>(); + if (!c->GetExtBanStatus(user, 'T').check(!c->IsModeSet(nt))) { - res = ServerInstance->OnCheckExemption(user,c,"nonotice"); + res = CheckExemption::Call(exemptionprov, user, c, "nonotice"); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; else { - user->WriteNumeric(ERR_CANNOTSENDTOCHAN, "%s %s :Can't send NOTICE to channel (+T set)",user->nick.c_str(), c->name.c_str()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, c->name, "Can't send NOTICE to channel (+T set)"); return MOD_RES_DENY; } } @@ -72,11 +60,7 @@ class ModuleNoNotice : public Module return MOD_RES_PASSTHRU; } - virtual ~ModuleNoNotice() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +T to block notices to the channel", VF_VENDOR); } diff --git a/src/modules/m_nopartmsg.cpp b/src/modules/m_nopartmsg.cpp index ad3413101..7aeb66920 100644 --- a/src/modules/m_nopartmsg.cpp +++ b/src/modules/m_nopartmsg.cpp @@ -19,45 +19,27 @@ #include "inspircd.h" -/* $ModDesc: Implements extban +b p: - part message bans */ - class ModulePartMsgBan : public Module { - private: public: - void init() - { - Implementation eventlist[] = { I_OnUserPart, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModulePartMsgBan() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements extban +b p: - part message bans", VF_OPTCOMMON|VF_VENDOR); } - - virtual void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) + void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE { if (!IS_LOCAL(memb->user)) return; if (memb->chan->GetExtBanStatus(memb->user, 'p') == MOD_RES_DENY) partmessage.clear(); - - return; } - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('p'); + tokens["EXTBAN"].push_back('p'); } }; - MODULE_INIT(ModulePartMsgBan) - diff --git a/src/modules/m_ojoin.cpp b/src/modules/m_ojoin.cpp index 026d98f86..67b79e07c 100644 --- a/src/modules/m_ojoin.cpp +++ b/src/modules/m_ojoin.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2009 Taros <taros34@hotmail.com> * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> * * This file is part of InspIRCd. InspIRCd is free software: you can @@ -16,57 +17,39 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -/* - * Written for InspIRCd-1.2 by Taros on the Tel'Laerad M&D Team - * <http://tellaerad.net> - */ - #include "inspircd.h" -/* $ModConfig: <ojoin prefix="!" notice="yes" op="yes"> - * Specify the prefix that +Y will grant here, it should be unused. - * Leave prefix empty if you do not wish +Y to grant a prefix - * If notice is set to on, upon ojoin, the server will notice - * the channel saying that the oper is joining on network business - * If op is set to on, it will give them +o along with +Y */ -/* $ModDesc: Provides the /ojoin command, which joins a user to a channel on network business, and gives them +Y, which makes them immune to kick / deop and so on. */ -/* $ModAuthor: Taros */ -/* $ModAuthorMail: taros34@hotmail.com */ - -/* A note: This will not protect against kicks from services, - * ulines, or operoverride. */ - #define NETWORK_VALUE 9000000 -char NPrefix; -bool notice; -bool op; - /** Handle /OJOIN */ -class CommandOjoin : public Command +class CommandOjoin : public SplitCommand { public: bool active; - CommandOjoin(Module* parent) : Command(parent,"OJOIN", 1) - { - flags_needed = 'o'; Penalty = 0; syntax = "<channel>"; + bool notice; + bool op; + ModeHandler* npmh; + CommandOjoin(Module* parent, ModeHandler& mode) + : SplitCommand(parent, "OJOIN", 1) + , npmh(&mode) + { + flags_needed = 'o'; syntax = "<channel>"; active = false; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { // Make sure the channel name is allowable. - if (!ServerInstance->IsChannel(parameters[0].c_str(), ServerInstance->Config->Limits.ChanMax)) + if (!ServerInstance->IsChannel(parameters[0])) { - user->WriteServ("NOTICE "+user->nick+" :*** Invalid characters in channel name or name too long"); + user->WriteNotice("*** Invalid characters in channel name or name too long"); return CMD_FAILURE; } active = true; - Channel* channel = Channel::JoinUser(user, parameters[0].c_str(), false, "", false); + // override is false because we want OnUserPreJoin to run + Channel* channel = Channel::JoinUser(user, parameters[0], false); active = false; if (channel) @@ -75,22 +58,24 @@ class CommandOjoin : public Command if (notice) { - channel->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :%s joined on official network business.", - parameters[0].c_str(), user->nick.c_str()); - ServerInstance->PI->SendChannelNotice(channel, 0, user->nick + " joined on official network business."); + const std::string msg = user->nick + " joined on official network business."; + channel->WriteNotice(msg); + ServerInstance->PI->SendChannelNotice(channel, 0, msg); } } else { + channel = ServerInstance->FindChan(parameters[0]); + if (!channel) + return CMD_FAILURE; + ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used OJOIN in "+parameters[0]); // they're already in the channel - std::vector<std::string> modes; - modes.push_back(parameters[0]); - modes.push_back(op ? "+Yo" : "+Y"); - modes.push_back(user->nick); + Modes::ChangeList changelist; + changelist.push_add(npmh, user->nick); if (op) - modes.push_back(user->nick); - ServerInstance->SendGlobalMode(modes, ServerInstance->FakeClient); + changelist.push_add(ServerInstance->Modes->FindMode('o', MODETYPE_CHANNEL), user->nick); + ServerInstance->Modes->Process(ServerInstance->FakeClient, channel, NULL, changelist); } return CMD_SUCCESS; } @@ -98,57 +83,16 @@ class CommandOjoin : public Command /** channel mode +Y */ -class NetworkPrefix : public ModeHandler +class NetworkPrefix : public PrefixMode { public: - NetworkPrefix(Module* parent) : ModeHandler(parent, "official-join", 'Y', PARAM_ALWAYS, MODETYPE_CHANNEL) - { - list = true; - prefix = NPrefix; - levelrequired = INT_MAX; - m_paramtype = TR_NICK; - } - - void RemoveMode(Channel* channel, irc::modestacker* stack) - { - const UserMembList* cl = channel->GetUsers(); - std::vector<std::string> mode_junk; - mode_junk.push_back(channel->name); - irc::modestacker modestack(false); - std::deque<std::string> stackresult; - - for (UserMembCIter i = cl->begin(); i != cl->end(); i++) - { - if (i->second->hasMode('Y')) - { - if (stack) - stack->Push(this->GetModeChar(), i->first->nick); - else - modestack.Push(this->GetModeChar(), i->first->nick); - } - } - - if (stack) - return; - - while (modestack.GetStackedLine(stackresult)) - { - mode_junk.insert(mode_junk.end(), stackresult.begin(), stackresult.end()); - ServerInstance->SendMode(mode_junk, ServerInstance->FakeClient); - mode_junk.erase(mode_junk.begin() + 1, mode_junk.end()); - } - } - - unsigned int GetPrefixRank() + NetworkPrefix(Module* parent, char NPrefix) + : PrefixMode(parent, "official-join", 'Y', NETWORK_VALUE, NPrefix) { - return NETWORK_VALUE; + ranktoset = ranktounset = UINT_MAX; } - void RemoveMode(User* user, irc::modestacker* stack) - { - } - - ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) + ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) CXX11_OVERRIDE { User* theuser = ServerInstance->FindNick(parameter); // remove own privs? @@ -157,47 +101,27 @@ class NetworkPrefix : public ModeHandler return MOD_RES_PASSTHRU; } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - return MODEACTION_ALLOW; - } - }; class ModuleOjoin : public Module { - NetworkPrefix* np; + NetworkPrefix np; CommandOjoin mycommand; public: ModuleOjoin() - : np(NULL), mycommand(this) + : np(this, ServerInstance->Config->ConfValue("ojoin")->getString("prefix").c_str()[0]) + , mycommand(this, np) { } - void init() - { - /* Load config stuff */ - OnRehash(NULL); - - /* Initialise module variables */ - np = new NetworkPrefix(this); - - ServerInstance->Modules->AddService(*np); - ServerInstance->Modules->AddService(mycommand); - - Implementation eventlist[] = { I_OnUserPreJoin, I_OnUserPreKick, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ModResult OnUserPreJoin(User *user, Channel *chan, const char *cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { if (mycommand.active) { - privs += 'Y'; - if (op) + privs += np.GetModeChar(); + if (mycommand.op) privs += 'o'; return MOD_RES_ALLOW; } @@ -205,53 +129,36 @@ class ModuleOjoin : public Module return MOD_RES_PASSTHRU; } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* Conf = ServerInstance->Config->ConfValue("ojoin"); - - if (!np) - { - // This is done on module load only - std::string npre = Conf->getString("prefix"); - NPrefix = npre.empty() ? 0 : npre[0]; - - if (NPrefix && ServerInstance->Modes->FindPrefix(NPrefix)) - throw ModuleException("Looks like the +Y prefix you picked for m_ojoin is already in use. Pick another."); - } - - notice = Conf->getBool("notice", true); - op = Conf->getBool("op", true); + mycommand.notice = Conf->getBool("notice", true); + mycommand.op = Conf->getBool("op", true); } - ModResult OnUserPreKick(User* source, Membership* memb, const std::string &reason) + ModResult OnUserPreKick(User* source, Membership* memb, const std::string &reason) CXX11_OVERRIDE { // Don't do anything if they're not +Y - if (!memb->hasMode('Y')) + if (!memb->HasMode(&np)) return MOD_RES_PASSTHRU; // Let them do whatever they want to themselves. if (source == memb->user) return MOD_RES_PASSTHRU; - source->WriteNumeric(484, source->nick+" "+memb->chan->name+" :Can't kick "+memb->user->nick+" as they're on official network business."); + source->WriteNumeric(ERR_RESTRICTED, memb->chan->name, "Can't kick "+memb->user->nick+" as they're on official network business."); return MOD_RES_DENY; } - ~ModuleOjoin() - { - delete np; - } - - void Prioritize() + void Prioritize() CXX11_OVERRIDE { ServerInstance->Modules->SetPriority(this, I_OnUserPreJoin, PRIORITY_FIRST); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Network Business Join", VF_VENDOR); } }; MODULE_INIT(ModuleOjoin) - diff --git a/src/modules/m_operchans.cpp b/src/modules/m_operchans.cpp index ca948d95b..f0812ce27 100644 --- a/src/modules/m_operchans.cpp +++ b/src/modules/m_operchans.cpp @@ -22,7 +22,11 @@ #include "inspircd.h" -/* $ModDesc: Provides support for oper-only chans via the +O channel mode */ +enum +{ + // From UnrealIRCd. + ERR_CANTJOINOPERSONLY = 520 +}; class OperChans : public SimpleChannelModeHandler { @@ -42,44 +46,32 @@ class ModuleOperChans : public Module { } - void init() - { - ServerInstance->Modules->AddService(oc); - Implementation eventlist[] = { I_OnCheckBan, I_On005Numeric, I_OnUserPreJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - if (chan && chan->IsModeSet('O') && !IS_OPER(user)) + if (chan && chan->IsModeSet(oc) && !user->IsOper()) { - user->WriteNumeric(ERR_CANTJOINOPERSONLY, "%s %s :Only IRC operators may join %s (+O is set)", - user->nick.c_str(), chan->name.c_str(), chan->name.c_str()); + user->WriteNumeric(ERR_CANTJOINOPERSONLY, chan->name, InspIRCd::Format("Only IRC operators may join %s (+O is set)", chan->name.c_str())); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) + ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) CXX11_OVERRIDE { if ((mask.length() > 2) && (mask[0] == 'O') && (mask[1] == ':')) { - if (IS_OPER(user) && InspIRCd::Match(user->oper->name, mask.substr(2))) + if (user->IsOper() && InspIRCd::Match(user->oper->name, mask.substr(2))) return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - void On005Numeric(std::string &output) - { - ServerInstance->AddExtBanChar('O'); - } - - ~ModuleOperChans() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { + tokens["EXTBAN"].push_back('O'); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for oper-only chans via the +O channel mode and 'O' extban", VF_VENDOR); } diff --git a/src/modules/m_operjoin.cpp b/src/modules/m_operjoin.cpp index bd77384a6..dd80d99ba 100644 --- a/src/modules/m_operjoin.cpp +++ b/src/modules/m_operjoin.cpp @@ -24,84 +24,44 @@ #include "inspircd.h" -/* $ModDesc: Forces opers to join the specified channel(s) on oper-up */ - class ModuleOperjoin : public Module { - private: - std::string operChan; std::vector<std::string> operChans; bool override; - int tokenize(const std::string &str, std::vector<std::string> &tokens) - { - // skip delimiters at beginning. - std::string::size_type lastPos = str.find_first_not_of(",", 0); - // find first "non-delimiter". - std::string::size_type pos = str.find_first_of(",", lastPos); - - while (std::string::npos != pos || std::string::npos != lastPos) - { - // found a token, add it to the vector. - tokens.push_back(str.substr(lastPos, pos - lastPos)); - // skip delimiters. Note the "not_of" - lastPos = str.find_first_not_of(",", pos); - // find next "non-delimiter" - pos = str.find_first_of(",", lastPos); - } - return tokens.size(); - } - public: - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnPostOper, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("operjoin"); - operChan = tag->getString("channel"); override = tag->getBool("override", false); + irc::commasepstream ss(tag->getString("channel")); operChans.clear(); - if (!operChan.empty()) - tokenize(operChan,operChans); - } - virtual ~ModuleOperjoin() - { + for (std::string channame; ss.GetToken(channame); ) + operChans.push_back(channame); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Forces opers to join the specified channel(s) on oper-up", VF_VENDOR); } - virtual void OnPostOper(User* user, const std::string &opertype, const std::string &opername) + void OnPostOper(User* user, const std::string &opertype, const std::string &opername) CXX11_OVERRIDE { - if (!IS_LOCAL(user)) + LocalUser* localuser = IS_LOCAL(user); + if (!localuser) return; - for(std::vector<std::string>::iterator it = operChans.begin(); it != operChans.end(); it++) - if (ServerInstance->IsChannel(it->c_str(), ServerInstance->Config->Limits.ChanMax)) - Channel::JoinUser(user, it->c_str(), override, "", false, ServerInstance->Time()); + for (std::vector<std::string>::const_iterator i = operChans.begin(); i != operChans.end(); ++i) + if (ServerInstance->IsChannel(*i)) + Channel::JoinUser(localuser, *i, override); - std::string chanList = IS_OPER(user)->getConfig("autojoin"); - if (!chanList.empty()) + irc::commasepstream ss(localuser->oper->getConfig("autojoin")); + for (std::string channame; ss.GetToken(channame); ) { - std::vector<std::string> typechans; - tokenize(chanList, typechans); - for (std::vector<std::string>::const_iterator it = typechans.begin(); it != typechans.end(); ++it) - { - if (ServerInstance->IsChannel(it->c_str(), ServerInstance->Config->Limits.ChanMax)) - { - Channel::JoinUser(user, it->c_str(), override, "", false, ServerInstance->Time()); - } - } + if (ServerInstance->IsChannel(channame)) + Channel::JoinUser(localuser, channame, override); } } }; diff --git a/src/modules/m_operlevels.cpp b/src/modules/m_operlevels.cpp index 569defd49..2e02ffddf 100644 --- a/src/modules/m_operlevels.cpp +++ b/src/modules/m_operlevels.cpp @@ -20,38 +20,33 @@ */ -/* $ModDesc: Gives each oper type a 'level', cannot kill opers 'above' your level. */ - #include "inspircd.h" class ModuleOperLevels : public Module { public: - void init() - { - ServerInstance->Modules->Attach(I_OnKill, this); - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Gives each oper type a 'level', cannot kill opers 'above' your level.", VF_VENDOR); } - virtual ModResult OnKill(User* source, User* dest, const std::string &reason) + ModResult OnKill(User* source, User* dest, const std::string &reason) CXX11_OVERRIDE { // oper killing an oper? - if (IS_OPER(dest) && IS_OPER(source)) + if (dest->IsOper() && source->IsOper()) { - std::string level = dest->oper->getConfig("level"); - long dest_level = atol(level.c_str()); - level = source->oper->getConfig("level"); - long source_level = atol(level.c_str()); + unsigned long dest_level = ConvToNum<unsigned long>(dest->oper->getConfig("level")); + unsigned long source_level = ConvToNum<unsigned long>(source->oper->getConfig("level")); if (dest_level > source_level) { - if (IS_LOCAL(source)) ServerInstance->SNO->WriteGlobalSno('a', "Oper %s (level %ld) attempted to /kill a higher oper: %s (level %ld): Reason: %s",source->nick.c_str(),source_level,dest->nick.c_str(),dest_level,reason.c_str()); - dest->WriteServ("NOTICE %s :*** Oper %s attempted to /kill you!",dest->nick.c_str(),source->nick.c_str()); - source->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper %s is a higher level than you",source->nick.c_str(),dest->nick.c_str()); + if (IS_LOCAL(source)) + { + ServerInstance->SNO->WriteGlobalSno('a', "Oper %s (level %lu) attempted to /kill a higher oper: %s (level %lu): Reason: %s", + source->nick.c_str(), source_level, dest->nick.c_str(), dest_level, reason.c_str()); + } + dest->WriteNotice("*** Oper " + source->nick + " attempted to /kill you!"); + source->WriteNumeric(ERR_NOPRIVILEGES, InspIRCd::Format("Permission Denied - Oper %s is a higher level than you", dest->nick.c_str())); return MOD_RES_DENY; } } @@ -60,4 +55,3 @@ class ModuleOperLevels : public Module }; MODULE_INIT(ModuleOperLevels) - diff --git a/src/modules/m_operlog.cpp b/src/modules/m_operlog.cpp index edb9109e8..b64e18870 100644 --- a/src/modules/m_operlog.cpp +++ b/src/modules/m_operlog.cpp @@ -21,51 +21,39 @@ #include "inspircd.h" -/* $ModDesc: A module which logs all oper commands to the ircd log at default loglevel. */ - class ModuleOperLog : public Module { bool tosnomask; public: - void init() + void init() CXX11_OVERRIDE { - Implementation eventlist[] = { I_OnPreCommand, I_On005Numeric, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); ServerInstance->SNO->EnableSnomask('r', "OPERLOG"); - OnRehash(NULL); } - virtual ~ModuleOperLog() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("A module which logs all oper commands to the ircd log at default loglevel.", VF_VENDOR); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { tosnomask = ServerInstance->Config->ConfValue("operlog")->getBool("tosnomask", false); } - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { /* If the command doesnt appear to be valid, we dont want to mess with it. */ if (!validated) return MOD_RES_PASSTHRU; - if ((IS_OPER(user)) && (IS_LOCAL(user)) && (user->HasPermission(command))) + if ((user->IsOper()) && (user->HasPermission(command))) { - Command* thiscommand = ServerInstance->Parser->GetHandler(command); + Command* thiscommand = ServerInstance->Parser.GetHandler(command); if ((thiscommand) && (thiscommand->flags_needed == 'o')) { - std::string line; - if (!parameters.empty()) - line = irc::stringjoiner(" ", parameters, 0, parameters.size() - 1).GetJoined(); - std::string msg = "[" + user->GetFullRealHost() + "] " + command + " " + line; - ServerInstance->Logs->Log("m_operlog", DEFAULT, "OPERLOG: " + msg); + std::string msg = "[" + user->GetFullRealHost() + "] " + command + " " + stdalgo::string::join(parameters); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "OPERLOG: " + msg); if (tosnomask) ServerInstance->SNO->WriteGlobalSno('r', msg); } @@ -74,12 +62,11 @@ class ModuleOperLog : public Module return MOD_RES_PASSTHRU; } - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" OPERLOG"); + tokens["OPERLOG"]; } }; - MODULE_INIT(ModuleOperLog) diff --git a/src/modules/m_opermodes.cpp b/src/modules/m_opermodes.cpp index 8b49f685e..475d561ba 100644 --- a/src/modules/m_opermodes.cpp +++ b/src/modules/m_opermodes.cpp @@ -22,26 +22,15 @@ #include "inspircd.h" -/* $ModDesc: Sets (and unsets) modes on opers when they oper up */ - class ModuleModesOnOper : public Module { public: - void init() - { - ServerInstance->Modules->Attach(I_OnPostOper, this); - } - - virtual ~ModuleModesOnOper() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Sets (and unsets) modes on opers when they oper up", VF_VENDOR); } - virtual void OnPostOper(User* user, const std::string &opertype, const std::string &opername) + void OnPostOper(User* user, const std::string &opertype, const std::string &opername) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return; @@ -63,15 +52,15 @@ class ModuleModesOnOper : public Module smodes = "+" + smodes; std::string buf; - std::stringstream ss(smodes); - std::vector<std::string> modes; + irc::spacesepstream ss(smodes); + CommandBase::Params modes; modes.push_back(u->nick); // split into modes and mode params - while (ss >> buf) + while (ss.GetToken(buf)) modes.push_back(buf); - ServerInstance->SendMode(modes, u); + ServerInstance->Parser.CallHandler("MODE", modes, u); } }; diff --git a/src/modules/m_opermotd.cpp b/src/modules/m_opermotd.cpp index 989f97689..49409a1c0 100644 --- a/src/modules/m_opermotd.cpp +++ b/src/modules/m_opermotd.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -/* $ModDesc: Shows a message to opers after oper-up, adds /opermotd */ - /** Handle /OPERMOTD */ class CommandOpermotd : public Command @@ -36,37 +34,36 @@ class CommandOpermotd : public Command flags_needed = 'o'; syntax = "[<servername>]"; } - CmdResult Handle (const std::vector<std::string>& parameters, User* user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { if ((parameters.empty()) || (parameters[0] == ServerInstance->Config->ServerName)) ShowOperMOTD(user); return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - if (!parameters.empty()) + if ((!parameters.empty()) && (parameters[0].find('.') != std::string::npos)) return ROUTE_OPT_UCAST(parameters[0]); return ROUTE_LOCALONLY; } void ShowOperMOTD(User* user) { - const std::string& servername = ServerInstance->Config->ServerName; if (opermotd.empty()) { - user->SendText(":%s 455 %s :OPERMOTD file is missing", servername.c_str(), user->nick.c_str()); + user->WriteRemoteNumeric(455, "OPERMOTD file is missing"); return; } - user->SendText(":%s 375 %s :- IRC Operators Message of the Day", servername.c_str(), user->nick.c_str()); + user->WriteRemoteNumeric(RPL_MOTDSTART, "- IRC Operators Message of the Day"); for (file_cache::const_iterator i = opermotd.begin(); i != opermotd.end(); ++i) { - user->SendText(":%s 372 %s :- %s", servername.c_str(), user->nick.c_str(), i->c_str()); + user->WriteRemoteNumeric(RPL_MOTD, InspIRCd::Format("- %s", i->c_str())); } - user->SendText(":%s 376 %s :- End of OPERMOTD", servername.c_str(), user->nick.c_str()); + user->WriteRemoteNumeric(RPL_ENDOFMOTD, "- End of OPERMOTD"); } }; @@ -82,37 +79,33 @@ class ModuleOpermotd : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - OnRehash(NULL); - Implementation eventlist[] = { I_OnRehash, I_OnOper }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Shows a message to opers after oper-up, adds /opermotd", VF_VENDOR | VF_OPTCOMMON); } - virtual void OnOper(User* user, const std::string &opertype) + void OnOper(User* user, const std::string &opertype) CXX11_OVERRIDE { if (onoper && IS_LOCAL(user)) cmd.ShowOperMOTD(user); } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { cmd.opermotd.clear(); ConfigTag* conf = ServerInstance->Config->ConfValue("opermotd"); onoper = conf->getBool("onoper", true); - FileReader f(conf->getString("file", "opermotd")); - for (int i=0, filesize = f.FileSize(); i < filesize; i++) - cmd.opermotd.push_back(f.GetLine(i)); - - if (conf->getBool("processcolors")) + try + { + FileReader reader(conf->getString("file", "opermotd")); + cmd.opermotd = reader.GetVector(); InspIRCd::ProcessColors(cmd.opermotd); + } + catch (CoreException&) + { + // Nothing happens here as we do the error handling in ShowOperMOTD. + } } }; diff --git a/src/modules/m_operprefix.cpp b/src/modules/m_operprefix.cpp index 7d5e6d118..a8afd7c96 100644 --- a/src/modules/m_operprefix.cpp +++ b/src/modules/m_operprefix.cpp @@ -22,74 +22,18 @@ * Originally by Chernov-Phoenix Alexey (Phoenix@RusNet) mailto:phoenix /email address separator/ pravmail.ru */ -/* $ModDesc: Gives opers cmode +y which provides a staff prefix. */ - #include "inspircd.h" #define OPERPREFIX_VALUE 1000000 -class OperPrefixMode : public ModeHandler +class OperPrefixMode : public PrefixMode { public: - OperPrefixMode(Module* Creator) : ModeHandler(Creator, "operprefix", 'y', PARAM_ALWAYS, MODETYPE_CHANNEL) - { - std::string pfx = ServerInstance->Config->ConfValue("operprefix")->getString("prefix", "!"); - list = true; - prefix = pfx.empty() ? '!' : pfx[0]; - levelrequired = OPERPREFIX_VALUE; - m_paramtype = TR_NICK; - } - - unsigned int GetPrefixRank() - { - return OPERPREFIX_VALUE; - } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - if (IS_SERVER(source) || ServerInstance->ULine(source->server)) - return MODEACTION_ALLOW; - else - { - if (channel) - source->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :Only servers are permitted to change channel mode '%c'", source->nick.c_str(), channel->name.c_str(), 'y'); - return MODEACTION_DENY; - } - } - - bool NeedsOper() { return true; } - - void RemoveMode(Channel* chan, irc::modestacker* stack) - { - irc::modestacker modestack(false); - const UserMembList* users = chan->GetUsers(); - for (UserMembCIter i = users->begin(); i != users->end(); ++i) - { - if (i->second->hasMode(mode)) - { - if (stack) - stack->Push(this->GetModeChar(), i->first->nick); - else - modestack.Push(this->GetModeChar(), i->first->nick); - } - } - - if (stack) - return; - - std::deque<std::string> stackresult; - std::vector<std::string> mode_junk; - mode_junk.push_back(chan->name); - while (modestack.GetStackedLine(stackresult)) - { - mode_junk.insert(mode_junk.end(), stackresult.begin(), stackresult.end()); - ServerInstance->SendMode(mode_junk, ServerInstance->FakeClient); - mode_junk.erase(mode_junk.begin() + 1, mode_junk.end()); - } - } - - void RemoveMode(User* user, irc::modestacker* stack) + OperPrefixMode(Module* Creator) + : PrefixMode(Creator, "operprefix", 'y', OPERPREFIX_VALUE) { + prefix = ServerInstance->Config->ConfValue("operprefix")->getString("prefix", "!", 1, 1)[0]; + ranktoset = ranktounset = UINT_MAX; } }; @@ -97,111 +41,70 @@ class ModuleOperPrefixMode; class HideOperWatcher : public ModeWatcher { ModuleOperPrefixMode* parentmod; + public: - HideOperWatcher(ModuleOperPrefixMode* parent) : ModeWatcher((Module*) parent, 'H', MODETYPE_USER), parentmod(parent) {} - void AfterMode(User* source, User* dest, Channel* channel, const std::string ¶meter, bool adding, ModeType type); + HideOperWatcher(ModuleOperPrefixMode* parent); + void AfterMode(User* source, User* dest, Channel* channel, const std::string ¶meter, bool adding) CXX11_OVERRIDE; }; class ModuleOperPrefixMode : public Module { - private: OperPrefixMode opm; - bool mw_added; HideOperWatcher hideoperwatcher; + UserModeReference hideopermode; + public: ModuleOperPrefixMode() - : opm(this), mw_added(false), hideoperwatcher(this) - { - } - - void init() + : opm(this), hideoperwatcher(this) + , hideopermode(this, "hideoper") { - ServerInstance->Modules->AddService(opm); - - Implementation eventlist[] = { I_OnUserPreJoin, I_OnPostOper, I_OnLoadModule, I_OnUnloadModule, I_OnPostJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - /* To give clients a chance to learn about the new prefix we don't give +y to opers * right now. That means if the module was loaded after opers have joined channels * they need to rejoin them in order to get the oper prefix. */ - - if (ServerInstance->Modules->Find("m_hideoper.so")) - mw_added = ServerInstance->Modes->AddModeWatcher(&hideoperwatcher); } - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string& privs, const std::string& keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - /* The user may have the +H umode on himself, but +H does not necessarily correspond - * to the +H of m_hideoper. - * However we only add the modewatcher when m_hideoper is loaded, so these - * conditions (mw_added and the user being +H) together mean the user is a hidden oper. - */ - - if (IS_OPER(user) && (!mw_added || !user->IsModeSet('H'))) + if ((user->IsOper()) && (!user->IsModeSet(hideopermode))) privs.push_back('y'); return MOD_RES_PASSTHRU; } - void OnPostJoin(Membership* memb) + void OnPostJoin(Membership* memb) CXX11_OVERRIDE { - if ((!IS_LOCAL(memb->user)) || (!IS_OPER(memb->user)) || (((mw_added) && (memb->user->IsModeSet('H'))))) + if ((!IS_LOCAL(memb->user)) || (!memb->user->IsOper()) || (memb->user->IsModeSet(hideopermode))) return; - if (memb->hasMode(opm.GetModeChar())) + if (memb->HasMode(&opm)) return; // The user was force joined and OnUserPreJoin() did not run. Set the operprefix now. - std::vector<std::string> modechange; - modechange.push_back(memb->chan->name); - modechange.push_back("+y"); - modechange.push_back(memb->user->nick); - ServerInstance->SendGlobalMode(modechange, ServerInstance->FakeClient); + Modes::ChangeList changelist; + changelist.push_add(&opm, memb->user->nick); + ServerInstance->Modes.Process(ServerInstance->FakeClient, memb->chan, NULL, changelist); } void SetOperPrefix(User* user, bool add) { - std::vector<std::string> modechange; - modechange.push_back(""); - modechange.push_back(add ? "+y" : "-y"); - modechange.push_back(user->nick); - for (UCListIter v = user->chans.begin(); v != user->chans.end(); v++) - { - modechange[0] = (*v)->name; - ServerInstance->SendGlobalMode(modechange, ServerInstance->FakeClient); - } + Modes::ChangeList changelist; + changelist.push(&opm, add, user->nick); + for (User::ChanList::iterator v = user->chans.begin(); v != user->chans.end(); v++) + ServerInstance->Modes->Process(ServerInstance->FakeClient, (*v)->chan, NULL, changelist); } - void OnPostOper(User* user, const std::string& opername, const std::string& opertype) + void OnPostOper(User* user, const std::string& opername, const std::string& opertype) CXX11_OVERRIDE { - if (IS_LOCAL(user) && (!mw_added || !user->IsModeSet('H'))) + if (IS_LOCAL(user) && (!user->IsModeSet(hideopermode))) SetOperPrefix(user, true); } - void OnLoadModule(Module* mod) - { - if ((!mw_added) && (mod->ModuleSourceFile == "m_hideoper.so")) - mw_added = ServerInstance->Modes->AddModeWatcher(&hideoperwatcher); - } - - void OnUnloadModule(Module* mod) - { - if ((mw_added) && (mod->ModuleSourceFile == "m_hideoper.so") && (ServerInstance->Modes->DelModeWatcher(&hideoperwatcher))) - mw_added = false; - } - - ~ModuleOperPrefixMode() - { - if (mw_added) - ServerInstance->Modes->DelModeWatcher(&hideoperwatcher); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Gives opers cmode +y which provides a staff prefix.", VF_VENDOR); } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { // m_opermodes may set +H on the oper to hide him, we don't want to set the oper prefix in that case Module* opermodes = ServerInstance->Modules->Find("m_opermodes.so"); @@ -209,10 +112,16 @@ class ModuleOperPrefixMode : public Module } }; -void HideOperWatcher::AfterMode(User* source, User* dest, Channel* channel, const std::string& parameter, bool adding, ModeType type) +HideOperWatcher::HideOperWatcher(ModuleOperPrefixMode* parent) + : ModeWatcher(parent, "hideoper", MODETYPE_USER) + , parentmod(parent) +{ +} + +void HideOperWatcher::AfterMode(User* source, User* dest, Channel* channel, const std::string& parameter, bool adding) { // If hideoper is being unset because the user is deopering, don't set +y - if (IS_LOCAL(dest) && IS_OPER(dest)) + if (IS_LOCAL(dest) && dest->IsOper()) parentmod->SetOperPrefix(dest, !adding); } diff --git a/src/modules/m_override.cpp b/src/modules/m_override.cpp index 3266d3eb0..7155e7e76 100644 --- a/src/modules/m_override.cpp +++ b/src/modules/m_override.cpp @@ -25,66 +25,105 @@ #include "inspircd.h" +#include "modules/invite.h" -/* $ModDesc: Provides support for allowing opers to override certain things. */ +class Override : public SimpleUserModeHandler +{ + public: + Override(Module* Creator) : SimpleUserModeHandler(Creator, "override", 'O') + { + oper = true; + if (!ServerInstance->Config->ConfValue("override")->getBool("enableumode")) + DisableAutoRegister(); + } +}; class ModuleOverride : public Module { bool RequireKey; bool NoisyOverride; - - static bool IsOverride(unsigned int userlevel, const std::string& modeline) + bool UmodeEnabled; + Override ou; + ChanModeReference topiclock; + ChanModeReference inviteonly; + ChanModeReference key; + ChanModeReference limit; + Invite::API invapi; + + static bool IsOverride(unsigned int userlevel, const Modes::ChangeList::List& list) { - for (std::string::const_iterator i = modeline.begin(); i != modeline.end(); ++i) + for (Modes::ChangeList::List::const_iterator i = list.begin(); i != list.end(); ++i) { - ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL); - if (!mh) - continue; - - if (mh->GetLevelRequired() > userlevel) + ModeHandler* mh = i->mh; + if (mh->GetLevelRequired(i->adding) > userlevel) return true; } return false; } + ModResult HandleJoinOverride(LocalUser* user, Channel* chan, const std::string& keygiven, const char* bypasswhat, const char* mode) + { + if (RequireKey && keygiven != "override") + { + // Can't join normally -- must use a special key to bypass restrictions + user->WriteNotice("*** You may not join normally. You must join with a key of 'override' to oper override."); + return MOD_RES_PASSTHRU; + } + + if (NoisyOverride) + chan->WriteNotice(InspIRCd::Format("%s used oper override to bypass %s", user->nick.c_str(), bypasswhat)); + ServerInstance->SNO->WriteGlobalSno('v', user->nick+" used oper override to bypass " + mode + " on " + chan->name); + return MOD_RES_ALLOW; + } + public: + ModuleOverride() + : UmodeEnabled(false) + , ou(this) + , topiclock(this, "topiclock") + , inviteonly(this, "inviteonly") + , key(this, "key") + , limit(this, "limit") + , invapi(this) + { + } - void init() + void init() CXX11_OVERRIDE { - // read our config options (main config file) - OnRehash(NULL); ServerInstance->SNO->EnableSnomask('v', "OVERRIDE"); - Implementation eventlist[] = { I_OnRehash, I_OnPreMode, I_On005Numeric, I_OnUserPreJoin, I_OnUserPreKick, I_OnPreTopicChange }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + UmodeEnabled = ServerInstance->Config->ConfValue("override")->getBool("enableumode"); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - // re-read our config options on a rehash + // re-read our config options ConfigTag* tag = ServerInstance->Config->ConfValue("override"); NoisyOverride = tag->getBool("noisy"); RequireKey = tag->getBool("requirekey"); } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" OVERRIDE"); + tokens["OVERRIDE"]; } bool CanOverride(User* source, const char* token) { - std::string tokenlist = source->oper->getConfig("override"); + // If we require oper override umode (+O) but it is not set + if (UmodeEnabled && !source->IsModeSet(ou)) + return false; + std::string tokenlist = source->oper->getConfig("override"); // its defined or * is set, return its value as a boolean for if the token is set return ((tokenlist.find(token, 0) != std::string::npos) || (tokenlist.find("*", 0) != std::string::npos)); } - ModResult OnPreTopicChange(User *source, Channel *channel, const std::string &topic) + ModResult OnPreTopicChange(User *source, Channel *channel, const std::string &topic) CXX11_OVERRIDE { - if (IS_LOCAL(source) && IS_OPER(source) && CanOverride(source, "TOPIC")) + if (IS_LOCAL(source) && source->IsOper() && CanOverride(source, "TOPIC")) { - if (!channel->HasUser(source) || (channel->IsModeSet('t') && channel->GetPrefixValue(source) < HALFOP_VALUE)) + if (!channel->HasUser(source) || (channel->IsModeSet(topiclock) && channel->GetPrefixValue(source) < HALFOP_VALUE)) { ServerInstance->SNO->WriteGlobalSno('v',source->nick+" used oper override to change a topic on "+channel->name); } @@ -96,9 +135,9 @@ class ModuleOverride : public Module return MOD_RES_PASSTHRU; } - ModResult OnUserPreKick(User* source, Membership* memb, const std::string &reason) + ModResult OnUserPreKick(User* source, Membership* memb, const std::string &reason) CXX11_OVERRIDE { - if (IS_OPER(source) && CanOverride(source,"KICK")) + if (source->IsOper() && CanOverride(source,"KICK")) { // If the kicker's status is less than the target's, or the kicker's status is less than or equal to voice if ((memb->chan->GetPrefixValue(source) < memb->getRank()) || (memb->chan->GetPrefixValue(source) <= VOICE_VALUE) || @@ -111,104 +150,75 @@ class ModuleOverride : public Module return MOD_RES_PASSTHRU; } - ModResult OnPreMode(User* source,User* dest,Channel* channel, const std::vector<std::string>& parameters) + ModResult OnPreMode(User* source, User* dest, Channel* channel, Modes::ChangeList& modes) CXX11_OVERRIDE { - if (!source || !channel) + if (!channel) return MOD_RES_PASSTHRU; - if (!IS_OPER(source) || !IS_LOCAL(source)) + if (!source->IsOper() || !IS_LOCAL(source)) return MOD_RES_PASSTHRU; + const Modes::ChangeList::List& list = modes.getlist(); unsigned int mode = channel->GetPrefixValue(source); - if (!IsOverride(mode, parameters[1])) + if (!IsOverride(mode, list)) return MOD_RES_PASSTHRU; if (CanOverride(source, "MODE")) { - std::string msg = source->nick+" overriding modes:"; - for(unsigned int i=0; i < parameters.size(); i++) - msg += " " + parameters[i]; + std::string msg = source->nick + " overriding modes: "; + + // Construct a MODE string in the old format for sending it as a snotice + std::string params; + char pm = 0; + for (Modes::ChangeList::List::const_iterator i = list.begin(); i != list.end(); ++i) + { + const Modes::Change& item = *i; + if (!item.param.empty()) + params.append(1, ' ').append(item.param); + + char wanted_pm = (item.adding ? '+' : '-'); + if (wanted_pm != pm) + { + pm = wanted_pm; + msg += pm; + } + + msg += item.mh->GetModeChar(); + } + msg += params; ServerInstance->SNO->WriteGlobalSno('v',msg); return MOD_RES_ALLOW; } return MOD_RES_PASSTHRU; } - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - if (IS_LOCAL(user) && IS_OPER(user)) + if (user->IsOper()) { if (chan) { - if (chan->IsModeSet('i') && (CanOverride(user,"INVITE"))) + if (chan->IsModeSet(inviteonly) && (CanOverride(user,"INVITE"))) { - irc::string x(chan->name.c_str()); - if (!IS_LOCAL(user)->IsInvited(x)) - { - if (RequireKey && keygiven != "override") - { - // Can't join normally -- must use a special key to bypass restrictions - user->WriteServ("NOTICE %s :*** You may not join normally. You must join with a key of 'override' to oper override.", user->nick.c_str()); - return MOD_RES_PASSTHRU; - } - - if (NoisyOverride) - chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :%s used oper override to bypass invite-only", cname, user->nick.c_str()); - ServerInstance->SNO->WriteGlobalSno('v', user->nick+" used oper override to bypass +i on "+std::string(cname)); - } + if (!invapi->IsInvited(user, chan)) + return HandleJoinOverride(user, chan, keygiven, "invite-only", "+i"); return MOD_RES_ALLOW; } - if (chan->IsModeSet('k') && (CanOverride(user,"KEY")) && keygiven != chan->GetModeParameter('k')) - { - if (RequireKey && keygiven != "override") - { - // Can't join normally -- must use a special key to bypass restrictions - user->WriteServ("NOTICE %s :*** You may not join normally. You must join with a key of 'override' to oper override.", user->nick.c_str()); - return MOD_RES_PASSTHRU; - } - - if (NoisyOverride) - chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :%s used oper override to bypass the channel key", cname, user->nick.c_str()); - ServerInstance->SNO->WriteGlobalSno('v', user->nick+" used oper override to bypass +k on "+std::string(cname)); - return MOD_RES_ALLOW; - } + if (chan->IsModeSet(key) && (CanOverride(user,"KEY")) && keygiven != chan->GetModeParameter(key)) + return HandleJoinOverride(user, chan, keygiven, "the channel key", "+k"); - if (chan->IsModeSet('l') && (chan->GetUserCounter() >= ConvToInt(chan->GetModeParameter('l'))) && (CanOverride(user,"LIMIT"))) - { - if (RequireKey && keygiven != "override") - { - // Can't join normally -- must use a special key to bypass restrictions - user->WriteServ("NOTICE %s :*** You may not join normally. You must join with a key of 'override' to oper override.", user->nick.c_str()); - return MOD_RES_PASSTHRU; - } - - if (NoisyOverride) - chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :%s used oper override to bypass the channel limit", cname, user->nick.c_str()); - ServerInstance->SNO->WriteGlobalSno('v', user->nick+" used oper override to bypass +l on "+std::string(cname)); - return MOD_RES_ALLOW; - } + if (chan->IsModeSet(limit) && (chan->GetUserCounter() >= ConvToNum<size_t>(chan->GetModeParameter(limit))) && (CanOverride(user,"LIMIT"))) + return HandleJoinOverride(user, chan, keygiven, "the channel limit", "+l"); if (chan->IsBanned(user) && CanOverride(user,"BANWALK")) - { - if (RequireKey && keygiven != "override") - { - // Can't join normally -- must use a special key to bypass restrictions - user->WriteServ("NOTICE %s :*** You may not join normally. You must join with a key of 'override' to oper override.", user->nick.c_str()); - return MOD_RES_PASSTHRU; - } - - if (NoisyOverride) - chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :%s used oper override to bypass channel ban", cname, user->nick.c_str()); - ServerInstance->SNO->WriteGlobalSno('v',"%s used oper override to bypass channel ban on %s", user->nick.c_str(), cname); - return MOD_RES_ALLOW; - } + return HandleJoinOverride(user, chan, keygiven, "channel ban", "channel ban"); } } return MOD_RES_PASSTHRU; } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for allowing opers to override certain things",VF_VENDOR); } diff --git a/src/modules/m_passforward.cpp b/src/modules/m_passforward.cpp index c04b306b1..08d3533cd 100644 --- a/src/modules/m_passforward.cpp +++ b/src/modules/m_passforward.cpp @@ -17,29 +17,19 @@ */ -/* $ModDesc: Forwards a password users can send on connect (for example for NickServ identification). */ - #include "inspircd.h" class ModulePassForward : public Module { - private: std::string nickrequired, forwardmsg, forwardcmd; public: - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnPostConnect, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Sends server password to NickServ", VF_VENDOR); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("passforward"); nickrequired = tag->getString("nick", "NickServ"); @@ -54,22 +44,22 @@ class ModulePassForward : public Module char c = format[i]; if (c == '$') { - if (format.substr(i, 13) == "$nickrequired") + if (!format.compare(i, 13, "$nickrequired", 13)) { result.append(nickrequired); i += 12; } - else if (format.substr(i, 5) == "$nick") + else if (!format.compare(i, 5, "$nick", 5)) { result.append(user->nick); i += 4; } - else if (format.substr(i, 5) == "$user") + else if (!format.compare(i, 5, "$user", 5)) { result.append(user->ident); i += 4; } - else if (format.substr(i,5) == "$pass") + else if (!format.compare(i, 5, "$pass", 5)) { result.append(user->password); i += 4; @@ -82,27 +72,31 @@ class ModulePassForward : public Module } } - virtual void OnPostConnect(User* ruser) + void OnPostConnect(User* ruser) CXX11_OVERRIDE { LocalUser* user = IS_LOCAL(ruser); if (!user || user->password.empty()) return; + // If the connect class requires a password, don't forward it + if (!user->MyClass->config->getString("password").empty()) + return; + if (!nickrequired.empty()) { /* Check if nick exists and its server is ulined */ User* u = ServerInstance->FindNick(nickrequired); - if (!u || !ServerInstance->ULine(u->server)) + if (!u || !u->server->IsULine()) return; } std::string tmp; - FormatStr(tmp,forwardmsg, user); - user->WriteServ(tmp); + FormatStr(tmp, forwardmsg, user); + ServerInstance->Parser.ProcessBuffer(user, tmp); tmp.clear(); FormatStr(tmp,forwardcmd, user); - ServerInstance->Parser->ProcessBuffer(tmp,user); + ServerInstance->Parser.ProcessBuffer(user, tmp); } }; diff --git a/src/modules/m_password_hash.cpp b/src/modules/m_password_hash.cpp index 98462780b..137ddb96c 100644 --- a/src/modules/m_password_hash.cpp +++ b/src/modules/m_password_hash.cpp @@ -18,10 +18,8 @@ */ -/* $ModDesc: Allows for hashed oper passwords */ - #include "inspircd.h" -#include "hash.h" +#include "modules/hash.h" /* Handle /MKPASSWD */ @@ -34,74 +32,71 @@ class CommandMkpasswd : public Command Penalty = 5; } - void MakeHash(User* user, const std::string& algo, const std::string& stuff) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (algo.substr(0,5) == "hmac-") + if (!parameters[0].compare(0, 5, "hmac-", 5)) { - std::string type = algo.substr(5); + std::string type(parameters[0], 5); HashProvider* hp = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + type); if (!hp) { - user->WriteServ("NOTICE %s :Unknown hash type", user->nick.c_str()); - return; + user->WriteNotice("Unknown hash type"); + return CMD_FAILURE; + } + + if (hp->IsKDF()) + { + user->WriteNotice(type + " does not support HMAC"); + return CMD_FAILURE; } - std::string salt = ServerInstance->GenRandomStr(6, false); - std::string target = hp->hmac(salt, stuff); + + std::string salt = ServerInstance->GenRandomStr(hp->out_size, false); + std::string target = hp->hmac(salt, parameters[1]); std::string str = BinToBase64(salt) + "$" + BinToBase64(target, NULL, 0); - user->WriteServ("NOTICE %s :%s hashed password for %s is %s", - user->nick.c_str(), algo.c_str(), stuff.c_str(), str.c_str()); - return; - } - HashProvider* hp = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + algo); - if (hp) - { - /* Now attempt to generate a hash */ - std::string hexsum = hp->hexsum(stuff); - user->WriteServ("NOTICE %s :%s hashed password for %s is %s", - user->nick.c_str(), algo.c_str(), stuff.c_str(), hexsum.c_str()); + user->WriteNotice(parameters[0] + " hashed password for " + parameters[1] + " is " + str); + return CMD_SUCCESS; } - else + + HashProvider* hp = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + parameters[0]); + if (!hp) { - user->WriteServ("NOTICE %s :Unknown hash type", user->nick.c_str()); + user->WriteNotice("Unknown hash type"); + return CMD_FAILURE; } - } - - CmdResult Handle (const std::vector<std::string>& parameters, User *user) - { - MakeHash(user, parameters[0], parameters[1]); + std::string hexsum = hp->Generate(parameters[1]); + user->WriteNotice(parameters[0] + " hashed password for " + parameters[1] + " is " + hexsum); return CMD_SUCCESS; } }; -class ModuleOperHash : public Module +class ModulePasswordHash : public Module { + private: CommandMkpasswd cmd; - public: - - ModuleOperHash() : cmd(this) - { - } - void init() + public: + ModulePasswordHash() + : cmd(this) { - /* Read the config file first */ - OnRehash(NULL); - - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnPassCompare }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual ModResult OnPassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype) + ModResult OnPassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype) CXX11_OVERRIDE { - if (hashtype.substr(0,5) == "hmac-") + if (!hashtype.compare(0, 5, "hmac-", 5)) { - std::string type = hashtype.substr(5); + std::string type(hashtype, 5); HashProvider* hp = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + type); if (!hp) return MOD_RES_PASSTHRU; + + if (hp->IsKDF()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Tried to use HMAC with %s, which does not support HMAC", type.c_str()); + return MOD_RES_DENY; + } + // this is a valid hash, from here on we either accept or deny std::string::size_type sep = data.find('$'); if (sep == std::string::npos) @@ -120,22 +115,21 @@ class ModuleOperHash : public Module /* Is this a valid hash name? */ if (hp) { - /* Compare the hash in the config to the generated hash */ - if (data == hp->hexsum(input)) + if (hp->Compare(input, data)) return MOD_RES_ALLOW; else /* No match, and must be hashed, forbid */ return MOD_RES_DENY; } - /* Not a hash, fall through to strcmp in core */ + // We don't handle this type, let other mods or the core decide return MOD_RES_PASSTHRU; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Allows for hashed oper passwords",VF_VENDOR); + return Version("Provides the ability to hash passwords to other modules", VF_VENDOR); } }; -MODULE_INIT(ModuleOperHash) +MODULE_INIT(ModulePasswordHash) diff --git a/src/modules/m_pbkdf2.cpp b/src/modules/m_pbkdf2.cpp new file mode 100644 index 000000000..89bc39ef4 --- /dev/null +++ b/src/modules/m_pbkdf2.cpp @@ -0,0 +1,226 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Daniel Vassdal <shutter@canternet.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/hash.h" + +// Format: +// Iterations:B64(Hash):B64(Salt) +// E.g. +// 10200:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +class PBKDF2Hash +{ + public: + unsigned int iterations; + unsigned int length; + std::string salt; + std::string hash; + + PBKDF2Hash(unsigned int itr, unsigned int dkl, const std::string& slt, const std::string& hsh = "") + : iterations(itr), length(dkl), salt(slt), hash(hsh) + { + } + + PBKDF2Hash(const std::string& data) + { + irc::sepstream ss(data, ':'); + std::string tok; + + ss.GetToken(tok); + this->iterations = ConvToInt(tok); + + ss.GetToken(tok); + this->hash = Base64ToBin(tok); + + ss.GetToken(tok); + this->salt = Base64ToBin(tok); + + this->length = this->hash.length(); + } + + std::string ToString() + { + if (!IsValid()) + return ""; + return ConvToStr(this->iterations) + ":" + BinToBase64(this->hash) + ":" + BinToBase64(this->salt); + } + + bool IsValid() + { + if (!this->iterations || !this->length || this->salt.empty() || this->hash.empty()) + return false; + return true; + } +}; + +class PBKDF2Provider : public HashProvider +{ + public: + HashProvider* provider; + unsigned int iterations; + unsigned int dkey_length; + + std::string PBKDF2(const std::string& pass, const std::string& salt, unsigned int itr = 0, unsigned int dkl = 0) + { + size_t blocks = std::ceil((double)dkl / provider->out_size); + + std::string output; + std::string tmphash; + std::string salt_block = salt; + for (size_t block = 1; block <= blocks; block++) + { + char salt_data[4]; + for (size_t i = 0; i < sizeof(salt_data); i++) + salt_data[i] = block >> (24 - i * 8) & 0x0F; + + salt_block.erase(salt.length()); + salt_block.append(salt_data, sizeof(salt_data)); + + std::string blockdata = provider->hmac(pass, salt_block); + std::string lasthash = blockdata; + for (size_t iter = 1; iter < itr; iter++) + { + tmphash = provider->hmac(pass, lasthash); + for (size_t i = 0; i < provider->out_size; i++) + blockdata[i] ^= tmphash[i]; + + lasthash.swap(tmphash); + } + output += blockdata; + } + + output.erase(dkl); + return output; + } + + std::string GenerateRaw(const std::string& data) CXX11_OVERRIDE + { + PBKDF2Hash hs(this->iterations, this->dkey_length, ServerInstance->GenRandomStr(dkey_length, false)); + hs.hash = PBKDF2(data, hs.salt, this->iterations, this->dkey_length); + return hs.ToString(); + } + + bool Compare(const std::string& input, const std::string& hash) CXX11_OVERRIDE + { + PBKDF2Hash hs(hash); + if (!hs.IsValid()) + return false; + + std::string cmp = PBKDF2(input, hs.salt, hs.iterations, hs.length); + return (cmp == hs.hash); + } + + std::string ToPrintable(const std::string& raw) CXX11_OVERRIDE + { + return raw; + } + + PBKDF2Provider(Module* mod, HashProvider* hp) + : HashProvider(mod, "pbkdf2-hmac-" + hp->name.substr(hp->name.find('/') + 1)) + , provider(hp) + { + DisableAutoRegister(); + } +}; + +class ModulePBKDF2 : public Module +{ + std::vector<PBKDF2Provider*> providers; + + void GetConfig() + { + // First set the common values + ConfigTag* tag = ServerInstance->Config->ConfValue("pbkdf2"); + unsigned int global_iterations = tag->getUInt("iterations", 12288, 1); + unsigned int global_dkey_length = tag->getUInt("length", 32, 1, 1024); + for (std::vector<PBKDF2Provider*>::iterator i = providers.begin(); i != providers.end(); ++i) + { + PBKDF2Provider* pi = *i; + pi->iterations = global_iterations; + pi->dkey_length = global_dkey_length; + } + + // Then the specific values + ConfigTagList tags = ServerInstance->Config->ConfTags("pbkdf2prov"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + tag = i->second; + std::string hash_name = "hash/" + tag->getString("hash"); + for (std::vector<PBKDF2Provider*>::iterator j = providers.begin(); j != providers.end(); ++j) + { + PBKDF2Provider* pi = *j; + if (pi->provider->name != hash_name) + continue; + + pi->iterations = tag->getUInt("iterations", global_iterations, 1); + pi->dkey_length = tag->getUInt("length", global_dkey_length, 1, 1024); + } + } + } + + public: + ~ModulePBKDF2() + { + stdalgo::delete_all(providers); + } + + void OnServiceAdd(ServiceProvider& provider) CXX11_OVERRIDE + { + // Check if it's a hash provider + if (provider.name.compare(0, 5, "hash/")) + return; + + HashProvider* hp = static_cast<HashProvider*>(&provider); + if (hp->IsKDF()) + return; + + PBKDF2Provider* prov = new PBKDF2Provider(this, hp); + providers.push_back(prov); + ServerInstance->Modules.AddService(*prov); + + GetConfig(); + } + + void OnServiceDel(ServiceProvider& prov) CXX11_OVERRIDE + { + for (std::vector<PBKDF2Provider*>::iterator i = providers.begin(); i != providers.end(); ++i) + { + PBKDF2Provider* item = *i; + if (item->provider != &prov) + continue; + + ServerInstance->Modules->DelService(*item); + delete item; + providers.erase(i); + break; + } + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + GetConfig(); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Implements PBKDF2 hashing", VF_VENDOR); + } +}; + +MODULE_INIT(ModulePBKDF2) diff --git a/src/modules/m_permchannels.cpp b/src/modules/m_permchannels.cpp index 74a798356..a7be6df08 100644 --- a/src/modules/m_permchannels.cpp +++ b/src/modules/m_permchannels.cpp @@ -19,192 +19,144 @@ #include "inspircd.h" +#include "listmode.h" +#include <fstream> -/* $ModDesc: Provides support for channel mode +P to provide permanent channels */ -struct ListModeData +/** Handles the +P channel mode + */ +class PermChannel : public ModeHandler { - std::string modes; - std::string params; + public: + PermChannel(Module* Creator) + : ModeHandler(Creator, "permanent", 'P', PARAM_NONE, MODETYPE_CHANNEL) + { + oper = true; + } + + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE + { + if (adding == channel->IsModeSet(this)) + return MODEACTION_DENY; + + channel->SetMode(this, adding); + if (!adding) + channel->CheckDestroy(); + + return MODEACTION_ALLOW; + } }; // Not in a class due to circular dependancy hell. static std::string permchannelsconf; -static bool WriteDatabase(Module* mod, bool save_listmodes) +static bool WriteDatabase(PermChannel& permchanmode, Module* mod, bool save_listmodes) { - FILE *f; + ChanModeReference ban(mod, "ban"); + /* + * We need to perform an atomic write so as not to fuck things up. + * So, let's write to a temporary file, flush it, then rename the file.. + * -- w00t + */ + // If the user has not specified a configuration file then we don't write one. if (permchannelsconf.empty()) - { - // Fake success. return true; - } - std::string tempname = permchannelsconf + ".tmp"; - - /* - * We need to perform an atomic write so as not to fuck things up. - * So, let's write to a temporary file, flush and sync the FD, then rename the file.. - * -- w00t - */ - f = fopen(tempname.c_str(), "w"); - if (!f) + std::string permchannelsnewconf = permchannelsconf + ".tmp"; + std::ofstream stream(permchannelsnewconf.c_str()); + if (!stream.is_open()) { - ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot create database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new db: %s (%d)", strerror(errno), errno); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Cannot create database \"%s\"! %s (%d)", permchannelsnewconf.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new permchan db \"%s\": %s (%d)", permchannelsnewconf.c_str(), strerror(errno), errno); return false; } - fputs("# Permchannels DB\n# This file is autogenerated; any changes will be overwritten!\n<config format=\"compat\">\n", f); - // Now, let's write. - std::string line; - for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); i++) + stream << "# This file is automatically generated by m_permchannels. Any changes will be overwritten." << std::endl + << "<config format=\"xml\">" << std::endl; + + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) { Channel* chan = i->second; - if (!chan->IsModeSet('P')) + if (!chan->IsModeSet(permchanmode)) continue; std::string chanmodes = chan->ChanModes(true); if (save_listmodes) { - ListModeData lm; + std::string modes; + std::string params; - // Bans are managed by the core, so we have to process them separately - lm.modes = std::string(chan->bans.size(), 'b'); - for (BanList::const_iterator j = chan->bans.begin(); j != chan->bans.end(); ++j) + const ModeParser::ListModeList& listmodes = ServerInstance->Modes->GetListModes(); + for (ModeParser::ListModeList::const_iterator j = listmodes.begin(); j != listmodes.end(); ++j) { - lm.params += j->data; - lm.params += ' '; - } + ListModeBase* lm = *j; + ListModeBase::ModeList* list = lm->GetList(chan); + if (!list || list->empty()) + continue; + + size_t n = 0; + // Append the parameters + for (ListModeBase::ModeList::const_iterator k = list->begin(); k != list->end(); ++k, n++) + { + params += k->mask; + params += ' '; + } - // All other listmodes are managed by modules, so we need to ask them (call their - // OnSyncChannel() handler) to give our ProtoSendMode() a list of modes that are - // set on the channel. The ListModeData struct is passed as an opaque pointer - // that will be passed back to us by the module handling the mode. - FOREACH_MOD(I_OnSyncChannel, OnSyncChannel(chan, mod, &lm)); + // Append the mode letters (for example "IIII", "gg") + modes.append(n, lm->GetModeChar()); + } - if (!lm.modes.empty()) + if (!params.empty()) { // Remove the last space - lm.params.erase(lm.params.end()-1); + params.erase(params.end()-1); // If there is at least a space in chanmodes (that is, a non-listmode has a parameter) // insert the listmode mode letters before the space. Otherwise just append them. std::string::size_type p = chanmodes.find(' '); if (p == std::string::npos) - chanmodes += lm.modes; + chanmodes += modes; else - chanmodes.insert(p, lm.modes); + chanmodes.insert(p, modes); // Append the listmode parameters (the masks themselves) chanmodes += ' '; - chanmodes += lm.params; + chanmodes += params; } } - std::string chants = ConvToStr(chan->age); - std::string topicts = ConvToStr(chan->topicset); - const char* items[] = - { - "<permchannels channel=", - chan->name.c_str(), - " ts=", - chants.c_str(), - " topic=", - chan->topic.c_str(), - " topicts=", - topicts.c_str(), - " topicsetby=", - chan->setby.c_str(), - " modes=", - chanmodes.c_str(), - ">\n" - }; - - line.clear(); - int item = 0, ipos = 0; - while (item < 13) - { - char c = items[item][ipos++]; - if (c == 0) - { - // end of this string; hop to next string, insert a quote - item++; - ipos = 0; - c = '"'; - } - else if (c == '\\' || c == '"') - { - line += '\\'; - } - line += c; - } - - // Erase last '"' - line.erase(line.end()-1); - fputs(line.c_str(), f); + stream << "<permchannels channel=\"" << ServerConfig::Escape(chan->name) + << "\" ts=\"" << chan->age + << "\" topic=\"" << ServerConfig::Escape(chan->topic) + << "\" topicts=\"" << chan->topicset + << "\" topicsetby=\"" << ServerConfig::Escape(chan->setby) + << "\" modes=\"" << ServerConfig::Escape(chanmodes) + << "\">" << std::endl; } - int write_error = 0; - write_error = ferror(f); - write_error |= fclose(f); - if (write_error) + if (stream.fail()) { - ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot write to new database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot write to new db: %s (%d)", strerror(errno), errno); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Cannot write to new database \"%s\"! %s (%d)", permchannelsnewconf.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('a', "database: cannot write to new permchan db \"%s\": %s (%d)", permchannelsnewconf.c_str(), strerror(errno), errno); return false; } + stream.close(); #ifdef _WIN32 remove(permchannelsconf.c_str()); #endif // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash. - if (rename(tempname.c_str(), permchannelsconf.c_str()) < 0) + if (rename(permchannelsnewconf.c_str(), permchannelsconf.c_str()) < 0) { - ServerInstance->Logs->Log("m_permchannels",DEFAULT, "permchannels: Cannot move new to old database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot replace old with new db: %s (%d)", strerror(errno), errno); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Cannot replace old database \"%s\" with new database \"%s\"! %s (%d)", permchannelsconf.c_str(), permchannelsnewconf.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('a', "database: cannot replace old permchan db \"%s\" with new db \"%s\": %s (%d)", permchannelsconf.c_str(), permchannelsnewconf.c_str(), strerror(errno), errno); return false; } return true; } - - -/** Handles the +P channel mode - */ -class PermChannel : public ModeHandler -{ - public: - PermChannel(Module* Creator) : ModeHandler(Creator, "permanent", 'P', PARAM_NONE, MODETYPE_CHANNEL) { oper = true; } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - if (adding) - { - if (!channel->IsModeSet('P')) - { - channel->SetMode('P',true); - return MODEACTION_ALLOW; - } - } - else - { - if (channel->IsModeSet('P')) - { - channel->SetMode(this,false); - if (channel->GetUserCounter() == 0) - { - channel->DelUser(ServerInstance->FakeClient); - } - return MODEACTION_ALLOW; - } - } - - return MODEACTION_DENY; - } -}; - class ModulePermanentChannels : public Module { PermChannel p; @@ -218,46 +170,14 @@ public: { } - void init() - { - ServerInstance->Modules->AddService(p); - Implementation eventlist[] = { I_OnChannelPreDelete, I_OnPostTopicChange, I_OnRawMode, I_OnRehash, I_OnBackgroundTimer }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - OnRehash(NULL); - } - - CullResult cull() - { - /* - * DelMode can't remove the +P mode on empty channels, or it will break - * merging modes with remote servers. Remove the empty channels now as - * we know this is not the case. - */ - chan_hash::iterator iter = ServerInstance->chanlist->begin(); - while (iter != ServerInstance->chanlist->end()) - { - Channel* c = iter->second; - if (c->GetUserCounter() == 0) - { - chan_hash::iterator at = iter; - iter++; - FOREACH_MOD(I_OnChannelDelete, OnChannelDelete(c)); - ServerInstance->chanlist->erase(at); - ServerInstance->GlobalCulls.AddItem(c); - } - else - iter++; - } - ServerInstance->Modes->DelMode(&p); - return Module::cull(); - } - - virtual void OnRehash(User *user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("permchanneldb"); permchannelsconf = tag->getString("filename"); save_listmodes = tag->getBool("listmodes"); + + if (!permchannelsconf.empty()) + permchannelsconf = ServerInstance->Config->Paths.PrependConfig(permchannelsconf); } void LoadDatabase() @@ -271,12 +191,11 @@ public: { ConfigTag* tag = i->second; std::string channel = tag->getString("channel"); - std::string topic = tag->getString("topic"); std::string modes = tag->getString("modes"); if ((channel.empty()) || (channel.length() > ServerInstance->Config->Limits.ChanMax)) { - ServerInstance->Logs->Log("m_permchannels", DEFAULT, "Ignoring permchannels tag with empty or too long channel name (\"" + channel + "\")"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring permchannels tag with empty or too long channel name (\"" + channel + "\")"); continue; } @@ -284,26 +203,24 @@ public: if (!c) { - time_t TS = tag->getInt("ts"); - c = new Channel(channel, ((TS > 0) ? TS : ServerInstance->Time())); + time_t TS = tag->getInt("ts", ServerInstance->Time(), 1); + c = new Channel(channel, TS); - c->SetTopic(NULL, topic, true); - c->setby = tag->getString("topicsetby"); - if (c->setby.empty()) - c->setby = ServerInstance->Config->ServerName; - unsigned int topicset = tag->getInt("topicts"); - // SetTopic() sets the topic TS to now, if there was no topicts saved then don't overwrite that with a 0 - if (topicset > 0) - c->topicset = topicset; + time_t topicset = tag->getInt("topicts", 0); + std::string topic = tag->getString("topic"); - ServerInstance->Logs->Log("m_permchannels", DEBUG, "Added %s with topic %s", channel.c_str(), topic.c_str()); - - if (modes.find('P') == std::string::npos) + if ((topicset != 0) || (!topic.empty())) { - ServerInstance->Logs->Log("m_permchannels", DEFAULT, "%s (%s) does not have +P set in <permchannels:modes>; it will be deleted when empty!", - c->name.c_str(), tag->getTagLocation().c_str()); + if (topicset == 0) + topicset = ServerInstance->Time(); + std::string topicsetby = tag->getString("topicsetby"); + if (topicsetby.empty()) + topicsetby = ServerInstance->Config->ServerName; + c->SetTopic(ServerInstance->FakeClient, topic, topicset, &topicsetby); } + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Added %s with topic %s", channel.c_str(), c->topic.c_str()); + if (modes.empty()) continue; @@ -319,7 +236,7 @@ public: ModeHandler* mode = ServerInstance->Modes->FindMode(*n, MODETYPE_CHANNEL); if (mode) { - if (mode->GetNumParams(true)) + if (mode->NeedsParam(true)) list.GetToken(par); else par.clear(); @@ -327,32 +244,36 @@ public: mode->OnModeChange(ServerInstance->FakeClient, ServerInstance->FakeClient, c, par, true); } } + + // We always apply the permchannels mode to permanent channels. + par.clear(); + p.OnModeChange(ServerInstance->FakeClient, ServerInstance->FakeClient, c, par, true); } } } - virtual ModResult OnRawMode(User* user, Channel* chan, const char mode, const std::string ¶m, bool adding, int pcnt) + ModResult OnRawMode(User* user, Channel* chan, ModeHandler* mh, const std::string& param, bool adding) CXX11_OVERRIDE { - if (chan && (chan->IsModeSet('P') || mode == 'P')) + if (chan && (chan->IsModeSet(p) || mh == &p)) dirty = true; return MOD_RES_PASSTHRU; } - virtual void OnPostTopicChange(User*, Channel *c, const std::string&) + void OnPostTopicChange(User*, Channel *c, const std::string&) CXX11_OVERRIDE { - if (c->IsModeSet('P')) + if (c->IsModeSet(p)) dirty = true; } - void OnBackgroundTimer(time_t) + void OnBackgroundTimer(time_t) CXX11_OVERRIDE { if (dirty) - WriteDatabase(this, save_listmodes); + WriteDatabase(p, this, save_listmodes); dirty = false; } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { // XXX: Load the DB here because the order in which modules are init()ed at boot is // alphabetical, this means we must wait until all modules have done their init() @@ -367,7 +288,7 @@ public: // Load only when there are no linked servers - we set the TS of the channels we // create to the current time, this can lead to desync because spanningtree has // no way of knowing what we do - ProtoServerList serverlist; + ProtocolInterface::ServerList serverlist; ServerInstance->PI->GetServerList(serverlist); if (serverlist.size() < 2) { @@ -377,38 +298,19 @@ public: } catch (CoreException& e) { - ServerInstance->Logs->Log("m_permchannels", DEFAULT, "Error loading permchannels database: " + std::string(e.GetReason())); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error loading permchannels database: " + std::string(e.GetReason())); } } } - void ProtoSendMode(void* opaque, TargetTypeFlags type, void* target, const std::vector<std::string>& modes, const std::vector<TranslateType>& translate) - { - // We never pass an empty modelist but better be sure - if (modes.empty()) - return; - - ListModeData* lm = static_cast<ListModeData*>(opaque); - - // Append the mode letters without the trailing '+' (for example "IIII", "gg") - lm->modes.append(modes[0].begin()+1, modes[0].end()); - - // Append the parameters - for (std::vector<std::string>::const_iterator i = modes.begin()+1; i != modes.end(); ++i) - { - lm->params += *i; - lm->params += ' '; - } - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for channel mode +P to provide permanent channels",VF_VENDOR); } - virtual ModResult OnChannelPreDelete(Channel *c) + ModResult OnChannelPreDelete(Channel *c) CXX11_OVERRIDE { - if (c->IsModeSet('P')) + if (c->IsModeSet(p)) return MOD_RES_DENY; return MOD_RES_PASSTHRU; diff --git a/src/modules/m_randquote.cpp b/src/modules/m_randquote.cpp index 668eea0e5..1bb28583e 100644 --- a/src/modules/m_randquote.cpp +++ b/src/modules/m_randquote.cpp @@ -21,80 +21,37 @@ */ -/* $ModDesc: Provides random quotes on connect. */ - #include "inspircd.h" -static FileReader *quotes = NULL; - -std::string prefix; -std::string suffix; - -/** Handle /RANDQUOTE - */ -class CommandRandquote : public Command -{ - public: - CommandRandquote(Module* Creator) : Command(Creator,"RANDQUOTE", 0) - { - } - - CmdResult Handle (const std::vector<std::string>& parameters, User *user) - { - int fsize = quotes->FileSize(); - if (fsize) - { - std::string str = quotes->GetLine(ServerInstance->GenRandomInt(fsize)); - if (!str.empty()) - user->WriteServ("NOTICE %s :%s%s%s",user->nick.c_str(),prefix.c_str(),str.c_str(),suffix.c_str()); - } - - return CMD_SUCCESS; - } -}; - class ModuleRandQuote : public Module { private: - CommandRandquote cmd; - public: - ModuleRandQuote() - : cmd(this) - { - } + std::string prefix; + std::string suffix; + std::vector<std::string> quotes; - void init() + public: + void init() CXX11_OVERRIDE { ConfigTag* conf = ServerInstance->Config->ConfValue("randquote"); - - std::string q_file = conf->getString("file","quotes"); prefix = conf->getString("prefix"); suffix = conf->getString("suffix"); - - quotes = new FileReader(q_file); - if (!quotes->Exists()) - { - throw ModuleException("m_randquote: QuoteFile not Found!! Please check your config - module will not function."); - } - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnUserConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + FileReader reader(conf->getString("file", "quotes")); + quotes = reader.GetVector(); } - - virtual ~ModuleRandQuote() + void OnUserConnect(LocalUser* user) CXX11_OVERRIDE { - delete quotes; - } - - virtual Version GetVersion() - { - return Version("Provides random quotes on connect.",VF_VENDOR); + if (!quotes.empty()) + { + unsigned long random = ServerInstance->GenRandomInt(quotes.size()); + user->WriteNotice(prefix + quotes[random] + suffix); + } } - virtual void OnUserConnect(LocalUser* user) + Version GetVersion() CXX11_OVERRIDE { - cmd.Handle(std::vector<std::string>(), user); + return Version("Provides random quotes on connect.", VF_VENDOR); } }; diff --git a/src/modules/m_redirect.cpp b/src/modules/m_redirect.cpp index 26d6b162b..b71a2f3db 100644 --- a/src/modules/m_redirect.cpp +++ b/src/modules/m_redirect.cpp @@ -24,143 +24,95 @@ #include "inspircd.h" -/* $ModDesc: Provides channel mode +L (limit redirection) and usermode +L (no forced redirection) */ - /** Handle channel mode +L */ -class Redirect : public ModeHandler +class Redirect : public ParamMode<Redirect, LocalStringExt> { public: - Redirect(Module* Creator) : ModeHandler(Creator, "redirect", 'L', PARAM_SETONLY, MODETYPE_CHANNEL) { } + Redirect(Module* Creator) + : ParamMode<Redirect, LocalStringExt>(Creator, "redirect", 'L') { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE { - if (adding) + if (IS_LOCAL(source)) { - if (IS_LOCAL(source)) + if (!ServerInstance->IsChannel(parameter)) { - if (!ServerInstance->IsChannel(parameter.c_str(), ServerInstance->Config->Limits.ChanMax)) - { - source->WriteNumeric(403, "%s %s :Invalid channel name", source->nick.c_str(), parameter.c_str()); - parameter.clear(); - return MODEACTION_DENY; - } + source->WriteNumeric(Numerics::NoSuchChannel(parameter)); + return MODEACTION_DENY; } + } - if (IS_LOCAL(source) && !IS_OPER(source)) + if (IS_LOCAL(source) && !source->IsOper()) + { + Channel* c = ServerInstance->FindChan(parameter); + if (!c) { - Channel* c = ServerInstance->FindChan(parameter); - if (!c) - { - source->WriteNumeric(690, "%s :Target channel %s must exist to be set as a redirect.",source->nick.c_str(),parameter.c_str()); - parameter.clear(); - return MODEACTION_DENY; - } - else if (c->GetPrefixValue(source) < OP_VALUE) - { - source->WriteNumeric(690, "%s :You must be opped on %s to set it as a redirect.",source->nick.c_str(),parameter.c_str()); - parameter.clear(); - return MODEACTION_DENY; - } - } - - if (channel->GetModeParameter('L') == parameter) + source->WriteNumeric(690, InspIRCd::Format("Target channel %s must exist to be set as a redirect.", parameter.c_str())); return MODEACTION_DENY; - /* - * We used to do some checking for circular +L here, but there is no real need for this any more especially as we - * now catch +L looping in PreJoin. Remove it, since O(n) logic makes me sad, and we catch it anyway. :) -- w00t - */ - channel->SetModeParam('L', parameter); - return MODEACTION_ALLOW; - } - else - { - if (channel->IsModeSet('L')) + } + else if (c->GetPrefixValue(source) < OP_VALUE) { - channel->SetModeParam('L', ""); - return MODEACTION_ALLOW; + source->WriteNumeric(690, InspIRCd::Format("You must be opped on %s to set it as a redirect.", parameter.c_str())); + return MODEACTION_DENY; } } - return MODEACTION_DENY; - + /* + * We used to do some checking for circular +L here, but there is no real need for this any more especially as we + * now catch +L looping in PreJoin. Remove it, since O(n) logic makes me sad, and we catch it anyway. :) -- w00t + */ + ext.set(channel, parameter); + return MODEACTION_ALLOW; } -}; -/** Handles usermode +L to stop forced redirection and print an error. -*/ -class AntiRedirect : public SimpleUserModeHandler -{ - public: - AntiRedirect(Module* Creator) : SimpleUserModeHandler(Creator, "antiredirect", 'L') {} + void SerializeParam(Channel* chan, const std::string* str, std::string& out) + { + out += *str; + } }; class ModuleRedirect : public Module { - Redirect re; - AntiRedirect re_u; - bool UseUsermode; + SimpleUserModeHandler antiredirectmode; + ChanModeReference limitmode; public: - ModuleRedirect() - : re(this), re_u(this) + : re(this) + , antiredirectmode(this, "antiredirect", 'L') + , limitmode(this, "limit") { } - void init() - { - /* Setting this here so it isn't changable by rehasing the config later. */ - UseUsermode = ServerInstance->Config->ConfValue("redirect")->getBool("antiredirect"); - - /* Channel mode */ - ServerInstance->Modules->AddService(re); - - /* Check to see if the usermode is enabled in the config */ - if (UseUsermode) - { - /* Log noting that this breaks compatability. */ - ServerInstance->Logs->Log("m_redirect", DEFAULT, "REDIRECT: Enabled usermode +L. This breaks linking with servers that do not have this enabled. This is disabled by default in the 2.0 branch but will be enabled in the next version."); - - /* Try to add the usermode */ - ServerInstance->Modules->AddService(re_u); - } - - Implementation eventlist[] = { I_OnUserPreJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { if (chan) { - if (chan->IsModeSet('L') && chan->IsModeSet('l')) + if (chan->IsModeSet(re) && chan->IsModeSet(limitmode)) { - if (chan->GetUserCounter() >= ConvToInt(chan->GetModeParameter('l'))) + if (chan->GetUserCounter() >= ConvToNum<size_t>(chan->GetModeParameter(limitmode))) { - std::string channel = chan->GetModeParameter('L'); + const std::string& channel = *re.ext.get(chan); /* sometimes broken ulines can make circular or chained +L, avoid this */ - Channel* destchan = NULL; - destchan = ServerInstance->FindChan(channel); - if (destchan && destchan->IsModeSet('L')) + Channel* destchan = ServerInstance->FindChan(channel); + if (destchan && destchan->IsModeSet(re)) { - user->WriteNumeric(470, "%s %s * :You may not join this channel. A redirect is set, but you may not be redirected as it is a circular loop.", user->nick.c_str(), cname); + user->WriteNumeric(470, cname, '*', "You may not join this channel. A redirect is set, but you may not be redirected as it is a circular loop."); return MOD_RES_DENY; } - /* We check the bool value here to make sure we have it enabled, if we don't then - usermode +L might be assigned to something else. */ - if (UseUsermode && user->IsModeSet('L')) + + if (user->IsModeSet(antiredirectmode)) { - user->WriteNumeric(470, "%s %s %s :Force redirection stopped.", - user->nick.c_str(), cname, channel.c_str()); + user->WriteNumeric(470, cname, channel, "Force redirection stopped."); return MOD_RES_DENY; } else { - user->WriteNumeric(470, "%s %s %s :You may not join this channel, so you are automatically being transferred to the redirect channel.", user->nick.c_str(), cname, channel.c_str()); - Channel::JoinUser(user, channel.c_str(), false, "", false, ServerInstance->Time()); + user->WriteNumeric(470, cname, channel, "You may not join this channel, so you are automatically being transferred to the redirect channel."); + Channel::JoinUser(user, channel); return MOD_RES_DENY; } } @@ -169,11 +121,7 @@ class ModuleRedirect : public Module return MOD_RES_PASSTHRU; } - virtual ~ModuleRedirect() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +L (limit redirection) and user mode +L (no forced redirection)", VF_VENDOR); } diff --git a/src/modules/m_regex.h b/src/modules/m_regex.h deleted file mode 100644 index 0233f938a..000000000 --- a/src/modules/m_regex.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#ifndef M_REGEX_H -#define M_REGEX_H - -#include "inspircd.h" - -class Regex : public classbase -{ -protected: - std::string regex_string; // The raw uncompiled regex string. - - // Constructor may as well be protected, as this class is abstract. - Regex(const std::string& rx) : regex_string(rx) - { - } - -public: - - virtual ~Regex() - { - } - - virtual bool Matches(const std::string& text) = 0; - - const std::string& GetRegexString() const - { - return regex_string; - } -}; - -class RegexFactory : public DataProvider -{ - public: - RegexFactory(Module* Creator, const std::string& Name) : DataProvider(Creator, Name) {} - - virtual Regex* Create(const std::string& expr) = 0; -}; - -#endif diff --git a/src/modules/m_regex_glob.cpp b/src/modules/m_regex_glob.cpp index 44d1a5898..9c3162885 100644 --- a/src/modules/m_regex_glob.cpp +++ b/src/modules/m_regex_glob.cpp @@ -18,11 +18,9 @@ */ -#include "m_regex.h" +#include "modules/regex.h" #include "inspircd.h" -/* $ModDesc: Regex module using plain wildcard matching. */ - class GlobRegex : public Regex { public: @@ -30,11 +28,7 @@ public: { } - virtual ~GlobRegex() - { - } - - virtual bool Matches(const std::string& text) + bool Matches(const std::string& text) CXX11_OVERRIDE { return InspIRCd::Match(text, this->regex_string); } @@ -43,7 +37,7 @@ public: class GlobFactory : public RegexFactory { public: - Regex* Create(const std::string& expr) + Regex* Create(const std::string& expr) CXX11_OVERRIDE { return new GlobRegex(expr); } @@ -55,11 +49,12 @@ class ModuleRegexGlob : public Module { GlobFactory gf; public: - ModuleRegexGlob() : gf(this) { - ServerInstance->Modules->AddService(gf); + ModuleRegexGlob() + : gf(this) + { } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Regex module using plain wildcard matching.", VF_VENDOR); } diff --git a/src/modules/m_regonlycreate.cpp b/src/modules/m_regonlycreate.cpp index 61f94c0bd..78b20ef6b 100644 --- a/src/modules/m_regonlycreate.cpp +++ b/src/modules/m_regonlycreate.cpp @@ -21,28 +21,27 @@ #include "inspircd.h" -#include "account.h" - -/* $ModDesc: Prevents users whose nicks are not registered from creating new channels */ +#include "modules/account.h" class ModuleRegOnlyCreate : public Module { + UserModeReference regusermode; + public: - void init() + ModuleRegOnlyCreate() + : regusermode(this, "u_registered") { - Implementation eventlist[] = { I_OnUserPreJoin }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { if (chan) return MOD_RES_PASSTHRU; - if (IS_OPER(user)) + if (user->IsOper()) return MOD_RES_PASSTHRU; - if (user->IsModeSet('r')) + if (user->IsModeSet(regusermode)) return MOD_RES_PASSTHRU; const AccountExtItem* ext = GetAccountExtItem(); @@ -50,15 +49,11 @@ class ModuleRegOnlyCreate : public Module return MOD_RES_PASSTHRU; // XXX. there may be a better numeric for this.. - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You must have a registered nickname to create a new channel", user->nick.c_str(), cname); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, cname, "You must have a registered nickname to create a new channel"); return MOD_RES_DENY; } - ~ModuleRegOnlyCreate() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Prevents users whose nicks are not registered from creating new channels", VF_VENDOR); } diff --git a/src/modules/m_remove.cpp b/src/modules/m_remove.cpp index cf139f4a3..8d60ff8c6 100644 --- a/src/modules/m_remove.cpp +++ b/src/modules/m_remove.cpp @@ -24,8 +24,6 @@ #include "inspircd.h" -/* $ModDesc: Provides a /remove command, this is mostly an alternative to /kick, except makes users appear to have parted the channel */ - /* * This module supports the use of the +q and +a usermodes, but should work without them too. * Usage of the command is restricted to +hoaq, and you cannot remove a user with a "higher" level than yourself. @@ -36,23 +34,27 @@ */ class RemoveBase : public Command { - private: bool& supportnokicks; + ChanModeReference& nokicksmode; public: - RemoveBase(Module* Creator, bool& snk, const char* cmdn) - : Command(Creator, cmdn, 2, 3), supportnokicks(snk) + unsigned int protectedrank; + + RemoveBase(Module* Creator, bool& snk, ChanModeReference& nkm, const char* cmdn) + : Command(Creator, cmdn, 2, 3) + , supportnokicks(snk) + , nokicksmode(nkm) { } - CmdResult HandleRMB(const std::vector<std::string>& parameters, User *user, bool neworder) + CmdResult HandleRMB(User* user, const CommandBase::Params& parameters, bool fpart) { User* target; Channel* channel; std::string reason; - std::string protectkey; - std::string founderkey; - bool hasnokicks; + + // If the command is a /REMOVE then detect the parameter order + bool neworder = ((fpart) || (parameters[0][0] == '#')); /* Set these to the parameters needed, the new version of this module switches it's parameters around * supplying a new command with the new order while keeping the old /remove with the older order. @@ -72,42 +74,55 @@ class RemoveBase : public Command channel = ServerInstance->FindChan(channame); /* Fix by brain - someone needs to learn to validate their input! */ - if ((!target) || (target->registered != REG_ALL) || (!channel)) + if (!channel) + { + user->WriteNumeric(Numerics::NoSuchChannel(channame)); + return CMD_FAILURE; + } + if ((!target) || (target->registered != REG_ALL)) { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), !channel ? channame.c_str() : username.c_str()); + user->WriteNumeric(Numerics::NoSuchNick(username)); return CMD_FAILURE; } if (!channel->HasUser(target)) { - user->WriteServ( "NOTICE %s :*** The user %s is not on channel %s", user->nick.c_str(), target->nick.c_str(), channel->name.c_str()); + user->WriteNotice(InspIRCd::Format("*** The user %s is not on channel %s", target->nick.c_str(), channel->name.c_str())); return CMD_FAILURE; } - int ulevel = channel->GetPrefixValue(user); - int tlevel = channel->GetPrefixValue(target); - - hasnokicks = (ServerInstance->Modules->Find("m_nokicks.so") && channel->IsModeSet('Q')); - - if (ServerInstance->ULine(target->server)) + if (target->server->IsULine()) { - user->WriteNumeric(482, "%s %s :Only a u-line may remove a u-line from a channel.", user->nick.c_str(), channame.c_str()); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, channame, "Only a u-line may remove a u-line from a channel."); return CMD_FAILURE; } /* We support the +Q channel mode via. the m_nokicks module, if the module is loaded and the mode is set then disallow the /remove */ - if ((!IS_LOCAL(user)) || (!supportnokicks || !hasnokicks)) + if ((!IS_LOCAL(user)) || (!supportnokicks) || (!channel->IsModeSet(nokicksmode))) { /* We'll let everyone remove their level and below, eg: * ops can remove ops, halfops, voices, and those with no mode (no moders actually are set to 1) - * a ulined target will get a higher level than it's possible for a /remover to get..so they're safe. - * Nobody may remove a founder. + a ulined target will get a higher level than it's possible for a /remover to get..so they're safe. + * Nobody may remove people with >= protectedrank rank. */ - if ((!IS_LOCAL(user)) || ((ulevel > VOICE_VALUE) && (ulevel >= tlevel) && (tlevel != 50000))) + unsigned int ulevel = channel->GetPrefixValue(user); + unsigned int tlevel = channel->GetPrefixValue(target); + if ((!IS_LOCAL(user)) || ((ulevel > VOICE_VALUE) && (ulevel >= tlevel) && ((protectedrank == 0) || (tlevel < protectedrank)))) { - // REMOVE/FPART will be sent to the target's server and it will reply with a PART (or do nothing if it doesn't understand the command) + // REMOVE will be sent to the target's server and it will reply with a PART (or do nothing if it doesn't understand the command) if (!IS_LOCAL(target)) + { + // Send an ENCAP REMOVE with parameters being in the old <user> <chan> order which is + // compatible with both 2.0 and 3.0. This also turns FPART into REMOVE. + CommandBase::Params p; + p.push_back(target->uuid); + p.push_back(channel->name); + if (parameters.size() > 2) + p.push_back(":" + parameters[2]); + ServerInstance->PI->SendEncapsulatedData(target->server->GetName(), "REMOVE", p, user); + return CMD_SUCCESS; + } std::string reasonparam; @@ -120,27 +135,26 @@ class RemoveBase : public Command /* Build up the part reason string. */ reason = "Removed by " + user->nick + ": " + reasonparam; - channel->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :%s removed %s from the channel", channel->name.c_str(), user->nick.c_str(), target->nick.c_str()); - target->WriteServ("NOTICE %s :*** %s removed you from %s with the message: %s", target->nick.c_str(), user->nick.c_str(), channel->name.c_str(), reasonparam.c_str()); + channel->WriteNotice(InspIRCd::Format("%s removed %s from the channel", user->nick.c_str(), target->nick.c_str())); + target->WriteNotice("*** " + user->nick + " removed you from " + channel->name + " with the message: " + reasonparam); channel->PartUser(target, reason); } else { - user->WriteServ( "NOTICE %s :*** You do not have access to /remove %s from %s", user->nick.c_str(), target->nick.c_str(), channel->name.c_str()); + user->WriteNotice(InspIRCd::Format("*** You do not have access to /remove %s from %s", target->nick.c_str(), channel->name.c_str())); return CMD_FAILURE; } } else { /* m_nokicks.so was loaded and +Q was set, block! */ - user->WriteServ( "484 %s %s :Can't remove user %s from channel (+Q set)", user->nick.c_str(), channel->name.c_str(), target->nick.c_str()); + user->WriteNumeric(ERR_RESTRICTED, channel->name, InspIRCd::Format("Can't remove user %s from channel (nokicks mode is set)", target->nick.c_str())); return CMD_FAILURE; } return CMD_SUCCESS; } - virtual RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) = 0; }; /** Handle /REMOVE @@ -148,24 +162,16 @@ class RemoveBase : public Command class CommandRemove : public RemoveBase { public: - CommandRemove(Module* Creator, bool& snk) - : RemoveBase(Creator, snk, "REMOVE") - { - syntax = "<nick> <channel> [<reason>]"; - TRANSLATE4(TR_NICK, TR_TEXT, TR_TEXT, TR_END); - } - - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CommandRemove(Module* Creator, bool& snk, ChanModeReference& nkm) + : RemoveBase(Creator, snk, nkm, "REMOVE") { - return HandleRMB(parameters, user, false); + syntax = "<channel> <nick> [<reason>]"; + TRANSLATE3(TR_NICK, TR_TEXT, TR_TEXT); } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return HandleRMB(user, parameters, false); } }; @@ -174,67 +180,50 @@ class CommandRemove : public RemoveBase class CommandFpart : public RemoveBase { public: - CommandFpart(Module* Creator, bool& snk) - : RemoveBase(Creator, snk, "FPART") + CommandFpart(Module* Creator, bool& snk, ChanModeReference& nkm) + : RemoveBase(Creator, snk, nkm, "FPART") { syntax = "<channel> <nick> [<reason>]"; - TRANSLATE4(TR_TEXT, TR_NICK, TR_TEXT, TR_END); - } - - CmdResult Handle (const std::vector<std::string>& parameters, User *user) - { - return HandleRMB(parameters, user, true); + TRANSLATE3(TR_TEXT, TR_NICK, TR_TEXT); } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[1]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return HandleRMB(user, parameters, true); } }; class ModuleRemove : public Module { + ChanModeReference nokicksmode; CommandRemove cmd1; CommandFpart cmd2; bool supportnokicks; - public: - ModuleRemove() : cmd1(this, supportnokicks), cmd2(this, supportnokicks) - { - } - - void init() - { - ServerInstance->Modules->AddService(cmd1); - ServerInstance->Modules->AddService(cmd2); - OnRehash(NULL); - Implementation eventlist[] = { I_On005Numeric, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void On005Numeric(std::string &output) + ModuleRemove() + : nokicksmode(this, "nokick") + , cmd1(this, supportnokicks, nokicksmode) + , cmd2(this, supportnokicks, nokicksmode) { - output.append(" REMOVE"); } - virtual void OnRehash(User* user) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - supportnokicks = ServerInstance->Config->ConfValue("remove")->getBool("supportnokicks"); + tokens["REMOVE"]; } - virtual ~ModuleRemove() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { + ConfigTag* tag = ServerInstance->Config->ConfValue("remove"); + supportnokicks = tag->getBool("supportnokicks"); + cmd1.protectedrank = cmd2.protectedrank = tag->getUInt("protectedrank", 50000); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides a /remove command, this is mostly an alternative to /kick, except makes users appear to have parted the channel", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleRemove) diff --git a/src/modules/m_repeat.cpp b/src/modules/m_repeat.cpp new file mode 100644 index 000000000..ef62e9ab1 --- /dev/null +++ b/src/modules/m_repeat.cpp @@ -0,0 +1,416 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/exemption.h" + +class ChannelSettings +{ + public: + enum RepeatAction + { + ACT_KICK, + ACT_BLOCK, + ACT_BAN + }; + + RepeatAction Action; + unsigned int Backlog; + unsigned int Lines; + unsigned int Diff; + unsigned int Seconds; + + void serialize(std::string& out) const + { + if (Action == ACT_BAN) + out.push_back('*'); + else if (Action == ACT_BLOCK) + out.push_back('~'); + + out.append(ConvToStr(Lines)).push_back(':'); + out.append(ConvToStr(Seconds)); + if (Diff) + { + out.push_back(':'); + out.append(ConvToStr(Diff)); + if (Backlog) + { + out.push_back(':'); + out.append(ConvToStr(Backlog)); + } + } + } +}; + +class RepeatMode : public ParamMode<RepeatMode, SimpleExtItem<ChannelSettings> > +{ + private: + struct RepeatItem + { + time_t ts; + std::string line; + RepeatItem(time_t TS, const std::string& Line) : ts(TS), line(Line) { } + }; + + typedef std::deque<RepeatItem> RepeatItemList; + + struct MemberInfo + { + RepeatItemList ItemList; + unsigned int Counter; + MemberInfo() : Counter(0) {} + }; + + struct ModuleSettings + { + unsigned int MaxLines; + unsigned int MaxSecs; + unsigned int MaxBacklog; + unsigned int MaxDiff; + unsigned int MaxMessageSize; + ModuleSettings() : MaxLines(0), MaxSecs(0), MaxBacklog(0), MaxDiff() { } + }; + + std::vector<unsigned int> mx[2]; + ModuleSettings ms; + + bool CompareLines(const std::string& message, const std::string& historyline, unsigned int trigger) + { + if (message == historyline) + return true; + else if (trigger) + return (Levenshtein(message, historyline) <= trigger); + + return false; + } + + unsigned int Levenshtein(const std::string& s1, const std::string& s2) + { + unsigned int l1 = s1.size(); + unsigned int l2 = s2.size(); + + for (unsigned int i = 0; i < l2; i++) + mx[0][i] = i; + for (unsigned int i = 0; i < l1; i++) + { + mx[1][0] = i + 1; + for (unsigned int j = 0; j < l2; j++) + mx[1][j + 1] = std::min(std::min(mx[1][j] + 1, mx[0][j + 1] + 1), mx[0][j] + ((s1[i] == s2[j]) ? 0 : 1)); + + mx[0].swap(mx[1]); + } + return mx[0][l2]; + } + + public: + SimpleExtItem<MemberInfo> MemberInfoExt; + + RepeatMode(Module* Creator) + : ParamMode<RepeatMode, SimpleExtItem<ChannelSettings> >(Creator, "repeat", 'E') + , MemberInfoExt("repeat_memb", ExtensionItem::EXT_MEMBERSHIP, Creator) + { + } + + void OnUnset(User* source, Channel* chan) + { + // Unset the per-membership extension when the mode is removed + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) + MemberInfoExt.unset(i->second); + } + + ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE + { + ChannelSettings settings; + if (!ParseSettings(source, parameter, settings)) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, + "Invalid repeat syntax. Syntax is {[~*]}[lines]:[time]{:[difference]}{:[backlog]}.")); + return MODEACTION_DENY; + } + + if ((settings.Backlog > 0) && (settings.Lines > settings.Backlog)) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, + "Invalid repeat syntax. You can't set needed lines higher than backlog.")); + return MODEACTION_DENY; + } + + LocalUser* localsource = IS_LOCAL(source); + if ((localsource) && (!ValidateSettings(localsource, channel, parameter, settings))) + return MODEACTION_DENY; + + ext.set(channel, settings); + + return MODEACTION_ALLOW; + } + + bool MatchLine(Membership* memb, ChannelSettings* rs, std::string message) + { + // If the message is larger than whatever size it's set to, + // let's pretend it isn't. If the first 512 (def. setting) match, it's probably spam. + if (message.size() > ms.MaxMessageSize) + message.erase(ms.MaxMessageSize); + + MemberInfo* rp = MemberInfoExt.get(memb); + if (!rp) + { + rp = new MemberInfo; + MemberInfoExt.set(memb, rp); + } + + unsigned int matches = 0; + if (!rs->Backlog) + matches = rp->Counter; + + RepeatItemList& items = rp->ItemList; + const unsigned int trigger = (message.size() * rs->Diff / 100); + const time_t now = ServerInstance->Time(); + + std::transform(message.begin(), message.end(), message.begin(), ::tolower); + + for (std::deque<RepeatItem>::iterator it = items.begin(); it != items.end(); ++it) + { + if (it->ts < now) + { + items.erase(it, items.end()); + matches = 0; + break; + } + + if (CompareLines(message, it->line, trigger)) + { + if (++matches >= rs->Lines) + { + if (rs->Action != ChannelSettings::ACT_BLOCK) + rp->Counter = 0; + return true; + } + } + else if ((ms.MaxBacklog == 0) || (rs->Backlog == 0)) + { + matches = 0; + items.clear(); + break; + } + } + + unsigned int max_items = (rs->Backlog ? rs->Backlog : 1); + if (items.size() >= max_items) + items.pop_back(); + + items.push_front(RepeatItem(now + rs->Seconds, message)); + rp->Counter = matches; + return false; + } + + void Resize(size_t size) + { + size_t newsize = size+1; + if (newsize <= mx[0].size()) + return; + ms.MaxMessageSize = size; + mx[0].resize(newsize); + mx[1].resize(newsize); + } + + void ReadConfig() + { + ConfigTag* conf = ServerInstance->Config->ConfValue("repeat"); + ms.MaxLines = conf->getUInt("maxlines", 20); + ms.MaxBacklog = conf->getUInt("maxbacklog", 20); + ms.MaxSecs = conf->getDuration("maxtime", conf->getDuration("maxsecs", 0)); + + ms.MaxDiff = conf->getUInt("maxdistance", 50); + if (ms.MaxDiff > 100) + ms.MaxDiff = 100; + + unsigned int newsize = conf->getUInt("size", 512); + if (newsize > ServerInstance->Config->Limits.MaxLine) + newsize = ServerInstance->Config->Limits.MaxLine; + Resize(newsize); + } + + std::string GetModuleSettings() const + { + return ConvToStr(ms.MaxLines) + ":" + ConvToStr(ms.MaxSecs) + ":" + ConvToStr(ms.MaxDiff) + ":" + ConvToStr(ms.MaxBacklog); + } + + void SerializeParam(Channel* chan, const ChannelSettings* chset, std::string& out) + { + chset->serialize(out); + } + + private: + bool ParseSettings(User* source, std::string& parameter, ChannelSettings& settings) + { + irc::sepstream stream(parameter, ':'); + std::string item; + if (!stream.GetToken(item)) + // Required parameter missing + return false; + + if ((item[0] == '*') || (item[0] == '~')) + { + settings.Action = ((item[0] == '*') ? ChannelSettings::ACT_BAN : ChannelSettings::ACT_BLOCK); + item.erase(item.begin()); + } + else + settings.Action = ChannelSettings::ACT_KICK; + + if ((settings.Lines = ConvToInt(item)) == 0) + return false; + + if ((!stream.GetToken(item)) || ((settings.Seconds = InspIRCd::Duration(item)) == 0)) + // Required parameter missing + return false; + + // The diff and backlog parameters are optional + settings.Diff = settings.Backlog = 0; + if (stream.GetToken(item)) + { + // There is a diff parameter, see if it's valid (> 0) + if ((settings.Diff = ConvToInt(item)) == 0) + return false; + + if (stream.GetToken(item)) + { + // There is a backlog parameter, see if it's valid + if ((settings.Backlog = ConvToInt(item)) == 0) + return false; + + // If there are still tokens, then it's invalid because we allow only 4 + if (stream.GetToken(item)) + return false; + } + } + + return true; + } + + bool ValidateSettings(LocalUser* source, Channel* channel, const std::string& parameter, const ChannelSettings& settings) + { + if (ms.MaxLines && settings.Lines > ms.MaxLines) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format( + "Invalid repeat parameter. The line number you specified is too great. Maximum allowed is %u.", ms.MaxLines))); + return false; + } + + if (ms.MaxSecs && settings.Seconds > ms.MaxSecs) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format( + "Invalid repeat parameter. The seconds you specified is too great. Maximum allowed is %u.", ms.MaxSecs))); + return false; + } + + if (settings.Diff && settings.Diff > ms.MaxDiff) + { + if (ms.MaxDiff == 0) + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, + "Invalid repeat parameter. The server administrator has disabled matching on edit distance.")); + else + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format( + "Invalid repeat parameter. The distance you specified is too great. Maximum allowed is %u.", ms.MaxDiff))); + return false; + } + + if (settings.Backlog && settings.Backlog > ms.MaxBacklog) + { + if (ms.MaxBacklog == 0) + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, + "Invalid repeat parameter. The server administrator has disabled backlog matching.")); + else + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format( + "Invalid repeat paramter. The backlog you specified is too great. Maximum allowed is %u.", ms.MaxBacklog))); + return false; + } + + return true; + } +}; + +class RepeatModule : public Module +{ + CheckExemption::EventProvider exemptionprov; + RepeatMode rm; + + public: + RepeatModule() + : exemptionprov(this) + , rm(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + rm.ReadConfig(); + } + + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE + { + if (target.type != MessageTarget::TYPE_CHANNEL || !IS_LOCAL(user)) + return MOD_RES_PASSTHRU; + + Channel* chan = target.Get<Channel>(); + ChannelSettings* settings = rm.ext.get(chan); + if (!settings) + return MOD_RES_PASSTHRU; + + Membership* memb = chan->GetUser(user); + if (!memb) + return MOD_RES_PASSTHRU; + + ModResult res = CheckExemption::Call(exemptionprov, user, chan, "repeat"); + if (res == MOD_RES_ALLOW) + return MOD_RES_PASSTHRU; + + if (rm.MatchLine(memb, settings, details.text)) + { + if (settings->Action == ChannelSettings::ACT_BLOCK) + { + user->WriteNotice("*** This line is too similar to one of your last lines."); + return MOD_RES_DENY; + } + + if (settings->Action == ChannelSettings::ACT_BAN) + { + Modes::ChangeList changelist; + changelist.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), "*!*@" + user->GetDisplayedHost()); + ServerInstance->Modes->Process(ServerInstance->FakeClient, chan, NULL, changelist); + } + + memb->chan->KickUser(ServerInstance->FakeClient, user, "Repeat flood"); + return MOD_RES_DENY; + } + return MOD_RES_PASSTHRU; + } + + void Prioritize() CXX11_OVERRIDE + { + ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the +E channel mode - for blocking of similar messages", VF_COMMON|VF_VENDOR, rm.GetModuleSettings()); + } +}; + +MODULE_INIT(RepeatModule) diff --git a/src/modules/m_restrictchans.cpp b/src/modules/m_restrictchans.cpp index c76b0e79f..9c7ed1213 100644 --- a/src/modules/m_restrictchans.cpp +++ b/src/modules/m_restrictchans.cpp @@ -22,13 +22,12 @@ #include "inspircd.h" -/* $ModDesc: Only opers may create new channels if this module is loaded */ - class ModuleRestrictChans : public Module { - std::set<irc::string> allowchans; + insp::flat_set<std::string, irc::insensitive_swo> allowchans; - void ReadConfig() + public: + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { allowchans.clear(); ConfigTagList tags = ServerInstance->Config->ConfTags("allowchannel"); @@ -36,48 +35,26 @@ class ModuleRestrictChans : public Module { ConfigTag* tag = i->second; std::string txt = tag->getString("name"); - allowchans.insert(txt.c_str()); + allowchans.insert(txt); } } - public: - void init() - { - ReadConfig(); - Implementation eventlist[] = { I_OnUserPreJoin, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void OnRehash(User* user) - { - ReadConfig(); - } - - - virtual ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - irc::string x = cname; - if (!IS_LOCAL(user)) - return MOD_RES_PASSTHRU; - // channel does not yet exist (record is null, about to be created IF we were to allow it) if (!chan) { // user is not an oper and its not in the allow list - if ((!IS_OPER(user)) && (allowchans.find(x) == allowchans.end())) + if ((!user->IsOper()) && (allowchans.find(cname) == allowchans.end())) { - user->WriteNumeric(ERR_BANNEDFROMCHAN, "%s %s :Only IRC operators may create new channels",user->nick.c_str(),cname); + user->WriteNumeric(ERR_BANNEDFROMCHAN, cname, "Only IRC operators may create new channels"); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - virtual ~ModuleRestrictChans() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Only opers may create new channels if this module is loaded",VF_VENDOR); } diff --git a/src/modules/m_restrictmsg.cpp b/src/modules/m_restrictmsg.cpp index 2a9f1dc93..16a9bae86 100644 --- a/src/modules/m_restrictmsg.cpp +++ b/src/modules/m_restrictmsg.cpp @@ -21,44 +21,25 @@ #include "inspircd.h" -/* $ModDesc: Forbids users from messaging each other. Users may still message opers and opers may message other opers. */ - - class ModuleRestrictMsg : public Module { - private: - bool uline; - public: - - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnRehash, I_OnUserPreMessage, I_OnUserPreNotice }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void OnRehash(User*) - { - uline = ServerInstance->Config->ConfValue("restrictmsg")->getBool("uline", false); - } - - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if ((target_type == TYPE_USER) && (IS_LOCAL(user))) + if ((target.type == MessageTarget::TYPE_USER) && (IS_LOCAL(user))) { - User* u = (User*)dest; + User* u = target.Get<User>(); // message allowed if: // (1) the sender is opered // (2) the recipient is opered // (3) the recipient is on a ulined server // anything else, blocked. - if (IS_OPER(u) || IS_OPER(user) || (uline && ServerInstance->ULine(u->server))) + if (u->IsOper() || user->IsOper() || u->server->IsULine()) { return MOD_RES_PASSTHRU; } - user->WriteNumeric(ERR_CANTSENDTOUSER, "%s %s :You are not permitted to send private messages to this user",user->nick.c_str(),u->nick.c_str()); + user->WriteNumeric(ERR_CANTSENDTOUSER, u->nick, "You are not permitted to send private messages to this user"); return MOD_RES_DENY; } @@ -66,16 +47,7 @@ class ModuleRestrictMsg : public Module return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return this->OnUserPreMessage(user,dest,target_type,text,status,exempt_list); - } - - virtual ~ModuleRestrictMsg() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Forbids users from messaging each other. Users may still message opers and opers may message other opers.",VF_VENDOR); } diff --git a/src/modules/m_ripemd160.cpp b/src/modules/m_ripemd160.cpp deleted file mode 100644 index 04c27e83d..000000000 --- a/src/modules/m_ripemd160.cpp +++ /dev/null @@ -1,481 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -/* - * - * AUTHOR: Antoon Bosselaers, ESAT-COSIC - * DATE: 1 March 1996 - * VERSION: 1.0 - * - * Copyright (c) Katholieke Universiteit Leuven - * 1996, All Rights Reserved - * - * Conditions for use of the RIPEMD-160 Software - * - * The RIPEMD-160 software is freely available for use under the terms and - * conditions described hereunder, which shall be deemed to be accepted by - * any user of the software and applicable on any use of the software: - * - * 1. K.U.Leuven Department of Electrical Engineering-ESAT/COSIC shall for - * all purposes be considered the owner of the RIPEMD-160 software and of - * all copyright, trade secret, patent or other intellectual property - * rights therein. - * 2. The RIPEMD-160 software is provided on an "as is" basis without - * warranty of any sort, express or implied. K.U.Leuven makes no - * representation that the use of the software will not infringe any - * patent or proprietary right of third parties. User will indemnify - * K.U.Leuven and hold K.U.Leuven harmless from any claims or liabilities - * which may arise as a result of its use of the software. In no - * circumstances K.U.Leuven R&D will be held liable for any deficiency, - * fault or other mishappening with regard to the use or performance of - * the software. - * 3. User agrees to give due credit to K.U.Leuven in scientific publications - * or communications in relation with the use of the RIPEMD-160 software - * as follows: RIPEMD-160 software written by Antoon Bosselaers, - * available at http://www.esat.kuleuven.be/~cosicart/ps/AB-9601/. - * - */ - - -/* $ModDesc: Allows for RIPEMD-160 encrypted oper passwords */ - -/* macro definitions */ - -#include "inspircd.h" -#ifdef HAS_STDINT -#include <stdint.h> -#endif -#include "hash.h" - -#define RMDsize 160 - -#ifndef HAS_STDINT -typedef unsigned char byte; -typedef unsigned int dword; -#else -typedef uint8_t byte; -typedef uint32_t dword; -#endif - -/* collect four bytes into one word: */ -#define BYTES_TO_DWORD(strptr) \ - (((dword) *((strptr)+3) << 24) | \ - ((dword) *((strptr)+2) << 16) | \ - ((dword) *((strptr)+1) << 8) | \ - ((dword) *(strptr))) - -/* ROL(x, n) cyclically rotates x over n bits to the left */ -/* x must be of an unsigned 32 bits type and 0 <= n < 32. */ -#define ROL(x, n) (((x) << (n)) | ((x) >> (32-(n)))) - -/* the five basic functions F(), G() and H() */ -#define F(x, y, z) ((x) ^ (y) ^ (z)) -#define G(x, y, z) (((x) & (y)) | (~(x) & (z))) -#define H(x, y, z) (((x) | ~(y)) ^ (z)) -#define I(x, y, z) (((x) & (z)) | ((y) & ~(z))) -#define J(x, y, z) ((x) ^ ((y) | ~(z))) - -/* the ten basic operations FF() through III() */ - -#define FF(a, b, c, d, e, x, s) {\ - (a) += F((b), (c), (d)) + (x);\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define GG(a, b, c, d, e, x, s) {\ - (a) += G((b), (c), (d)) + (x) + 0x5a827999UL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define HH(a, b, c, d, e, x, s) {\ - (a) += H((b), (c), (d)) + (x) + 0x6ed9eba1UL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define II(a, b, c, d, e, x, s) {\ - (a) += I((b), (c), (d)) + (x) + 0x8f1bbcdcUL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define JJ(a, b, c, d, e, x, s) {\ - (a) += J((b), (c), (d)) + (x) + 0xa953fd4eUL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define FFF(a, b, c, d, e, x, s) {\ - (a) += F((b), (c), (d)) + (x);\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define GGG(a, b, c, d, e, x, s) {\ - (a) += G((b), (c), (d)) + (x) + 0x7a6d76e9UL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define HHH(a, b, c, d, e, x, s) {\ - (a) += H((b), (c), (d)) + (x) + 0x6d703ef3UL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define III(a, b, c, d, e, x, s) {\ - (a) += I((b), (c), (d)) + (x) + 0x5c4dd124UL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - -#define JJJ(a, b, c, d, e, x, s) {\ - (a) += J((b), (c), (d)) + (x) + 0x50a28be6UL;\ - (a) = ROL((a), (s)) + (e);\ - (c) = ROL((c), 10);\ - } - - -class RIProv : public HashProvider -{ - /** Final hash value - */ - byte hashcode[RMDsize/8]; - - void MDinit(dword *MDbuf, unsigned int* key) - { - if (key) - { - ServerInstance->Logs->Log("m_ripemd160.so", DEBUG, "initialize with custom mdbuf"); - MDbuf[0] = key[0]; - MDbuf[1] = key[1]; - MDbuf[2] = key[2]; - MDbuf[3] = key[3]; - MDbuf[4] = key[4]; - } - else - { - ServerInstance->Logs->Log("m_ripemd160.so", DEBUG, "initialize with default mdbuf"); - MDbuf[0] = 0x67452301UL; - MDbuf[1] = 0xefcdab89UL; - MDbuf[2] = 0x98badcfeUL; - MDbuf[3] = 0x10325476UL; - MDbuf[4] = 0xc3d2e1f0UL; - } - return; - } - - - void compress(dword *MDbuf, dword *X) - { - dword aa = MDbuf[0], bb = MDbuf[1], cc = MDbuf[2], - dd = MDbuf[3], ee = MDbuf[4]; - dword aaa = MDbuf[0], bbb = MDbuf[1], ccc = MDbuf[2], - ddd = MDbuf[3], eee = MDbuf[4]; - - /* round 1 */ - FF(aa, bb, cc, dd, ee, X[ 0], 11); - FF(ee, aa, bb, cc, dd, X[ 1], 14); - FF(dd, ee, aa, bb, cc, X[ 2], 15); - FF(cc, dd, ee, aa, bb, X[ 3], 12); - FF(bb, cc, dd, ee, aa, X[ 4], 5); - FF(aa, bb, cc, dd, ee, X[ 5], 8); - FF(ee, aa, bb, cc, dd, X[ 6], 7); - FF(dd, ee, aa, bb, cc, X[ 7], 9); - FF(cc, dd, ee, aa, bb, X[ 8], 11); - FF(bb, cc, dd, ee, aa, X[ 9], 13); - FF(aa, bb, cc, dd, ee, X[10], 14); - FF(ee, aa, bb, cc, dd, X[11], 15); - FF(dd, ee, aa, bb, cc, X[12], 6); - FF(cc, dd, ee, aa, bb, X[13], 7); - FF(bb, cc, dd, ee, aa, X[14], 9); - FF(aa, bb, cc, dd, ee, X[15], 8); - - /* round 2 */ - GG(ee, aa, bb, cc, dd, X[ 7], 7); - GG(dd, ee, aa, bb, cc, X[ 4], 6); - GG(cc, dd, ee, aa, bb, X[13], 8); - GG(bb, cc, dd, ee, aa, X[ 1], 13); - GG(aa, bb, cc, dd, ee, X[10], 11); - GG(ee, aa, bb, cc, dd, X[ 6], 9); - GG(dd, ee, aa, bb, cc, X[15], 7); - GG(cc, dd, ee, aa, bb, X[ 3], 15); - GG(bb, cc, dd, ee, aa, X[12], 7); - GG(aa, bb, cc, dd, ee, X[ 0], 12); - GG(ee, aa, bb, cc, dd, X[ 9], 15); - GG(dd, ee, aa, bb, cc, X[ 5], 9); - GG(cc, dd, ee, aa, bb, X[ 2], 11); - GG(bb, cc, dd, ee, aa, X[14], 7); - GG(aa, bb, cc, dd, ee, X[11], 13); - GG(ee, aa, bb, cc, dd, X[ 8], 12); - - /* round 3 */ - HH(dd, ee, aa, bb, cc, X[ 3], 11); - HH(cc, dd, ee, aa, bb, X[10], 13); - HH(bb, cc, dd, ee, aa, X[14], 6); - HH(aa, bb, cc, dd, ee, X[ 4], 7); - HH(ee, aa, bb, cc, dd, X[ 9], 14); - HH(dd, ee, aa, bb, cc, X[15], 9); - HH(cc, dd, ee, aa, bb, X[ 8], 13); - HH(bb, cc, dd, ee, aa, X[ 1], 15); - HH(aa, bb, cc, dd, ee, X[ 2], 14); - HH(ee, aa, bb, cc, dd, X[ 7], 8); - HH(dd, ee, aa, bb, cc, X[ 0], 13); - HH(cc, dd, ee, aa, bb, X[ 6], 6); - HH(bb, cc, dd, ee, aa, X[13], 5); - HH(aa, bb, cc, dd, ee, X[11], 12); - HH(ee, aa, bb, cc, dd, X[ 5], 7); - HH(dd, ee, aa, bb, cc, X[12], 5); - - /* round 4 */ - II(cc, dd, ee, aa, bb, X[ 1], 11); - II(bb, cc, dd, ee, aa, X[ 9], 12); - II(aa, bb, cc, dd, ee, X[11], 14); - II(ee, aa, bb, cc, dd, X[10], 15); - II(dd, ee, aa, bb, cc, X[ 0], 14); - II(cc, dd, ee, aa, bb, X[ 8], 15); - II(bb, cc, dd, ee, aa, X[12], 9); - II(aa, bb, cc, dd, ee, X[ 4], 8); - II(ee, aa, bb, cc, dd, X[13], 9); - II(dd, ee, aa, bb, cc, X[ 3], 14); - II(cc, dd, ee, aa, bb, X[ 7], 5); - II(bb, cc, dd, ee, aa, X[15], 6); - II(aa, bb, cc, dd, ee, X[14], 8); - II(ee, aa, bb, cc, dd, X[ 5], 6); - II(dd, ee, aa, bb, cc, X[ 6], 5); - II(cc, dd, ee, aa, bb, X[ 2], 12); - - /* round 5 */ - JJ(bb, cc, dd, ee, aa, X[ 4], 9); - JJ(aa, bb, cc, dd, ee, X[ 0], 15); - JJ(ee, aa, bb, cc, dd, X[ 5], 5); - JJ(dd, ee, aa, bb, cc, X[ 9], 11); - JJ(cc, dd, ee, aa, bb, X[ 7], 6); - JJ(bb, cc, dd, ee, aa, X[12], 8); - JJ(aa, bb, cc, dd, ee, X[ 2], 13); - JJ(ee, aa, bb, cc, dd, X[10], 12); - JJ(dd, ee, aa, bb, cc, X[14], 5); - JJ(cc, dd, ee, aa, bb, X[ 1], 12); - JJ(bb, cc, dd, ee, aa, X[ 3], 13); - JJ(aa, bb, cc, dd, ee, X[ 8], 14); - JJ(ee, aa, bb, cc, dd, X[11], 11); - JJ(dd, ee, aa, bb, cc, X[ 6], 8); - JJ(cc, dd, ee, aa, bb, X[15], 5); - JJ(bb, cc, dd, ee, aa, X[13], 6); - - /* parallel round 1 */ - JJJ(aaa, bbb, ccc, ddd, eee, X[ 5], 8); - JJJ(eee, aaa, bbb, ccc, ddd, X[14], 9); - JJJ(ddd, eee, aaa, bbb, ccc, X[ 7], 9); - JJJ(ccc, ddd, eee, aaa, bbb, X[ 0], 11); - JJJ(bbb, ccc, ddd, eee, aaa, X[ 9], 13); - JJJ(aaa, bbb, ccc, ddd, eee, X[ 2], 15); - JJJ(eee, aaa, bbb, ccc, ddd, X[11], 15); - JJJ(ddd, eee, aaa, bbb, ccc, X[ 4], 5); - JJJ(ccc, ddd, eee, aaa, bbb, X[13], 7); - JJJ(bbb, ccc, ddd, eee, aaa, X[ 6], 7); - JJJ(aaa, bbb, ccc, ddd, eee, X[15], 8); - JJJ(eee, aaa, bbb, ccc, ddd, X[ 8], 11); - JJJ(ddd, eee, aaa, bbb, ccc, X[ 1], 14); - JJJ(ccc, ddd, eee, aaa, bbb, X[10], 14); - JJJ(bbb, ccc, ddd, eee, aaa, X[ 3], 12); - JJJ(aaa, bbb, ccc, ddd, eee, X[12], 6); - - /* parallel round 2 */ - III(eee, aaa, bbb, ccc, ddd, X[ 6], 9); - III(ddd, eee, aaa, bbb, ccc, X[11], 13); - III(ccc, ddd, eee, aaa, bbb, X[ 3], 15); - III(bbb, ccc, ddd, eee, aaa, X[ 7], 7); - III(aaa, bbb, ccc, ddd, eee, X[ 0], 12); - III(eee, aaa, bbb, ccc, ddd, X[13], 8); - III(ddd, eee, aaa, bbb, ccc, X[ 5], 9); - III(ccc, ddd, eee, aaa, bbb, X[10], 11); - III(bbb, ccc, ddd, eee, aaa, X[14], 7); - III(aaa, bbb, ccc, ddd, eee, X[15], 7); - III(eee, aaa, bbb, ccc, ddd, X[ 8], 12); - III(ddd, eee, aaa, bbb, ccc, X[12], 7); - III(ccc, ddd, eee, aaa, bbb, X[ 4], 6); - III(bbb, ccc, ddd, eee, aaa, X[ 9], 15); - III(aaa, bbb, ccc, ddd, eee, X[ 1], 13); - III(eee, aaa, bbb, ccc, ddd, X[ 2], 11); - - /* parallel round 3 */ - HHH(ddd, eee, aaa, bbb, ccc, X[15], 9); - HHH(ccc, ddd, eee, aaa, bbb, X[ 5], 7); - HHH(bbb, ccc, ddd, eee, aaa, X[ 1], 15); - HHH(aaa, bbb, ccc, ddd, eee, X[ 3], 11); - HHH(eee, aaa, bbb, ccc, ddd, X[ 7], 8); - HHH(ddd, eee, aaa, bbb, ccc, X[14], 6); - HHH(ccc, ddd, eee, aaa, bbb, X[ 6], 6); - HHH(bbb, ccc, ddd, eee, aaa, X[ 9], 14); - HHH(aaa, bbb, ccc, ddd, eee, X[11], 12); - HHH(eee, aaa, bbb, ccc, ddd, X[ 8], 13); - HHH(ddd, eee, aaa, bbb, ccc, X[12], 5); - HHH(ccc, ddd, eee, aaa, bbb, X[ 2], 14); - HHH(bbb, ccc, ddd, eee, aaa, X[10], 13); - HHH(aaa, bbb, ccc, ddd, eee, X[ 0], 13); - HHH(eee, aaa, bbb, ccc, ddd, X[ 4], 7); - HHH(ddd, eee, aaa, bbb, ccc, X[13], 5); - - /* parallel round 4 */ - GGG(ccc, ddd, eee, aaa, bbb, X[ 8], 15); - GGG(bbb, ccc, ddd, eee, aaa, X[ 6], 5); - GGG(aaa, bbb, ccc, ddd, eee, X[ 4], 8); - GGG(eee, aaa, bbb, ccc, ddd, X[ 1], 11); - GGG(ddd, eee, aaa, bbb, ccc, X[ 3], 14); - GGG(ccc, ddd, eee, aaa, bbb, X[11], 14); - GGG(bbb, ccc, ddd, eee, aaa, X[15], 6); - GGG(aaa, bbb, ccc, ddd, eee, X[ 0], 14); - GGG(eee, aaa, bbb, ccc, ddd, X[ 5], 6); - GGG(ddd, eee, aaa, bbb, ccc, X[12], 9); - GGG(ccc, ddd, eee, aaa, bbb, X[ 2], 12); - GGG(bbb, ccc, ddd, eee, aaa, X[13], 9); - GGG(aaa, bbb, ccc, ddd, eee, X[ 9], 12); - GGG(eee, aaa, bbb, ccc, ddd, X[ 7], 5); - GGG(ddd, eee, aaa, bbb, ccc, X[10], 15); - GGG(ccc, ddd, eee, aaa, bbb, X[14], 8); - - /* parallel round 5 */ - FFF(bbb, ccc, ddd, eee, aaa, X[12] , 8); - FFF(aaa, bbb, ccc, ddd, eee, X[15] , 5); - FFF(eee, aaa, bbb, ccc, ddd, X[10] , 12); - FFF(ddd, eee, aaa, bbb, ccc, X[ 4] , 9); - FFF(ccc, ddd, eee, aaa, bbb, X[ 1] , 12); - FFF(bbb, ccc, ddd, eee, aaa, X[ 5] , 5); - FFF(aaa, bbb, ccc, ddd, eee, X[ 8] , 14); - FFF(eee, aaa, bbb, ccc, ddd, X[ 7] , 6); - FFF(ddd, eee, aaa, bbb, ccc, X[ 6] , 8); - FFF(ccc, ddd, eee, aaa, bbb, X[ 2] , 13); - FFF(bbb, ccc, ddd, eee, aaa, X[13] , 6); - FFF(aaa, bbb, ccc, ddd, eee, X[14] , 5); - FFF(eee, aaa, bbb, ccc, ddd, X[ 0] , 15); - FFF(ddd, eee, aaa, bbb, ccc, X[ 3] , 13); - FFF(ccc, ddd, eee, aaa, bbb, X[ 9] , 11); - FFF(bbb, ccc, ddd, eee, aaa, X[11] , 11); - - /* combine results */ - ddd += cc + MDbuf[1]; /* final result for MDbuf[0] */ - MDbuf[1] = MDbuf[2] + dd + eee; - MDbuf[2] = MDbuf[3] + ee + aaa; - MDbuf[3] = MDbuf[4] + aa + bbb; - MDbuf[4] = MDbuf[0] + bb + ccc; - MDbuf[0] = ddd; - - return; - } - - void MDfinish(dword *MDbuf, byte *strptr, dword lswlen, dword mswlen) - { - unsigned int i; /* counter */ - dword X[16]; /* message words */ - - memset(X, 0, sizeof(X)); - - /* put bytes from strptr into X */ - for (i=0; i<(lswlen&63); i++) { - /* byte i goes into word X[i div 4] at pos. 8*(i mod 4) */ - X[i>>2] ^= (dword) *strptr++ << (8 * (i&3)); - } - - /* append the bit m_n == 1 */ - X[(lswlen>>2)&15] ^= (dword)1 << (8*(lswlen&3) + 7); - - if ((lswlen & 63) > 55) { - /* length goes to next block */ - compress(MDbuf, X); - memset(X, 0, sizeof(X)); - } - - /* append length in bits*/ - X[14] = lswlen << 3; - X[15] = (lswlen >> 29) | (mswlen << 3); - compress(MDbuf, X); - - return; - } - - byte *RMD(byte *message, dword length, unsigned int* key) - { - ServerInstance->Logs->Log("m_ripemd160", DEBUG, "RMD: '%s' length=%u", (const char*)message, length); - dword MDbuf[RMDsize/32]; /* contains (A, B, C, D(E)) */ - dword X[16]; /* current 16-word chunk */ - unsigned int i; /* counter */ - dword nbytes; /* # of bytes not yet processed */ - - /* initialize */ - MDinit(MDbuf, key); - - /* process message in 16-word chunks */ - for (nbytes=length; nbytes > 63; nbytes-=64) { - for (i=0; i<16; i++) { - X[i] = BYTES_TO_DWORD(message); - message += 4; - } - compress(MDbuf, X); - } /* length mod 64 bytes left */ - - MDfinish(MDbuf, message, length, 0); - - for (i=0; i<RMDsize/8; i+=4) { - hashcode[i] = MDbuf[i>>2]; /* implicit cast to byte */ - hashcode[i+1] = (MDbuf[i>>2] >> 8); /* extracts the 8 least */ - hashcode[i+2] = (MDbuf[i>>2] >> 16); /* significant bits. */ - hashcode[i+3] = (MDbuf[i>>2] >> 24); - } - - return (byte *)hashcode; - } -public: - std::string sum(const std::string& data) - { - char* rv = (char*)RMD((byte*)data.data(), data.length(), NULL); - return std::string(rv, RMDsize / 8); - } - - std::string sumIV(unsigned int* IV, const char* HexMap, const std::string &sdata) - { - return ""; - } - - RIProv(Module* m) : HashProvider(m, "hash/ripemd160", 20, 64) {} -}; - -class ModuleRIPEMD160 : public Module -{ - public: - RIProv mr; - ModuleRIPEMD160() : mr(this) - { - ServerInstance->Modules->AddService(mr); - } - - Version GetVersion() - { - return Version("Provides RIPEMD-160 hashing", VF_VENDOR); - } - -}; - -MODULE_INIT(ModuleRIPEMD160) - diff --git a/src/modules/m_rline.cpp b/src/modules/m_rline.cpp index d1ab5d9ba..bf6a64d84 100644 --- a/src/modules/m_rline.cpp +++ b/src/modules/m_rline.cpp @@ -20,10 +20,9 @@ */ -/* $ModDesc: RLINE: Regexp user banning. */ - #include "inspircd.h" -#include "m_regex.h" +#include "modules/regex.h" +#include "modules/stats.h" #include "xline.h" static bool ZlineOnMatch = false; @@ -58,28 +57,30 @@ class RLine : public XLine delete regex; } - bool Matches(User *u) + bool Matches(User* u) CXX11_OVERRIDE { - if (u->exempt) + LocalUser* lu = IS_LOCAL(u); + if (lu && lu->exempt) return false; - std::string compare = u->nick + "!" + u->ident + "@" + u->host + " " + u->fullname; - return regex->Matches(compare); + const std::string host = u->nick + "!" + u->ident + "@" + u->GetRealHost() + " " + u->GetRealName(); + const std::string ip = u->nick + "!" + u->ident + "@" + u->GetIPString() + " " + u->GetRealName(); + return (regex->Matches(host) || regex->Matches(ip)); } - bool Matches(const std::string &compare) + bool Matches(const std::string& compare) CXX11_OVERRIDE { return regex->Matches(compare); } - void Apply(User* u) + void Apply(User* u) CXX11_OVERRIDE { if (ZlineOnMatch) { ZLine* zl = new ZLine(ServerInstance->Time(), duration ? expiry - ServerInstance->Time() : 0, ServerInstance->Config->ServerName.c_str(), reason.c_str(), u->GetIPString()); if (ServerInstance->XLines->AddLine(zl, NULL)) { - std::string timestr = ServerInstance->TimeString(zl->expiry); + std::string timestr = InspIRCd::TimeString(zl->expiry); ServerInstance->SNO->WriteToSnoMask('x', "Z-line added due to R-line match on *@%s%s%s: %s", zl->ipaddr.c_str(), zl->duration ? " to expire on " : "", zl->duration ? timestr.c_str() : "", zl->reason.c_str()); added_zline = true; @@ -90,15 +91,9 @@ class RLine : public XLine DefaultApply(u, "R", false); } - void DisplayExpiry() - { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired R-line %s (set by %s %ld seconds ago)", - this->matchtext.c_str(), this->source.c_str(), (long int)(ServerInstance->Time() - this->set_time)); - } - - const char* Displayable() + const std::string& Displayable() CXX11_OVERRIDE { - return matchtext.c_str(); + return matchtext; } std::string matchtext; @@ -116,10 +111,10 @@ class RLineFactory : public XLineFactory RLineFactory(dynamic_reference<RegexFactory>& rx) : XLineFactory("R"), rxfactory(rx) { } - + /** Generate a RLine */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { if (!rxfactory) { @@ -129,10 +124,6 @@ class RLineFactory : public XLineFactory return new RLine(set_time, duration, source, reason, xline_specific_mask, rxfactory); } - - ~RLineFactory() - { - } }; /** Handle /RLINE @@ -149,14 +140,14 @@ class CommandRLine : public Command flags_needed = 'o'; this->syntax = "<regex> [<rline-duration>] :<reason>"; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { if (parameters.size() >= 3) { // Adding - XXX todo make this respect <insane> tag perhaps.. - long duration = ServerInstance->Duration(parameters[1]); + unsigned long duration = InspIRCd::Duration(parameters[1]); XLine *r = NULL; try @@ -165,7 +156,7 @@ class CommandRLine : public Command } catch (ModuleException &e) { - ServerInstance->SNO->WriteToSnoMask('a',"Could not add RLINE: %s", e.GetReason()); + ServerInstance->SNO->WriteToSnoMask('a',"Could not add RLINE: " + e.GetReason()); } if (r) @@ -179,7 +170,7 @@ class CommandRLine : public Command else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x', "%s added timed R-line for %s to expire on %s: %s", user->nick.c_str(), parameters[0].c_str(), timestr.c_str(), parameters[2].c_str()); } @@ -188,7 +179,7 @@ class CommandRLine : public Command else { delete r; - user->WriteServ("NOTICE %s :*** R-Line for %s already exists", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** R-Line for " + parameters[0] + " already exists"); } } } @@ -200,14 +191,14 @@ class CommandRLine : public Command } else { - user->WriteServ("NOTICE %s :*** R-Line %s not found in list, try /stats R.",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** R-Line " + parameters[0] + " not found in list, try /stats R."); } } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { if (IS_LOCAL(user)) return ROUTE_LOCALONLY; // spanningtree will send ADDLINE @@ -216,9 +207,8 @@ class CommandRLine : public Command } }; -class ModuleRLine : public Module +class ModuleRLine : public Module, public Stats::EventListener { - private: dynamic_reference<RegexFactory> rxfactory; RLineFactory f; CommandRLine r; @@ -228,34 +218,31 @@ class ModuleRLine : public Module public: ModuleRLine() - : rxfactory(this, "regex"), f(rxfactory), r(this, f) + : Stats::EventListener(this) + , rxfactory(this, "regex") + , f(rxfactory) + , r(this, f) , initing(true) { } - void init() + void init() CXX11_OVERRIDE { - OnRehash(NULL); - - ServerInstance->Modules->AddService(r); ServerInstance->XLines->RegisterFactory(&f); - - Implementation eventlist[] = { I_OnUserRegister, I_OnRehash, I_OnUserPostNick, I_OnStats, I_OnBackgroundTimer, I_OnUnloadModule }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual ~ModuleRLine() + ~ModuleRLine() { ServerInstance->XLines->DelAll("R"); ServerInstance->XLines->UnregisterFactory(&f); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("RLINE: Regexp user banning.", VF_COMMON | VF_VENDOR, rxfactory ? rxfactory->name : ""); } - ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { // Apply lines on user connect XLine *rl = ServerInstance->XLines->MatchesLine("R", user); @@ -269,7 +256,7 @@ class ModuleRLine : public Module return MOD_RES_PASSTHRU; } - virtual void OnRehash(User *user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("rline"); @@ -302,16 +289,16 @@ class ModuleRLine : public Module initing = false; } - virtual ModResult OnStats(char symbol, User* user, string_list &results) + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if (symbol != 'R') + if (stats.GetSymbol() != 'R') return MOD_RES_PASSTHRU; - ServerInstance->XLines->InvokeStats("R", 223, user, results); + ServerInstance->XLines->InvokeStats("R", 223, stats); return MOD_RES_DENY; } - virtual void OnUserPostNick(User *user, const std::string &oldnick) + void OnUserPostNick(User *user, const std::string &oldnick) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return; @@ -328,7 +315,7 @@ class ModuleRLine : public Module } } - virtual void OnBackgroundTimer(time_t curtime) + void OnBackgroundTimer(time_t curtime) CXX11_OVERRIDE { if (added_zline) { @@ -337,7 +324,7 @@ class ModuleRLine : public Module } } - void OnUnloadModule(Module* mod) + void OnUnloadModule(Module* mod) CXX11_OVERRIDE { // If the regex engine became unavailable or has changed, remove all rlines if (!rxfactory) @@ -351,7 +338,7 @@ class ModuleRLine : public Module } } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { Module* mod = ServerInstance->Modules->Find("m_cgiirc.so"); ServerInstance->Modules->SetPriority(this, I_OnUserRegister, PRIORITY_AFTER, mod); diff --git a/src/modules/m_rmode.cpp b/src/modules/m_rmode.cpp new file mode 100644 index 000000000..ce28630b4 --- /dev/null +++ b/src/modules/m_rmode.cpp @@ -0,0 +1,110 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "listmode.h" + +/** Handle /RMODE + */ +class CommandRMode : public Command +{ + public: + CommandRMode(Module* Creator) : Command(Creator,"RMODE", 2, 3) + { + allow_empty_last_param = false; + syntax = "<channel> <mode> [pattern]"; + } + + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + ModeHandler* mh; + Channel* chan = ServerInstance->FindChan(parameters[0]); + char modeletter = parameters[1][0]; + + if (chan == NULL) + { + user->WriteNotice("The channel " + parameters[0] + " does not exist."); + return CMD_FAILURE; + } + + mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL); + if (mh == NULL || parameters[1].size() > 1) + { + user->WriteNotice(parameters[1] + " is not a valid channel mode."); + return CMD_FAILURE; + } + + if (chan->GetPrefixValue(user) < mh->GetLevelRequired(false)) + { + user->WriteNotice("You do not have access to unset " + ConvToStr(modeletter) + " on " + chan->name + "."); + return CMD_FAILURE; + } + + std::string pattern = parameters.size() > 2 ? parameters[2] : "*"; + PrefixMode* pm; + ListModeBase* lm; + ListModeBase::ModeList* ml; + Modes::ChangeList changelist; + + if ((pm = mh->IsPrefixMode())) + { + // As user prefix modes don't have a GetList() method, let's iterate through the channel's users. + const Channel::MemberMap& users = chan->GetUsers(); + for (Channel::MemberMap::const_iterator it = users.begin(); it != users.end(); ++it) + { + if (!InspIRCd::Match(it->first->nick, pattern)) + continue; + if (it->second->HasMode(pm) && !((it->first == user) && (pm->GetPrefixRank() > VOICE_VALUE))) + changelist.push_remove(mh, it->first->nick); + } + } + else if ((lm = mh->IsListModeBase()) && ((ml = lm->GetList(chan)) != NULL)) + { + for (ListModeBase::ModeList::iterator it = ml->begin(); it != ml->end(); ++it) + { + if (!InspIRCd::Match(it->mask, pattern)) + continue; + changelist.push_remove(mh, it->mask); + } + } + else + { + if (chan->IsModeSet(mh)) + changelist.push_remove(mh); + } + + ServerInstance->Modes->Process(user, chan, NULL, changelist); + return CMD_SUCCESS; + } +}; + +class ModuleRMode : public Module +{ + CommandRMode cmd; + + public: + ModuleRMode() : cmd(this) { } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Allows glob-based removal of list modes", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleRMode) diff --git a/src/modules/m_sajoin.cpp b/src/modules/m_sajoin.cpp index 7ac465732..9aa8837e0 100644 --- a/src/modules/m_sajoin.cpp +++ b/src/modules/m_sajoin.cpp @@ -21,62 +21,71 @@ #include "inspircd.h" -/* $ModDesc: Provides command SAJOIN to allow opers to force-join users to channels */ - /** Handle /SAJOIN */ class CommandSajoin : public Command { public: - CommandSajoin(Module* Creator) : Command(Creator,"SAJOIN", 2) + CommandSajoin(Module* Creator) : Command(Creator,"SAJOIN", 1) { allow_empty_last_param = false; - flags_needed = 'o'; Penalty = 0; syntax = "<nick> <channel>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + flags_needed = 'o'; syntax = "[<nick>] <channel>[,<channel>]"; + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); + const unsigned int channelindex = (parameters.size() > 1) ? 1 : 0; + if (CommandParser::LoopCall(user, this, parameters, channelindex)) + return CMD_FAILURE; + + const std::string& channel = parameters[channelindex]; + const std::string& nickname = parameters.size() > 1 ? parameters[0] : user->nick; + + User* dest = ServerInstance->FindNick(nickname); if ((dest) && (dest->registered == REG_ALL)) { - if (ServerInstance->ULine(dest->server)) + if (user != dest && !user->HasPrivPermission("users/sajoin-others", false)) + { + user->WriteNotice("*** You are not allowed to /SAJOIN other users (the privilege users/sajoin-others is needed to /SAJOIN others)."); + return CMD_FAILURE; + } + + if (dest->server->IsULine()) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Cannot use an SA command on a u-lined client",user->nick.c_str()); + user->WriteNumeric(ERR_NOPRIVILEGES, "Cannot use an SA command on a u-lined client"); return CMD_FAILURE; } - if (IS_LOCAL(user) && !ServerInstance->IsChannel(parameters[1].c_str(), ServerInstance->Config->Limits.ChanMax)) + if (IS_LOCAL(user) && !ServerInstance->IsChannel(channel)) { /* we didn't need to check this for each character ;) */ - user->WriteServ("NOTICE "+user->nick+" :*** Invalid characters in channel name or name too long"); + user->WriteNotice("*** Invalid characters in channel name or name too long"); return CMD_FAILURE; } - /* For local users, we send the JoinUser which may create a channel and set its TS. + Channel* chan = ServerInstance->FindChan(channel); + if ((chan) && (chan->HasUser(dest))) + { + user->WriteRemoteNotice("*** " + dest->nick + " is already on " + channel); + return CMD_FAILURE; + } + + /* For local users, we call Channel::JoinUser which may create a channel and set its TS. * For non-local users, we just return CMD_SUCCESS, knowing this will propagate it where it needs to be - * and then that server will generate the users JOIN or FJOIN instead. + * and then that server will handle the command. */ - if (IS_LOCAL(dest)) + LocalUser* localuser = IS_LOCAL(dest); + if (localuser) { - Channel::JoinUser(dest, parameters[1].c_str(), true, "", false, ServerInstance->Time()); - /* Fix for dotslasher and w00t - if the join didnt succeed, return CMD_FAILURE so that it doesnt propagate */ - Channel* n = ServerInstance->FindChan(parameters[1]); - if (n) + chan = Channel::JoinUser(localuser, channel, true); + if (chan) { - if (n->HasUser(dest)) - { - ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SAJOIN to make "+dest->nick+" join "+parameters[1]); - return CMD_SUCCESS; - } - else - { - user->WriteServ("NOTICE "+user->nick+" :*** Could not join "+dest->nick+" to "+parameters[1]+" (User is probably banned, or blocking modes)"); - return CMD_FAILURE; - } + ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SAJOIN to make "+dest->nick+" join "+channel); + return CMD_SUCCESS; } else { - user->WriteServ("NOTICE "+user->nick+" :*** Could not join "+dest->nick+" to "+parameters[1]); + user->WriteNotice("*** Could not join "+dest->nick+" to "+channel); return CMD_FAILURE; } } @@ -87,17 +96,14 @@ class CommandSajoin : public Command } else { - user->WriteServ("NOTICE "+user->nick+" :*** No such nickname "+parameters[0]); + user->WriteNotice("*** No such nickname "+nickname); return CMD_FAILURE; } } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -110,20 +116,10 @@ class ModuleSajoin : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSajoin() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides command SAJOIN to allow opers to force-join users to channels", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleSajoin) diff --git a/src/modules/m_sakick.cpp b/src/modules/m_sakick.cpp index afca49e25..6a9e11fe7 100644 --- a/src/modules/m_sakick.cpp +++ b/src/modules/m_sakick.cpp @@ -20,8 +20,6 @@ #include "inspircd.h" -/* $ModDesc: Provides a SAKICK command */ - /** Handle /SAKICK */ class CommandSakick : public Command @@ -29,30 +27,28 @@ class CommandSakick : public Command public: CommandSakick(Module* Creator) : Command(Creator,"SAKICK", 2, 3) { - flags_needed = 'o'; Penalty = 0; syntax = "<channel> <nick> [reason]"; - TRANSLATE4(TR_TEXT, TR_NICK, TR_TEXT, TR_END); + flags_needed = 'o'; syntax = "<channel> <nick> [reason]"; + TRANSLATE3(TR_TEXT, TR_NICK, TR_TEXT); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* dest = ServerInstance->FindNick(parameters[1]); Channel* channel = ServerInstance->FindChan(parameters[0]); - const char* reason = ""; if ((dest) && (dest->registered == REG_ALL) && (channel)) { - if (parameters.size() > 2) - { - reason = parameters[2].c_str(); - } - else + const std::string& reason = (parameters.size() > 2) ? parameters[2] : dest->nick; + + if (dest->server->IsULine()) { - reason = dest->nick.c_str(); + user->WriteNumeric(ERR_NOPRIVILEGES, "Cannot use an SA command on a u-lined client"); + return CMD_FAILURE; } - if (ServerInstance->ULine(dest->server)) + if (!channel->HasUser(dest)) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Cannot use an SA command on a u-lined client", user->nick.c_str()); + user->WriteNotice("*** " + dest->nick + " is not on " + channel->name); return CMD_FAILURE; } @@ -62,31 +58,24 @@ class CommandSakick : public Command */ if (IS_LOCAL(dest)) { + // Target is on this server, kick them and send the snotice channel->KickUser(ServerInstance->FakeClient, dest, reason); - } - - if (IS_LOCAL(user)) - { - /* Locally issued command; send the snomasks */ - ServerInstance->SNO->WriteGlobalSno('a', user->nick + " SAKICKed " + dest->nick + " on " + parameters[0]); + ServerInstance->SNO->WriteGlobalSno('a', user->nick + " SAKICKed " + dest->nick + " on " + channel->name); } return CMD_SUCCESS; } else { - user->WriteServ("NOTICE %s :*** Invalid nickname or channel", user->nick.c_str()); + user->WriteNotice("*** Invalid nickname or channel"); } return CMD_FAILURE; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[1]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[1]); } }; @@ -99,21 +88,10 @@ class ModuleSakick : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSakick() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides a SAKICK command", VF_OPTCOMMON|VF_VENDOR); } - }; MODULE_INIT(ModuleSakick) - diff --git a/src/modules/m_samode.cpp b/src/modules/m_samode.cpp index ea2ae24ab..8f28a7e9c 100644 --- a/src/modules/m_samode.cpp +++ b/src/modules/m_samode.cpp @@ -20,42 +20,66 @@ */ -/* $ModDesc: Provides command SAMODE to allow opers to change modes on channels and users */ - #include "inspircd.h" /** Handle /SAMODE */ class CommandSamode : public Command { + bool logged; + public: bool active; CommandSamode(Module* Creator) : Command(Creator,"SAMODE", 2) { allow_empty_last_param = false; - flags_needed = 'o'; Penalty = 0; syntax = "<target> <modes> {<mode-parameters>}"; + flags_needed = 'o'; syntax = "<target> <modes> {<mode-parameters>}"; active = false; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { if (parameters[0].c_str()[0] != '#') { User* target = ServerInstance->FindNickOnly(parameters[0]); if ((!target) || (target->registered != REG_ALL)) { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); return CMD_FAILURE; } + + // Changing the modes of another user requires a special permission + if ((target != user) && (!user->HasPrivPermission("users/samode-usermodes", true))) + return CMD_FAILURE; } + // XXX: Make ModeParser clear LastParse + Modes::ChangeList emptychangelist; + ServerInstance->Modes->ProcessSingle(ServerInstance->FakeClient, NULL, ServerInstance->FakeClient, emptychangelist); + + logged = false; this->active = true; - ServerInstance->Parser->CallHandler("MODE", parameters, user); - if (ServerInstance->Modes->GetLastParse().length()) - ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " +ServerInstance->Modes->GetLastParse()); + ServerInstance->Parser.CallHandler("MODE", parameters, user); this->active = false; + + if (!logged) + { + // If we haven't logged anything yet then the client queried the list of a listmode + // (e.g. /SAMODE #chan b), which was handled internally by the MODE command handler. + // + // Viewing the modes of a user or a channel could also result in this, but + // that is not possible with /SAMODE because we require at least 2 parameters. + LogUsage(user, stdalgo::string::join(parameters)); + } + return CMD_SUCCESS; } + + void LogUsage(const User* user, const std::string& text) + { + logged = true; + ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + text); + } }; class ModuleSaMode : public Module @@ -67,32 +91,41 @@ class ModuleSaMode : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->Attach(I_OnPreMode, this); - } - - ~ModuleSaMode() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides command SAMODE to allow opers to change modes on channels and users", VF_VENDOR); } - ModResult OnPreMode(User* source,User* dest,Channel* channel, const std::vector<std::string>& parameters) + ModResult OnPreMode(User* source, User* dest, Channel* channel, Modes::ChangeList& modes) CXX11_OVERRIDE { if (cmd.active) return MOD_RES_ALLOW; return MOD_RES_PASSTHRU; } - void Prioritize() + void OnMode(User* user, User* destuser, Channel* destchan, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE + { + if (!cmd.active) + return; + + std::string logtext = (destuser ? destuser->nick : destchan->name); + logtext.push_back(' '); + logtext += ClientProtocol::Messages::Mode::ToModeLetters(modes); + + for (Modes::ChangeList::List::const_iterator i = modes.getlist().begin(); i != modes.getlist().end(); ++i) + { + const Modes::Change& item = *i; + if (!item.param.empty()) + logtext.append(1, ' ').append(item.param); + } + + cmd.LogUsage(user, logtext); + } + + void Prioritize() CXX11_OVERRIDE { Module *override = ServerInstance->Modules->Find("m_override.so"); - ServerInstance->Modules->SetPriority(this, I_OnPreMode, PRIORITY_BEFORE, &override); + ServerInstance->Modules->SetPriority(this, I_OnPreMode, PRIORITY_BEFORE, override); } }; diff --git a/src/modules/m_sanick.cpp b/src/modules/m_sanick.cpp index 4e4be77ae..4744ca1de 100644 --- a/src/modules/m_sanick.cpp +++ b/src/modules/m_sanick.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for SANICK command */ - /** Handle /SANICK */ class CommandSanick : public Command @@ -31,32 +29,32 @@ class CommandSanick : public Command CommandSanick(Module* Creator) : Command(Creator,"SANICK", 2) { allow_empty_last_param = false; - flags_needed = 'o'; Penalty = 0; syntax = "<nick> <new-nick>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + flags_needed = 'o'; syntax = "<nick> <new-nick>"; + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* target = ServerInstance->FindNick(parameters[0]); /* Do local sanity checks and bails */ if (IS_LOCAL(user)) { - if (target && ServerInstance->ULine(target->server)) + if (target && target->server->IsULine()) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Cannot use an SA command on a u-lined client",user->nick.c_str()); + user->WriteNumeric(ERR_NOPRIVILEGES, "Cannot use an SA command on a u-lined client"); return CMD_FAILURE; } if ((!target) || (target->registered != REG_ALL)) { - user->WriteServ("NOTICE %s :*** No such nickname: '%s'", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** No such nickname: '" + parameters[0] + "'"); return CMD_FAILURE; } - if (!ServerInstance->IsNick(parameters[1].c_str(), ServerInstance->Config->Limits.NickMax)) + if (!ServerInstance->IsNick(parameters[1])) { - user->WriteServ("NOTICE %s :*** Invalid nickname '%s'", user->nick.c_str(), parameters[1].c_str()); + user->WriteNotice("*** Invalid nickname '" + parameters[1] + "'"); return CMD_FAILURE; } } @@ -66,7 +64,7 @@ class CommandSanick : public Command { std::string oldnick = user->nick; std::string newnick = target->nick; - if (target->ChangeNick(parameters[1], true)) + if (target->ChangeNick(parameters[1])) { ServerInstance->SNO->WriteGlobalSno('a', oldnick+" used SANICK to change "+newnick+" to "+parameters[1]); } @@ -79,12 +77,9 @@ class CommandSanick : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -98,20 +93,10 @@ class ModuleSanick : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSanick() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for SANICK command", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleSanick) diff --git a/src/modules/m_sapart.cpp b/src/modules/m_sapart.cpp index 89256e0e4..0cd82fa15 100644 --- a/src/modules/m_sapart.cpp +++ b/src/modules/m_sapart.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides command SAPART to force-part users from a channel. */ - /** Handle /SAPART */ class CommandSapart : public Command @@ -30,12 +28,15 @@ class CommandSapart : public Command public: CommandSapart(Module* Creator) : Command(Creator,"SAPART", 2, 3) { - flags_needed = 'o'; Penalty = 0; syntax = "<nick> <channel> [reason]"; - TRANSLATE4(TR_NICK, TR_TEXT, TR_TEXT, TR_END); + flags_needed = 'o'; syntax = "<nick> <channel>[,<channel>] [reason]"; + TRANSLATE3(TR_NICK, TR_TEXT, TR_TEXT); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { + if (CommandParser::LoopCall(user, this, parameters, 1)) + return CMD_FAILURE; + User* dest = ServerInstance->FindNick(parameters[0]); Channel* channel = ServerInstance->FindChan(parameters[1]); std::string reason; @@ -45,9 +46,15 @@ class CommandSapart : public Command if (parameters.size() > 2) reason = parameters[2]; - if (ServerInstance->ULine(dest->server)) + if (dest->server->IsULine()) + { + user->WriteNumeric(ERR_NOPRIVILEGES, "Cannot use an SA command on a u-lined client"); + return CMD_FAILURE; + } + + if (!channel->HasUser(dest)) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Cannot use an SA command on a u-lined client",user->nick.c_str()); + user->WriteNotice("*** " + dest->nick + " is not on " + channel->name); return CMD_FAILURE; } @@ -58,44 +65,22 @@ class CommandSapart : public Command if (IS_LOCAL(dest)) { channel->PartUser(dest, reason); - - Channel* n = ServerInstance->FindChan(parameters[1]); - if (!n) - { - ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SAPART to make "+dest->nick+" part "+parameters[1]); - return CMD_SUCCESS; - } - else - { - if (!n->HasUser(dest)) - { - ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SAPART to make "+dest->nick+" part "+parameters[1]); - return CMD_SUCCESS; - } - else - { - user->WriteServ("NOTICE %s :*** Unable to make %s part %s",user->nick.c_str(), dest->nick.c_str(), parameters[1].c_str()); - return CMD_FAILURE; - } - } + ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SAPART to make "+dest->nick+" part "+channel->name); } return CMD_SUCCESS; } else { - user->WriteServ("NOTICE %s :*** Invalid nickname or channel", user->nick.c_str()); + user->WriteNotice("*** Invalid nickname or channel"); } return CMD_FAILURE; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -109,21 +94,10 @@ class ModuleSapart : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSapart() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides command SAPART to force-part users from a channel.", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleSapart) - diff --git a/src/modules/m_saquit.cpp b/src/modules/m_saquit.cpp index 909a026ab..9034016d2 100644 --- a/src/modules/m_saquit.cpp +++ b/src/modules/m_saquit.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for an SAQUIT command, exits user with a reason */ - /** Handle /SAQUIT */ class CommandSaquit : public Command @@ -30,25 +28,25 @@ class CommandSaquit : public Command public: CommandSaquit(Module* Creator) : Command(Creator, "SAQUIT", 2, 2) { - flags_needed = 'o'; Penalty = 0; syntax = "<nick> <reason>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + flags_needed = 'o'; syntax = "<nick> <reason>"; + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* dest = ServerInstance->FindNick(parameters[0]); - if ((dest) && (!IS_SERVER(dest)) && (dest->registered == REG_ALL)) + if ((dest) && (dest->registered == REG_ALL)) { - if (ServerInstance->ULine(dest->server)) + if (dest->server->IsULine()) { - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Cannot use an SA command on a u-lined client",user->nick.c_str()); + user->WriteNumeric(ERR_NOPRIVILEGES, "Cannot use an SA command on a u-lined client"); return CMD_FAILURE; } // Pass the command on, so the client's server can quit it properly. if (!IS_LOCAL(dest)) return CMD_SUCCESS; - + ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SAQUIT to make "+dest->nick+" quit with a reason of "+parameters[1]); ServerInstance->Users->QuitUser(dest, parameters[1]); @@ -56,17 +54,14 @@ class CommandSaquit : public Command } else { - user->WriteServ("NOTICE %s :*** Invalid nickname '%s'", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** Invalid nickname '" + parameters[0] + "'"); return CMD_FAILURE; } } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -79,20 +74,10 @@ class ModuleSaquit : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSaquit() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for an SAQUIT command, exits user with a reason", VF_OPTCOMMON | VF_VENDOR); } - }; MODULE_INIT(ModuleSaquit) diff --git a/src/modules/m_sasl.cpp b/src/modules/m_sasl.cpp index 0ef93ec5a..480f8f6db 100644 --- a/src/modules/m_sasl.cpp +++ b/src/modules/m_sasl.cpp @@ -19,26 +19,146 @@ #include "inspircd.h" -#include "m_cap.h" -#include "account.h" -#include "sasl.h" -#include "ssl.h" +#include "modules/cap.h" +#include "modules/account.h" +#include "modules/sasl.h" +#include "modules/ssl.h" +#include "modules/server.h" -/* $ModDesc: Provides support for IRC Authentication Layer (aka: atheme SASL) via AUTHENTICATE. */ +enum +{ + // From IRCv3 sasl-3.1 + RPL_SASLSUCCESS = 903, + ERR_SASLFAIL = 904, + ERR_SASLTOOLONG = 905, + ERR_SASLABORTED = 906, + RPL_SASLMECHS = 908 +}; + +static std::string sasl_target; + +class ServerTracker : public ServerEventListener +{ + bool online; + + void Update(const Server* server, bool linked) + { + if (sasl_target == "*") + return; + + if (InspIRCd::Match(server->GetName(), sasl_target)) + { + ServerInstance->Logs->Log(MODNAME, LOG_VERBOSE, "SASL target server \"%s\" %s", sasl_target.c_str(), (linked ? "came online" : "went offline")); + online = linked; + } + } + + void OnServerLink(const Server* server) CXX11_OVERRIDE + { + Update(server, true); + } + + void OnServerSplit(const Server* server) CXX11_OVERRIDE + { + Update(server, false); + } + + public: + ServerTracker(Module* mod) + : ServerEventListener(mod) + { + Reset(); + } + + void Reset() + { + if (sasl_target == "*") + { + online = true; + return; + } + + online = false; + + ProtocolInterface::ServerList servers; + ServerInstance->PI->GetServerList(servers); + for (ProtocolInterface::ServerList::const_iterator i = servers.begin(); i != servers.end(); ++i) + { + const ProtocolInterface::ServerInfo& server = *i; + if (InspIRCd::Match(server.servername, sasl_target)) + { + online = true; + break; + } + } + } + + bool IsOnline() const { return online; } +}; + +class SASLCap : public Cap::Capability +{ + std::string mechlist; + const ServerTracker& servertracker; + + bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE + { + // Requesting this cap is allowed anytime + if (adding) + return true; + + // But removing it can only be done when unregistered + return (user->registered != REG_ALL); + } + + bool OnList(LocalUser* user) CXX11_OVERRIDE + { + return servertracker.IsOnline(); + } + + const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE + { + return &mechlist; + } + + public: + SASLCap(Module* mod, const ServerTracker& tracker) + : Cap::Capability(mod, "sasl") + , servertracker(tracker) + { + } + + void SetMechlist(const std::string& newmechlist) + { + if (mechlist == newmechlist) + return; + + mechlist = newmechlist; + NotifyValueChange(); + } +}; enum SaslState { SASL_INIT, SASL_COMM, SASL_DONE }; enum SaslResult { SASL_OK, SASL_FAIL, SASL_ABORT }; -static std::string sasl_target = "*"; +static Events::ModuleEventProvider* saslevprov; -static void SendSASL(const parameterlist& params) +static void SendSASL(LocalUser* user, const std::string& agent, char mode, const std::vector<std::string>& parameters) { - if (!ServerInstance->PI->SendEncapsulatedData(params)) + CommandBase::Params params; + params.push_back(user->uuid); + params.push_back(agent); + params.push_back(ConvToStr(mode)); + params.insert(params.end(), parameters.begin(), parameters.end()); + + if (!ServerInstance->PI->SendEncapsulatedData(sasl_target, "SASL", params)) { - SASLFallback(NULL, params); + FOREACH_MOD_CUSTOM(*saslevprov, SASLEventListener, OnSASLAuth, (params)); } } +static ClientProtocol::EventProvider* g_protoev; + /** * Tracks SASL authentication state like charybdis does. --nenolod */ @@ -46,95 +166,35 @@ class SaslAuthenticator { private: std::string agent; - User *user; + LocalUser* user; SaslState state; SaslResult result; bool state_announced; - /* taken from m_services_account */ - static bool ReadCGIIRCExt(const char* extname, User* user, std::string& out) - { - ExtensionItem* wiext = ServerInstance->Extensions.GetItem(extname); - if (!wiext) - return false; - - if (wiext->creator->ModuleSourceFile != "m_cgiirc.so") - return false; - - StringExtItem* stringext = static_cast<StringExtItem*>(wiext); - std::string* addr = stringext->get(user); - if (!addr) - return false; - - out = *addr; - return true; - } - - void SendHostIP() { - std::string host, ip; - - if (!ReadCGIIRCExt("cgiirc_webirc_hostname", user, host)) - { - host = user->host; - } - if (!ReadCGIIRCExt("cgiirc_webirc_ip", user, ip)) - { - ip = user->GetIPString(); - } - else - { - /* IP addresses starting with a : on irc are a Bad Thing (tm) */ - if (ip.c_str()[0] == ':') - ip.insert(ip.begin(),1,'0'); - } - - parameterlist params; - params.push_back(sasl_target); - params.push_back("SASL"); - params.push_back(user->uuid); - params.push_back("*"); - params.push_back("H"); - params.push_back(host); - params.push_back(ip); - - LocalUser* lu = IS_LOCAL(user); - if (lu) - { - // NOTE: SaslAuthenticator instances are only created for local - // users so this parameter will always be appended. - SocketCertificateRequest req(&lu->eh, ServerInstance->Modules->Find("m_sasl.so")); - params.push_back(req.cert ? "S" : "P"); - } + std::vector<std::string> params; + params.push_back(user->GetRealHost()); + params.push_back(user->GetIPString()); + params.push_back(SSLIOHook::IsSSL(&user->eh) ? "S" : "P"); - SendSASL(params); + SendSASL(user, "*", 'H', params); } public: - SaslAuthenticator(User* user_, const std::string& method) + SaslAuthenticator(LocalUser* user_, const std::string& method) : user(user_), state(SASL_INIT), state_announced(false) { SendHostIP(); - parameterlist params; - params.push_back(sasl_target); - params.push_back("SASL"); - params.push_back(user->uuid); - params.push_back("*"); - params.push_back("S"); + std::vector<std::string> params; params.push_back(method); - if (method == "EXTERNAL" && IS_LOCAL(user_)) - { - SocketCertificateRequest req(&((LocalUser*)user_)->eh, ServerInstance->Modules->Find("m_sasl.so")); - std::string fp = req.GetFingerprint(); - - if (fp.size()) - params.push_back(fp); - } + const std::string fp = SSLClientCert::GetFingerprint(&user->eh); + if (fp.size()) + params.push_back(fp); - SendSASL(params); + SendSASL(user, "*", 'S', params); } SaslResult GetSaslResult(const std::string &result_) @@ -149,7 +209,7 @@ class SaslAuthenticator } /* checks for and deals with a state change. */ - SaslState ProcessInboundMessage(const std::vector<std::string> &msg) + SaslState ProcessInboundMessage(const CommandBase::Params& msg) { switch (this->state) { @@ -165,53 +225,47 @@ class SaslAuthenticator return this->state; if (msg[2] == "C") - this->user->Write("AUTHENTICATE %s", msg[3].c_str()); + { + ClientProtocol::Message authmsg("AUTHENTICATE"); + authmsg.PushParamRef(msg[3]); + + ClientProtocol::Event authevent(*g_protoev, authmsg); + LocalUser* const localuser = IS_LOCAL(user); + if (localuser) + localuser->Send(authevent); + } else if (msg[2] == "D") { this->state = SASL_DONE; this->result = this->GetSaslResult(msg[3]); } else if (msg[2] == "M") - this->user->WriteNumeric(908, "%s %s :are available SASL mechanisms", this->user->nick.c_str(), msg[3].c_str()); + this->user->WriteNumeric(RPL_SASLMECHS, msg[3], "are available SASL mechanisms"); else - ServerInstance->Logs->Log("m_sasl", DEFAULT, "Services sent an unknown SASL message \"%s\" \"%s\"", msg[2].c_str(), msg[3].c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Services sent an unknown SASL message \"%s\" \"%s\"", msg[2].c_str(), msg[3].c_str()); break; case SASL_DONE: break; default: - ServerInstance->Logs->Log("m_sasl", DEFAULT, "WTF: SaslState is not a known state (%d)", this->state); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WTF: SaslState is not a known state (%d)", this->state); break; } return this->state; } - void Abort(void) - { - this->state = SASL_DONE; - this->result = SASL_ABORT; - } - bool SendClientMessage(const std::vector<std::string>& parameters) { if (this->state != SASL_COMM) return true; - parameterlist params; - params.push_back(sasl_target); - params.push_back("SASL"); - params.push_back(this->user->uuid); - params.push_back(this->agent); - params.push_back("C"); - - params.insert(params.end(), parameters.begin(), parameters.end()); - - SendSASL(params); + SendSASL(this->user, this->agent, 'C', parameters); if (parameters[0].c_str()[0] == '*') { - this->Abort(); + this->state = SASL_DONE; + this->result = SASL_ABORT; return false; } @@ -226,13 +280,13 @@ class SaslAuthenticator switch (this->result) { case SASL_OK: - this->user->WriteNumeric(903, "%s :SASL authentication successful", this->user->nick.c_str()); + this->user->WriteNumeric(RPL_SASLSUCCESS, "SASL authentication successful"); break; case SASL_ABORT: - this->user->WriteNumeric(906, "%s :SASL authentication aborted", this->user->nick.c_str()); + this->user->WriteNumeric(ERR_SASLABORTED, "SASL authentication aborted"); break; case SASL_FAIL: - this->user->WriteNumeric(904, "%s :SASL authentication failed", this->user->nick.c_str()); + this->user->WriteNumeric(ERR_SASLFAIL, "SASL authentication failed"); break; default: break; @@ -242,29 +296,39 @@ class SaslAuthenticator } }; -class CommandAuthenticate : public Command +class CommandAuthenticate : public SplitCommand { + private: + // The maximum length of an AUTHENTICATE request. + static const size_t MAX_AUTHENTICATE_SIZE = 400; + public: SimpleExtItem<SaslAuthenticator>& authExt; - GenericCap& cap; - CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, GenericCap& Cap) - : Command(Creator, "AUTHENTICATE", 1), authExt(ext), cap(Cap) + Cap::Capability& cap; + CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, Cap::Capability& Cap) + : SplitCommand(Creator, "AUTHENTICATE", 1) + , authExt(ext) + , cap(Cap) { works_before_reg = true; allow_empty_last_param = false; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { - /* Only allow AUTHENTICATE on unregistered clients */ - if (user->registered != REG_ALL) { - if (!cap.ext.get(user)) + if (!cap.get(user)) return CMD_FAILURE; if (parameters[0].find(' ') != std::string::npos || parameters[0][0] == ':') return CMD_FAILURE; + if (parameters[0].length() > MAX_AUTHENTICATE_SIZE) + { + user->WriteNumeric(ERR_SASLTOOLONG, "SASL message too long"); + return CMD_FAILURE; + } + SaslAuthenticator *sasl = authExt.get(user); if (!sasl) authExt.set(user, new SaslAuthenticator(user, parameters[0])); @@ -287,12 +351,12 @@ class CommandSASL : public Command this->flags_needed = FLAG_SERVERONLY; // should not be called by users } - CmdResult Handle(const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - User* target = ServerInstance->FindNick(parameters[1]); - if ((!target) || (IS_SERVER(target))) + User* target = ServerInstance->FindUUID(parameters[1]); + if (!target) { - ServerInstance->Logs->Log("m_sasl", DEBUG,"User not found in sasl ENCAP event: %s", parameters[1].c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User not found in sasl ENCAP event: %s", parameters[1].c_str()); return CMD_FAILURE; } @@ -309,7 +373,7 @@ class CommandSASL : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } @@ -318,51 +382,52 @@ class CommandSASL : public Command class ModuleSASL : public Module { SimpleExtItem<SaslAuthenticator> authExt; - GenericCap cap; + ServerTracker servertracker; + SASLCap cap; CommandAuthenticate auth; CommandSASL sasl; + Events::ModuleEventProvider sasleventprov; + ClientProtocol::EventProvider protoev; + public: ModuleSASL() - : authExt("sasl_auth", this), cap(this, "sasl"), auth(this, authExt, cap), sasl(this, authExt) + : authExt("sasl_auth", ExtensionItem::EXT_USER, this) + , servertracker(this) + , cap(this, servertracker) + , auth(this, authExt, cap) + , sasl(this, authExt) + , sasleventprov(this, "event/sasl") + , protoev(this, auth.name) { + saslevprov = &sasleventprov; + g_protoev = &protoev; } - void init() + void init() CXX11_OVERRIDE { - OnRehash(NULL); - Implementation eventlist[] = { I_OnEvent, I_OnUserConnect, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - ServiceProvider* providelist[] = { &auth, &sasl, &authExt }; - ServerInstance->Modules->AddServices(providelist, 3); - if (!ServerInstance->Modules->Find("m_services_account.so") || !ServerInstance->Modules->Find("m_cap.so")) - ServerInstance->Logs->Log("m_sasl", DEFAULT, "WARNING: m_services_account.so and m_cap.so are not loaded! m_sasl.so will NOT function correctly until these two modules are loaded!"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: m_services_account and m_cap are not loaded! m_sasl will NOT function correctly until these two modules are loaded!"); } - void OnRehash(User*) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - sasl_target = ServerInstance->Config->ConfValue("sasl")->getString("target", "*"); - } + std::string target = ServerInstance->Config->ConfValue("sasl")->getString("target"); + if (target.empty()) + throw ModuleException("<sasl:target> must be set to the name of your services server!"); - void OnUserConnect(LocalUser *user) - { - SaslAuthenticator *sasl_ = authExt.get(user); - if (sasl_) - { - sasl_->Abort(); - authExt.unset(user); - } + sasl_target = target; + servertracker.Reset(); } - Version GetVersion() + void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string& extdata) CXX11_OVERRIDE { - return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR); + if ((target == NULL) && (extname == "saslmechlist")) + cap.SetMechlist(extdata); } - void OnEvent(Event &ev) + Version GetVersion() CXX11_OVERRIDE { - cap.HandleEvent(ev); + return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR); } }; diff --git a/src/modules/m_satopic.cpp b/src/modules/m_satopic.cpp index ae1c19d91..8c8629221 100644 --- a/src/modules/m_satopic.cpp +++ b/src/modules/m_satopic.cpp @@ -17,8 +17,6 @@ */ -/* $ModDesc: Provides a SATOPIC command */ - #include "inspircd.h" /** Handle /SATOPIC @@ -28,10 +26,10 @@ class CommandSATopic : public Command public: CommandSATopic(Module* Creator) : Command(Creator,"SATOPIC", 2, 2) { - flags_needed = 'o'; Penalty = 0; syntax = "<target> <topic>"; + flags_needed = 'o'; syntax = "<target> <topic>"; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { /* * Handles a SATOPIC request. Notifies all +s users. @@ -40,17 +38,21 @@ class CommandSATopic : public Command if(target) { - std::string newTopic = parameters[1]; + const std::string newTopic(parameters[1], 0, ServerInstance->Config->Limits.MaxTopic); + if (target->topic == newTopic) + { + user->WriteNotice(InspIRCd::Format("The topic on %s is already what you are trying to change it to.", target->name.c_str())); + return CMD_SUCCESS; + } - // 3rd parameter overrides access checks - target->SetTopic(user, newTopic, true); + target->SetTopic(user, newTopic, ServerInstance->Time(), NULL); ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SATOPIC on " + target->name + ", new topic: " + newTopic); return CMD_SUCCESS; } else { - user->WriteNumeric(401, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); return CMD_FAILURE; } } @@ -65,16 +67,7 @@ class ModuleSATopic : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSATopic() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides a SATOPIC command", VF_VENDOR); } diff --git a/src/modules/m_securelist.cpp b/src/modules/m_securelist.cpp index 5d11d27f7..aa14707b1 100644 --- a/src/modules/m_securelist.cpp +++ b/src/modules/m_securelist.cpp @@ -20,32 +20,21 @@ #include "inspircd.h" - -/* $ModDesc: Disallows /LIST for recently connected clients to hinder spam bots */ +#include "modules/account.h" class ModuleSecureList : public Module { - private: std::vector<std::string> allowlist; - time_t WaitTime; - public: - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnRehash, I_OnPreCommand, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } + bool exemptregistered; + unsigned int WaitTime; - virtual ~ModuleSecureList() - { - } - - virtual Version GetVersion() + public: + Version GetVersion() CXX11_OVERRIDE { return Version("Disallows /LIST for recently connected clients to hinder spam bots", VF_VENDOR); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { allowlist.clear(); @@ -53,7 +42,9 @@ class ModuleSecureList : public Module for (ConfigIter i = tags.first; i != tags.second; ++i) allowlist.push_back(i->second->getString("exception")); - WaitTime = ServerInstance->Config->ConfValue("securelist")->getInt("waittime", 60); + ConfigTag* tag = ServerInstance->Config->ConfValue("securelist"); + exemptregistered = tag->getBool("exemptregistered"); + WaitTime = tag->getDuration("waittime", 60, 1); } @@ -61,34 +52,38 @@ class ModuleSecureList : public Module * OnPreCommand() * Intercept the LIST command. */ - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { /* If the command doesnt appear to be valid, we dont want to mess with it. */ if (!validated) return MOD_RES_PASSTHRU; - if ((command == "LIST") && (ServerInstance->Time() < (user->signon+WaitTime)) && (!IS_OPER(user))) + if ((command == "LIST") && (ServerInstance->Time() < (user->signon+WaitTime)) && (!user->IsOper())) { /* Normally wouldnt be allowed here, are they exempt? */ for (std::vector<std::string>::iterator x = allowlist.begin(); x != allowlist.end(); x++) if (InspIRCd::Match(user->MakeHost(), *x, ascii_case_insensitive_map)) return MOD_RES_PASSTHRU; + const AccountExtItem* ext = GetAccountExtItem(); + if (exemptregistered && ext && ext->get(user)) + return MOD_RES_PASSTHRU; + /* Not exempt, BOOK EM DANNO! */ - user->WriteServ("NOTICE %s :*** You cannot list within the first %lu seconds of connecting. Please try again later.",user->nick.c_str(), (unsigned long) WaitTime); + user->WriteNotice("*** You cannot list within the first " + ConvToStr(WaitTime) + " seconds of connecting. Please try again later."); /* Some clients (e.g. mIRC, various java chat applets) muck up if they don't * receive these numerics whenever they send LIST, so give them an empty LIST to mull over. */ - user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str()); - user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str()); + user->WriteNumeric(RPL_LISTSTART, "Channel", "Users Name"); + user->WriteNumeric(RPL_LISTEND, "End of channel list."); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" SECURELIST"); + tokens["SECURELIST"]; } }; diff --git a/src/modules/m_seenicks.cpp b/src/modules/m_seenicks.cpp index 95872b5b2..bff3516f1 100644 --- a/src/modules/m_seenicks.cpp +++ b/src/modules/m_seenicks.cpp @@ -21,24 +21,20 @@ #include "inspircd.h" -/* $ModDesc: Provides support for seeing local and remote nickchanges via snomasks 'n' and 'N'. */ - class ModuleSeeNicks : public Module { public: - void init() + void init() CXX11_OVERRIDE { ServerInstance->SNO->EnableSnomask('n',"NICK"); - Implementation eventlist[] = { I_OnUserPostNick }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for seeing local and remote nickchanges via snomasks", VF_VENDOR); } - virtual void OnUserPostNick(User* user, const std::string &oldnick) + void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE { ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'n' : 'N',"User %s changed their nickname to %s", oldnick.c_str(), user->nick.c_str()); } diff --git a/src/modules/m_serverban.cpp b/src/modules/m_serverban.cpp index cf77ae9ba..f51d1d373 100644 --- a/src/modules/m_serverban.cpp +++ b/src/modules/m_serverban.cpp @@ -19,43 +19,28 @@ #include "inspircd.h" -/* $ModDesc: Implements extban +b s: - server name bans */ - class ModuleServerBan : public Module { - private: public: - void init() - { - Implementation eventlist[] = { I_OnCheckBan, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ~ModuleServerBan() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Extban 's' - server ban",VF_OPTCOMMON|VF_VENDOR); } - ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) + ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) CXX11_OVERRIDE { if ((mask.length() > 2) && (mask[0] == 's') && (mask[1] == ':')) { - if (InspIRCd::Match(user->server, mask.substr(2))) + if (InspIRCd::Match(user->server->GetName(), mask.substr(2))) return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('s'); + tokens["EXTBAN"].push_back('s'); } }; - MODULE_INIT(ModuleServerBan) - diff --git a/src/modules/m_services_account.cpp b/src/modules/m_services_account.cpp index 154968e9e..c3b84dbba 100644 --- a/src/modules/m_services_account.cpp +++ b/src/modules/m_services_account.cpp @@ -22,10 +22,26 @@ */ -/* $ModDesc: Provides support for ircu-style services accounts, including chmode +R, etc. */ - #include "inspircd.h" -#include "account.h" +#include "modules/account.h" +#include "modules/exemption.h" +#include "modules/whois.h" + +enum +{ + // From UnrealIRCd. + RPL_WHOISREGNICK = 307, + + // From ircu. + RPL_WHOISACCOUNT = 330, + + // From ircd-hybrid? + ERR_NEEDREGGEDNICK = 477, + + // From IRCv3 sasl-3.1. + RPL_LOGGEDIN = 900, + RPL_LOGGEDOUT = 901 +}; /** Channel mode +r - mark a channel as identified */ @@ -34,21 +50,21 @@ class Channel_r : public ModeHandler public: Channel_r(Module* Creator) : ModeHandler(Creator, "c_registered", 'r', PARAM_NONE, MODETYPE_CHANNEL) { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { // only a u-lined server may add or remove the +r mode. if (!IS_LOCAL(source)) { // Only change the mode if it's not redundant - if ((adding != channel->IsModeSet('r'))) + if ((adding != channel->IsModeSet(this))) { - channel->SetMode('r',adding); + channel->SetMode(this, adding); return MODEACTION_ALLOW; } } else { - source->WriteNumeric(500, "%s :Only a server may modify the +r channel mode", source->nick.c_str()); + source->WriteNumeric(ERR_NOPRIVILEGES, "Only a server may modify the +r channel mode"); } return MODEACTION_DENY; } @@ -62,128 +78,118 @@ class User_r : public ModeHandler public: User_r(Module* Creator) : ModeHandler(Creator, "u_registered", 'r', PARAM_NONE, MODETYPE_USER) { } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { if (!IS_LOCAL(source)) { - if ((adding != dest->IsModeSet('r'))) + if ((adding != dest->IsModeSet(this))) { - dest->SetMode('r',adding); + dest->SetMode(this, adding); return MODEACTION_ALLOW; } } else { - source->WriteNumeric(500, "%s :Only a server may modify the +r user mode", source->nick.c_str()); + source->WriteNumeric(ERR_NOPRIVILEGES, "Only a server may modify the +r user mode"); } return MODEACTION_DENY; } }; -/** Channel mode +R - unidentified users cannot join - */ -class AChannel_R : public SimpleChannelModeHandler -{ - public: - AChannel_R(Module* Creator) : SimpleChannelModeHandler(Creator, "reginvite", 'R') { } -}; - -/** User mode +R - unidentified users cannot message - */ -class AUser_R : public SimpleUserModeHandler +class AccountExtItemImpl : public AccountExtItem { - public: - AUser_R(Module* Creator) : SimpleUserModeHandler(Creator, "regdeaf", 'R') { } -}; + Events::ModuleEventProvider eventprov; -/** Channel mode +M - unidentified users cannot message channel - */ -class AChannel_M : public SimpleChannelModeHandler -{ public: - AChannel_M(Module* Creator) : SimpleChannelModeHandler(Creator, "regmoderated", 'M') { } -}; - -class ModuleServicesAccount : public Module -{ - AChannel_R m1; - AChannel_M m2; - AUser_R m3; - Channel_r m4; - User_r m5; - AccountExtItem accountname; - bool checking_ban; + AccountExtItemImpl(Module* mod) + : AccountExtItem("accountname", ExtensionItem::EXT_USER, mod) + , eventprov(mod, "event/account") + { + } - static bool ReadCGIIRCExt(const char* extname, User* user, const std::string*& out) + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE { - ExtensionItem* wiext = ServerInstance->Extensions.GetItem(extname); - if (!wiext) - return false; + User* user = static_cast<User*>(container); - if (wiext->creator->ModuleSourceFile != "m_cgiirc.so") - return false; + StringExtItem::unserialize(format, container, value); - StringExtItem* stringext = static_cast<StringExtItem*>(wiext); - std::string* addr = stringext->get(user); - if (!addr) - return false; + // If we are being reloaded then don't send the numeric or run the event + if (format == FORMAT_INTERNAL) + return; - out = addr; - return true; - } + if (IS_LOCAL(user)) + { + if (value.empty()) + { + // Logged out. + user->WriteNumeric(RPL_LOGGEDOUT, user->GetFullHost(), "You are now logged out"); + } + else + { + // Logged in. + user->WriteNumeric(RPL_LOGGEDIN, user->GetFullHost(), value, InspIRCd::Format("You are now logged in as %s", value.c_str())); + } + } - public: - ModuleServicesAccount() : m1(this), m2(this), m3(this), m4(this), m5(this), - accountname("accountname", this), checking_ban(false) - { + FOREACH_MOD_CUSTOM(eventprov, AccountEventListener, OnAccountChange, (user, value)); } +}; - void init() +class ModuleServicesAccount : public Module, public Whois::EventListener +{ + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler m1; + SimpleChannelModeHandler m2; + SimpleUserModeHandler m3; + Channel_r m4; + User_r m5; + AccountExtItemImpl accountname; + bool checking_ban; + public: + ModuleServicesAccount() + : Whois::EventListener(this) + , exemptionprov(this) + , m1(this, "reginvite", 'R') + , m2(this, "regmoderated", 'M') + , m3(this, "regdeaf", 'R') + , m4(this) + , m5(this) + , accountname(this) + , checking_ban(false) { - ServiceProvider* providerlist[] = { &m1, &m2, &m3, &m4, &m5, &accountname }; - ServerInstance->Modules->AddServices(providerlist, sizeof(providerlist)/sizeof(ServiceProvider*)); - Implementation eventlist[] = { I_OnWhois, I_OnUserPreMessage, I_OnUserPreNotice, I_OnUserPreJoin, I_OnCheckBan, - I_OnDecodeMetaData, I_On005Numeric, I_OnUserPostNick, I_OnSetConnectClass }; - - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - void On005Numeric(std::string &t) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('R'); - ServerInstance->AddExtBanChar('U'); + tokens["EXTBAN"].push_back('R'); + tokens["EXTBAN"].push_back('U'); } /* <- :twisted.oscnet.org 330 w00t2 w00t2 w00t :is logged in as */ - void OnWhois(User* source, User* dest) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - std::string *account = accountname.get(dest); + std::string* account = accountname.get(whois.GetTarget()); if (account) { - ServerInstance->SendWhoisLine(source, dest, 330, "%s %s %s :is logged in as", source->nick.c_str(), dest->nick.c_str(), account->c_str()); + whois.SendLine(RPL_WHOISACCOUNT, *account, "is logged in as"); } - if (dest->IsModeSet('r')) + if (whois.GetTarget()->IsModeSet(m5)) { /* user is registered */ - ServerInstance->SendWhoisLine(source, dest, 307, "%s %s :is a registered nick", source->nick.c_str(), dest->nick.c_str()); + whois.SendLine(RPL_WHOISREGNICK, "is a registered nick"); } } - void OnUserPostNick(User* user, const std::string &oldnick) + void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE { /* On nickchange, if they have +r, remove it */ - if (user->IsModeSet('r') && assign(user->nick) != oldnick) - { - std::vector<std::string> modechange; - modechange.push_back(user->nick); - modechange.push_back("-r"); - ServerInstance->SendMode(modechange, ServerInstance->FakeClient); - } + if ((user->IsModeSet(m5)) && (ServerInstance->FindNickOnly(oldnick) != user)) + m5.RemoveMode(user); } - ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; @@ -191,33 +197,33 @@ class ModuleServicesAccount : public Module std::string *account = accountname.get(user); bool is_registered = account && !account->empty(); - if (target_type == TYPE_CHANNEL) + if (target.type == MessageTarget::TYPE_CHANNEL) { - Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"regmoderated"); + Channel* c = target.Get<Channel>(); + ModResult res = CheckExemption::Call(exemptionprov, user, c, "regmoderated"); - if (c->IsModeSet('M') && !is_registered && res != MOD_RES_ALLOW) + if (c->IsModeSet(m2) && !is_registered && res != MOD_RES_ALLOW) { // user messaging a +M channel and is not registered - user->WriteNumeric(477, user->nick+" "+c->name+" :You need to be identified to a registered account to message this channel"); + user->WriteNumeric(ERR_NEEDREGGEDNICK, c->name, "You need to be identified to a registered account to message this channel"); return MOD_RES_DENY; } } - else if (target_type == TYPE_USER) + else if (target.type == MessageTarget::TYPE_USER) { - User* u = (User*)dest; + User* u = target.Get<User>(); - if (u->IsModeSet('R') && !is_registered) + if (u->IsModeSet(m3) && !is_registered) { // user messaging a +R user and is not registered - user->WriteNumeric(477, ""+ user->nick +" "+ u->nick +" :You need to be identified to a registered account to message this user"); + user->WriteNumeric(ERR_NEEDREGGEDNICK, u->nick, "You need to be identified to a registered account to message this user"); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - ModResult OnCheckBan(User* user, Channel* chan, const std::string& mask) + ModResult OnCheckBan(User* user, Channel* chan, const std::string& mask) CXX11_OVERRIDE { if (checking_ban) return MOD_RES_PASSTHRU; @@ -253,27 +259,19 @@ class ModuleServicesAccount : public Module return MOD_RES_PASSTHRU; } - ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - return OnUserPreMessage(user, dest, target_type, text, status, exempt_list); - } - - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) - { - if (!IS_LOCAL(user)) - return MOD_RES_PASSTHRU; - std::string *account = accountname.get(user); bool is_registered = account && !account->empty(); if (chan) { - if (chan->IsModeSet('R')) + if (chan->IsModeSet(m1)) { if (!is_registered) { // joining a +R channel and not identified - user->WriteNumeric(477, user->nick + " " + chan->name + " :You need to be identified to a registered account to join this channel"); + user->WriteNumeric(ERR_NEEDREGGEDNICK, chan->name, "You need to be identified to a registered account to join this channel"); return MOD_RES_DENY; } } @@ -281,56 +279,14 @@ class ModuleServicesAccount : public Module return MOD_RES_PASSTHRU; } - // Whenever the linking module receives metadata from another server and doesnt know what - // to do with it (of course, hence the 'meta') it calls this method, and it is up to each - // module in turn to figure out if this metadata key belongs to them, and what they want - // to do with it. - // In our case we're only sending a single string around, so we just construct a std::string. - // Some modules will probably get much more complex and format more detailed structs and classes - // in a textual way for sending over the link. - void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata) - { - User* dest = dynamic_cast<User*>(target); - // check if its our metadata key, and its associated with a user - if (dest && (extname == "accountname")) - { - std::string *account = accountname.get(dest); - if (account && !account->empty()) - { - trim(*account); - - if (IS_LOCAL(dest)) - { - const std::string* host = &dest->dhost; - if (dest->registered != REG_ALL) - { - if (!ReadCGIIRCExt("cgiirc_webirc_hostname", dest, host)) - { - ReadCGIIRCExt("cgiirc_webirc_ip", dest, host); - } - } - - dest->WriteNumeric(900, "%s %s!%s@%s %s :You are now logged in as %s", - dest->nick.c_str(), dest->nick.c_str(), dest->ident.c_str(), host->c_str(), account->c_str(), account->c_str()); - } - - AccountEvent(this, dest, *account).Send(); - } - else - { - AccountEvent(this, dest, "").Send(); - } - } - } - - ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { if (myclass->config->getBool("requireaccount") && !accountname.get(user)) return MOD_RES_DENY; return MOD_RES_PASSTHRU; } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for ircu-style services accounts, including chmode +R, etc.",VF_OPTCOMMON|VF_VENDOR); } diff --git a/src/modules/m_servprotect.cpp b/src/modules/m_servprotect.cpp index b4f2b5bbd..a5d7a82e4 100644 --- a/src/modules/m_servprotect.cpp +++ b/src/modules/m_servprotect.cpp @@ -20,8 +20,16 @@ #include "inspircd.h" +#include "modules/whois.h" -/* $ModDesc: Provides usermode +k to protect services from kicks, kills and mode changes. */ +enum +{ + // From AustHex. + RPL_WHOISSERVICE = 310, + + // From UnrealIRCd. + ERR_KILLDENY = 485 +}; /** Handles user mode +k */ @@ -30,7 +38,7 @@ class ServProtectMode : public ModeHandler public: ServProtectMode(Module* Creator) : ModeHandler(Creator, "servprotect", 'k', PARAM_NONE, MODETYPE_USER) { oper = true; } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { /* Because this returns MODEACTION_DENY all the time, there is only ONE * way to add this mode and that is at client introduction in the UID command, @@ -44,47 +52,41 @@ class ServProtectMode : public ModeHandler } }; -class ModuleServProtectMode : public Module +class ModuleServProtectMode : public Module, public Whois::EventListener, public Whois::LineEventListener { ServProtectMode bm; public: ModuleServProtectMode() - : bm(this) - { - } - - void init() + : Whois::EventListener(this) + , Whois::LineEventListener(this) + , bm(this) { - ServerInstance->Modules->AddService(bm); - Implementation eventlist[] = { I_OnWhois, I_OnKill, I_OnWhoisLine, I_OnRawMode, I_OnUserPreKick }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - - ~ModuleServProtectMode() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides usermode +k to protect services from kicks, kills, and mode changes.", VF_VENDOR); } - void OnWhois(User* src, User* dst) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - if (dst->IsModeSet('k')) + if (whois.GetTarget()->IsModeSet(bm)) { - ServerInstance->SendWhoisLine(src, dst, 310, src->nick+" "+dst->nick+" :is an "+ServerInstance->Config->Network+" Service"); + whois.SendLine(RPL_WHOISSERVICE, "is a Network Service on " + ServerInstance->Config->Network); } } - ModResult OnRawMode(User* user, Channel* chan, const char mode, const std::string ¶m, bool adding, int pcnt) + ModResult OnRawMode(User* user, Channel* chan, ModeHandler* mh, const std::string& param, bool adding) CXX11_OVERRIDE { /* Check that the mode is not a server mode, it is being removed, the user making the change is local, there is a parameter, * and the user making the change is not a uline */ - if (!adding && chan && IS_LOCAL(user) && !param.empty() && !ServerInstance->ULine(user->server)) + if (!adding && chan && IS_LOCAL(user) && !param.empty()) { + const PrefixMode* const pm = mh->IsPrefixMode(); + if (!pm) + return MOD_RES_PASSTHRU; + /* Check if the parameter is a valid nick/uuid */ User *u = ServerInstance->FindNick(param); @@ -95,10 +97,10 @@ class ModuleServProtectMode : public Module * This includes any prefix permission mode, even those registered in other modules, e.g. +qaohv. Using ::ModeString() * here means that the number of modes is restricted to only modes the user has, limiting it to as short a loop as possible. */ - if (u->IsModeSet('k') && memb && memb->modes.find(mode) != std::string::npos) + if ((u->IsModeSet(bm)) && (memb) && (memb->HasMode(pm))) { /* BZZZT, Denied! */ - user->WriteNumeric(482, "%s %s :You are not permitted to remove privileges from %s services", user->nick.c_str(), chan->name.c_str(), ServerInstance->Config->Network.c_str()); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, InspIRCd::Format("You are not permitted to remove privileges from %s services", ServerInstance->Config->Network.c_str())); return MOD_RES_DENY; } } @@ -107,37 +109,35 @@ class ModuleServProtectMode : public Module return MOD_RES_PASSTHRU; } - ModResult OnKill(User* src, User* dst, const std::string &reason) + ModResult OnKill(User* src, User* dst, const std::string &reason) CXX11_OVERRIDE { if (src == NULL) return MOD_RES_PASSTHRU; - if (dst->IsModeSet('k')) + if (dst->IsModeSet(bm)) { - src->WriteNumeric(485, "%s :You are not permitted to kill %s services!", src->nick.c_str(), ServerInstance->Config->Network.c_str()); + src->WriteNumeric(ERR_KILLDENY, InspIRCd::Format("You are not permitted to kill %s services!", ServerInstance->Config->Network.c_str())); ServerInstance->SNO->WriteGlobalSno('a', src->nick+" tried to kill service "+dst->nick+" ("+reason+")"); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - ModResult OnUserPreKick(User *src, Membership* memb, const std::string &reason) + ModResult OnUserPreKick(User *src, Membership* memb, const std::string &reason) CXX11_OVERRIDE { - if (memb->user->IsModeSet('k')) + if (memb->user->IsModeSet(bm)) { - src->WriteNumeric(484, "%s %s :You are not permitted to kick services", - src->nick.c_str(), memb->chan->name.c_str()); + src->WriteNumeric(ERR_RESTRICTED, memb->chan->name, "You are not permitted to kick services"); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - ModResult OnWhoisLine(User* src, User* dst, int &numeric, std::string &text) + ModResult OnWhoisLine(Whois::Context& whois, Numeric::Numeric& numeric) CXX11_OVERRIDE { - return ((src != dst) && (numeric == 319) && dst->IsModeSet('k')) ? MOD_RES_DENY : MOD_RES_PASSTHRU; + return ((numeric.GetNumeric() == 319) && whois.GetTarget()->IsModeSet(bm)) ? MOD_RES_DENY : MOD_RES_PASSTHRU; } }; - MODULE_INIT(ModuleServProtectMode) diff --git a/src/modules/m_sethost.cpp b/src/modules/m_sethost.cpp index 2ef0c0548..87eed4022 100644 --- a/src/modules/m_sethost.cpp +++ b/src/modules/m_sethost.cpp @@ -21,43 +21,40 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the SETHOST command */ - /** Handle /SETHOST */ class CommandSethost : public Command { - private: - char* hostmap; public: - CommandSethost(Module* Creator, char* hmap) : Command(Creator,"SETHOST", 1), hostmap(hmap) + std::bitset<UCHAR_MAX> hostmap; + + CommandSethost(Module* Creator) + : Command(Creator,"SETHOST", 1) { allow_empty_last_param = false; flags_needed = 'o'; syntax = "<new-hostname>"; - TRANSLATE2(TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - size_t len = 0; - for (std::string::const_iterator x = parameters[0].begin(); x != parameters[0].end(); x++, len++) + if (parameters[0].length() > ServerInstance->Config->Limits.MaxHost) { - if (!hostmap[(const unsigned char)*x]) - { - user->WriteServ("NOTICE "+user->nick+" :*** SETHOST: Invalid characters in hostname"); - return CMD_FAILURE; - } + user->WriteNotice("*** SETHOST: Host too long"); + return CMD_FAILURE; } - if (len > 64) + for (std::string::const_iterator x = parameters[0].begin(); x != parameters[0].end(); x++) { - user->WriteServ("NOTICE %s :*** SETHOST: Host too long",user->nick.c_str()); - return CMD_FAILURE; + if (!hostmap.test(static_cast<unsigned char>(*x))) + { + user->WriteNotice("*** SETHOST: Invalid characters in hostname"); + return CMD_FAILURE; + } } - if (user->ChangeDisplayedHost(parameters[0].c_str())) + if (user->ChangeDisplayedHost(parameters[0])) { - ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SETHOST to change their displayed host to "+user->dhost); + ServerInstance->SNO->WriteGlobalSno('a', user->nick+" used SETHOST to change their displayed host to "+user->GetDisplayedHost()); return CMD_SUCCESS; } @@ -69,39 +66,26 @@ class CommandSethost : public Command class ModuleSetHost : public Module { CommandSethost cmd; - char hostmap[256]; + public: ModuleSetHost() - : cmd(this, hostmap) - { - } - - void init() + : cmd(this) { - OnRehash(NULL); - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { std::string hmap = ServerInstance->Config->ConfValue("hostname")->getString("charmap", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_/0123456789"); - memset(hostmap, 0, sizeof(hostmap)); + cmd.hostmap.reset(); for (std::string::iterator n = hmap.begin(); n != hmap.end(); n++) - hostmap[(unsigned char)*n] = 1; + cmd.hostmap.set(static_cast<unsigned char>(*n)); } - virtual ~ModuleSetHost() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the SETHOST command", VF_VENDOR); } - }; MODULE_INIT(ModuleSetHost) diff --git a/src/modules/m_setident.cpp b/src/modules/m_setident.cpp index f63be1381..11ce8f015 100644 --- a/src/modules/m_setident.cpp +++ b/src/modules/m_setident.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the SETIDENT command */ - /** Handle /SETIDENT */ class CommandSetident : public Command @@ -33,31 +31,29 @@ class CommandSetident : public Command { allow_empty_last_param = false; flags_needed = 'o'; syntax = "<new-ident>"; - TRANSLATE2(TR_TEXT, TR_END); } - CmdResult Handle(const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { if (parameters[0].size() > ServerInstance->Config->Limits.IdentMax) { - user->WriteServ("NOTICE %s :*** SETIDENT: Ident is too long", user->nick.c_str()); + user->WriteNotice("*** SETIDENT: Ident is too long"); return CMD_FAILURE; } - if (!ServerInstance->IsIdent(parameters[0].c_str())) + if (!ServerInstance->IsIdent(parameters[0])) { - user->WriteServ("NOTICE %s :*** SETIDENT: Invalid characters in ident", user->nick.c_str()); + user->WriteNotice("*** SETIDENT: Invalid characters in ident"); return CMD_FAILURE; } - user->ChangeIdent(parameters[0].c_str()); + user->ChangeIdent(parameters[0]); ServerInstance->SNO->WriteGlobalSno('a', "%s used SETIDENT to change their ident to '%s'", user->nick.c_str(), user->ident.c_str()); return CMD_SUCCESS; } }; - class ModuleSetIdent : public Module { CommandSetident cmd; @@ -67,21 +63,10 @@ class ModuleSetIdent : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSetIdent() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the SETIDENT command", VF_VENDOR); } - }; - MODULE_INIT(ModuleSetIdent) diff --git a/src/modules/m_setidle.cpp b/src/modules/m_setidle.cpp index fdb29d14f..37984030b 100644 --- a/src/modules/m_setidle.cpp +++ b/src/modules/m_setidle.cpp @@ -21,25 +21,29 @@ #include "inspircd.h" -/* $ModDesc: Allows opers to set their idle time */ +enum +{ + // InspIRCd-specific. + ERR_INVALIDIDLETIME = 948, + RPL_IDLETIMESET = 944 +}; /** Handle /SETIDLE */ -class CommandSetidle : public Command +class CommandSetidle : public SplitCommand { public: - CommandSetidle(Module* Creator) : Command(Creator,"SETIDLE", 1) + CommandSetidle(Module* Creator) : SplitCommand(Creator,"SETIDLE", 1) { flags_needed = 'o'; syntax = "<duration>"; - TRANSLATE2(TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { - time_t idle = ServerInstance->Duration(parameters[0]); + int idle = InspIRCd::Duration(parameters[0]); if (idle < 1) { - user->WriteNumeric(948, "%s :Invalid idle time.",user->nick.c_str()); + user->WriteNumeric(ERR_INVALIDIDLETIME, "Invalid idle time."); return CMD_FAILURE; } user->idle_lastmsg = (ServerInstance->Time() - idle); @@ -47,7 +51,7 @@ class CommandSetidle : public Command if (user->signon > user->idle_lastmsg) user->signon = user->idle_lastmsg; ServerInstance->SNO->WriteToSnoMask('a', user->nick+" used SETIDLE to set their idle time to "+ConvToStr(idle)+" seconds"); - user->WriteNumeric(944, "%s :Idle time set.",user->nick.c_str()); + user->WriteNumeric(RPL_IDLETIMESET, "Idle time set."); return CMD_SUCCESS; } @@ -63,16 +67,7 @@ class ModuleSetIdle : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSetIdle() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows opers to set their idle time", VF_VENDOR); } diff --git a/src/modules/m_setname.cpp b/src/modules/m_setname.cpp index d0610853b..efe7028b7 100644 --- a/src/modules/m_setname.cpp +++ b/src/modules/m_setname.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the SETNAME command */ - class CommandSetname : public Command @@ -31,21 +29,20 @@ class CommandSetname : public Command CommandSetname(Module* Creator) : Command(Creator,"SETNAME", 1, 1) { allow_empty_last_param = false; - syntax = "<new-gecos>"; - TRANSLATE2(TR_TEXT, TR_END); + syntax = "<new real name>"; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (parameters[0].size() > ServerInstance->Config->Limits.MaxGecos) + if (parameters[0].size() > ServerInstance->Config->Limits.MaxReal) { - user->WriteServ("NOTICE %s :*** SETNAME: GECOS too long", user->nick.c_str()); + user->WriteNotice("*** SETNAME: Real name is too long"); return CMD_FAILURE; } - if (user->ChangeName(parameters[0].c_str())) + if (user->ChangeRealName(parameters[0])) { - ServerInstance->SNO->WriteGlobalSno('a', "%s used SETNAME to change their GECOS to '%s'", user->nick.c_str(), parameters[0].c_str()); + ServerInstance->SNO->WriteGlobalSno('a', "%s used SETNAME to change their real name to '%s'", user->nick.c_str(), parameters[0].c_str()); } return CMD_SUCCESS; @@ -62,16 +59,7 @@ class ModuleSetName : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleSetName() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the SETNAME command", VF_VENDOR); } diff --git a/src/modules/m_sha1.cpp b/src/modules/m_sha1.cpp new file mode 100644 index 000000000..561a7b6cb --- /dev/null +++ b/src/modules/m_sha1.cpp @@ -0,0 +1,199 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain +*/ + +#include "inspircd.h" +#include "modules/hash.h" + +union CHAR64LONG16 +{ + unsigned char c[64]; + uint32_t l[16]; +}; + +inline static uint32_t rol(uint32_t value, uint32_t bits) { return (value << bits) | (value >> (32 - bits)); } + +// blk0() and blk() perform the initial expand. +// I got the idea of expanding during the round function from SSLeay +static bool big_endian; +inline static uint32_t blk0(CHAR64LONG16& block, uint32_t i) +{ + if (big_endian) + return block.l[i]; + else + return block.l[i] = (rol(block.l[i], 24) & 0xFF00FF00) | (rol(block.l[i], 8) & 0x00FF00FF); +} +inline static uint32_t blk(CHAR64LONG16 &block, uint32_t i) { return block.l[i & 15] = rol(block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15] ^ block.l[(i + 2) & 15] ^ block.l[i & 15],1); } + +// (R0+R1), R2, R3, R4 are the different operations used in SHA1 +inline static void R0(CHAR64LONG16& block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); w = rol(w, 30); } +inline static void R1(CHAR64LONG16& block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + rol(v, 5); w = rol(w, 30); } +inline static void R2(CHAR64LONG16& block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + rol(v, 5); w = rol(w, 30); } +inline static void R3(CHAR64LONG16& block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + rol(v, 5); w = rol(w, 30); } +inline static void R4(CHAR64LONG16& block, uint32_t v, uint32_t &w, uint32_t x, uint32_t y, uint32_t &z, uint32_t i) { z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + rol(v, 5); w = rol(w, 30); } + +static const uint32_t sha1_iv[5] = +{ + 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 +}; + +class SHA1Context +{ + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; + unsigned char digest[20]; + + void Transform(const unsigned char buf[64]) + { + uint32_t a, b, c, d, e; + + CHAR64LONG16 block; + memcpy(block.c, buf, 64); + + // Copy state[] to working vars + a = this->state[0]; + b = this->state[1]; + c = this->state[2]; + d = this->state[3]; + e = this->state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(block, a, b, c, d, e, 0); R0(block, e, a, b, c, d, 1); R0(block, d, e, a, b, c, 2); R0(block, c, d, e, a, b, 3); + R0(block, b, c, d, e, a, 4); R0(block, a, b, c, d, e, 5); R0(block, e, a, b, c, d, 6); R0(block, d, e, a, b, c, 7); + R0(block, c, d, e, a, b, 8); R0(block, b, c, d, e, a, 9); R0(block, a, b, c, d, e, 10); R0(block, e, a, b, c, d, 11); + R0(block, d, e, a, b, c, 12); R0(block, c, d, e, a, b, 13); R0(block, b, c, d, e, a, 14); R0(block, a, b, c, d, e, 15); + R1(block, e, a, b, c, d, 16); R1(block, d, e, a, b, c, 17); R1(block, c, d, e, a, b, 18); R1(block, b, c, d, e, a, 19); + R2(block, a, b, c, d, e, 20); R2(block, e, a, b, c, d, 21); R2(block, d, e, a, b, c, 22); R2(block, c, d, e, a, b, 23); + R2(block, b, c, d, e, a, 24); R2(block, a, b, c, d, e, 25); R2(block, e, a, b, c, d, 26); R2(block, d, e, a, b, c, 27); + R2(block, c, d, e, a, b, 28); R2(block, b, c, d, e, a, 29); R2(block, a, b, c, d, e, 30); R2(block, e, a, b, c, d, 31); + R2(block, d, e, a, b, c, 32); R2(block, c, d, e, a, b, 33); R2(block, b, c, d, e, a, 34); R2(block, a, b, c, d, e, 35); + R2(block, e, a, b, c, d, 36); R2(block, d, e, a, b, c, 37); R2(block, c, d, e, a, b, 38); R2(block, b, c, d, e, a, 39); + R3(block, a, b, c, d, e, 40); R3(block, e, a, b, c, d, 41); R3(block, d, e, a, b, c, 42); R3(block, c, d, e, a, b, 43); + R3(block, b, c, d, e, a, 44); R3(block, a, b, c, d, e, 45); R3(block, e, a, b, c, d, 46); R3(block, d, e, a, b, c, 47); + R3(block, c, d, e, a, b, 48); R3(block, b, c, d, e, a, 49); R3(block, a, b, c, d, e, 50); R3(block, e, a, b, c, d, 51); + R3(block, d, e, a, b, c, 52); R3(block, c, d, e, a, b, 53); R3(block, b, c, d, e, a, 54); R3(block, a, b, c, d, e, 55); + R3(block, e, a, b, c, d, 56); R3(block, d, e, a, b, c, 57); R3(block, c, d, e, a, b, 58); R3(block, b, c, d, e, a, 59); + R4(block, a, b, c, d, e, 60); R4(block, e, a, b, c, d, 61); R4(block, d, e, a, b, c, 62); R4(block, c, d, e, a, b, 63); + R4(block, b, c, d, e, a, 64); R4(block, a, b, c, d, e, 65); R4(block, e, a, b, c, d, 66); R4(block, d, e, a, b, c, 67); + R4(block, c, d, e, a, b, 68); R4(block, b, c, d, e, a, 69); R4(block, a, b, c, d, e, 70); R4(block, e, a, b, c, d, 71); + R4(block, d, e, a, b, c, 72); R4(block, c, d, e, a, b, 73); R4(block, b, c, d, e, a, 74); R4(block, a, b, c, d, e, 75); + R4(block, e, a, b, c, d, 76); R4(block, d, e, a, b, c, 77); R4(block, c, d, e, a, b, 78); R4(block, b, c, d, e, a, 79); + // Add the working vars back into state[] + this->state[0] += a; + this->state[1] += b; + this->state[2] += c; + this->state[3] += d; + this->state[4] += e; + } + + public: + SHA1Context() + { + for (int i = 0; i < 5; ++i) + this->state[i] = sha1_iv[i]; + + this->count[0] = this->count[1] = 0; + memset(this->buffer, 0, sizeof(this->buffer)); + memset(this->digest, 0, sizeof(this->digest)); + } + + void Update(const unsigned char* data, size_t len) + { + uint32_t i, j; + + j = (this->count[0] >> 3) & 63; + if ((this->count[0] += len << 3) < (len << 3)) + ++this->count[1]; + this->count[1] += len >> 29; + if (j + len > 63) + { + memcpy(&this->buffer[j], data, (i = 64 - j)); + this->Transform(this->buffer); + for (; i + 63 < len; i += 64) + this->Transform(&data[i]); + j = 0; + } + else + i = 0; + memcpy(&this->buffer[j], &data[i], len - i); + } + + void Finalize() + { + uint32_t i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; ++i) + finalcount[i] = static_cast<unsigned char>((this->count[i >= 4 ? 0 : 1] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + this->Update(reinterpret_cast<const unsigned char *>("\200"), 1); + while ((this->count[0] & 504) != 448) + this->Update(reinterpret_cast<const unsigned char *>("\0"), 1); + this->Update(finalcount, 8); // Should cause a SHA1Transform() + for (i = 0; i < 20; ++i) + this->digest[i] = static_cast<unsigned char>((this->state[i>>2] >> ((3 - (i & 3)) * 8)) & 255); + + this->Transform(this->buffer); + } + + std::string GetRaw() const + { + return std::string((const char*)digest, sizeof(digest)); + } +}; + +class SHA1HashProvider : public HashProvider +{ + public: + SHA1HashProvider(Module* mod) + : HashProvider(mod, "hash/sha1", 20, 64) + { + } + + std::string GenerateRaw(const std::string& data) CXX11_OVERRIDE + { + SHA1Context ctx; + ctx.Update(reinterpret_cast<const unsigned char*>(data.data()), data.length()); + ctx.Finalize(); + return ctx.GetRaw(); + } +}; + +class ModuleSHA1 : public Module +{ + SHA1HashProvider sha1; + + public: + ModuleSHA1() + : sha1(this) + { + big_endian = (htonl(1337) == 1337); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Implements SHA-1 hashing", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleSHA1) diff --git a/src/modules/m_sha256.cpp b/src/modules/m_sha256.cpp index 86970968a..d905ab9b4 100644 --- a/src/modules/m_sha256.cpp +++ b/src/modules/m_sha256.cpp @@ -56,17 +56,8 @@ * SUCH DAMAGE. */ -/* $ModDesc: Allows for SHA-256 encrypted oper passwords */ - #include "inspircd.h" -#ifdef HAS_STDINT -#include <stdint.h> -#endif -#include "hash.h" - -#ifndef HAS_STDINT -typedef unsigned int uint32_t; -#endif +#include "modules/hash.h" #define SHA256_DIGEST_SIZE (256 / 8) #define SHA256_BLOCK_SIZE (512 / 8) @@ -256,19 +247,14 @@ class HashSHA256 : public HashProvider } public: - std::string sum(const std::string& data) + std::string GenerateRaw(const std::string& data) CXX11_OVERRIDE { unsigned char bytes[SHA256_DIGEST_SIZE]; SHA256(data.data(), bytes, data.length()); return std::string((char*)bytes, SHA256_DIGEST_SIZE); } - std::string sumIV(unsigned int* IV, const char* HexMap, const std::string &sdata) - { - return ""; - } - - HashSHA256(Module* parent) : HashProvider(parent, "hash/sha256", 32, 64) {} + HashSHA256(Module* parent) : HashProvider(parent, "sha256", 32, 64) {} }; class ModuleSHA256 : public Module @@ -277,10 +263,9 @@ class ModuleSHA256 : public Module public: ModuleSHA256() : sha(this) { - ServerInstance->Modules->AddService(sha); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements SHA-256 hashing", VF_VENDOR); } diff --git a/src/modules/m_showfile.cpp b/src/modules/m_showfile.cpp new file mode 100644 index 000000000..99c545140 --- /dev/null +++ b/src/modules/m_showfile.cpp @@ -0,0 +1,179 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +enum +{ + // From UnrealIRCd. + RPL_RULES = 232, + RPL_RULESTART = 308, + RPL_RULESEND = 309, + ERR_NORULES = 434 +}; + +class CommandShowFile : public Command +{ + enum Method + { + SF_MSG, + SF_NOTICE, + SF_NUMERIC + }; + + std::string introtext; + std::string endtext; + unsigned int intronumeric; + unsigned int textnumeric; + unsigned int endnumeric; + file_cache contents; + Method method; + + public: + CommandShowFile(Module* parent, const std::string& cmdname) + : Command(parent, cmdname) + { + } + + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + { + if (method == SF_NUMERIC) + { + if (!introtext.empty()) + user->WriteRemoteNumeric(intronumeric, introtext); + + for (file_cache::const_iterator i = contents.begin(); i != contents.end(); ++i) + user->WriteRemoteNumeric(textnumeric, InspIRCd::Format("- %s", i->c_str())); + + user->WriteRemoteNumeric(endnumeric, endtext.c_str()); + } + else if (IS_LOCAL(user)) + { + LocalUser* const localuser = IS_LOCAL(user); + for (file_cache::const_iterator i = contents.begin(); i != contents.end(); ++i) + { + const std::string& line = *i; + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, line, ((method == SF_MSG) ? MSG_PRIVMSG : MSG_NOTICE)); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); + } + } + return CMD_SUCCESS; + } + + void UpdateSettings(ConfigTag* tag, const std::vector<std::string>& filecontents) + { + introtext = tag->getString("introtext", "Showing " + name); + endtext = tag->getString("endtext", "End of " + name); + intronumeric = tag->getUInt("intronumeric", RPL_RULESTART, 0, 999); + textnumeric = tag->getUInt("numeric", RPL_RULES, 0, 999); + endnumeric = tag->getUInt("endnumeric", RPL_RULESEND, 0, 999); + std::string smethod = tag->getString("method"); + + method = SF_NUMERIC; + if (smethod == "msg") + method = SF_MSG; + else if (smethod == "notice") + method = SF_NOTICE; + + contents = filecontents; + InspIRCd::ProcessColors(contents); + } +}; + +class ModuleShowFile : public Module +{ + std::vector<CommandShowFile*> cmds; + + void ReadTag(ConfigTag* tag, std::vector<CommandShowFile*>& newcmds) + { + std::string cmdname = tag->getString("name"); + if (cmdname.empty()) + throw ModuleException("Empty value for 'name'"); + + std::transform(cmdname.begin(), cmdname.end(), cmdname.begin(), ::toupper); + + const std::string file = tag->getString("file", cmdname); + if (file.empty()) + throw ModuleException("Empty value for 'file'"); + FileReader reader(file); + + CommandShowFile* sfcmd; + Command* handler = ServerInstance->Parser.GetHandler(cmdname); + if (handler) + { + // Command exists, check if it is ours + if (handler->creator != this) + throw ModuleException("Command " + cmdname + " already exists"); + + // This is our command, make sure we don't have the same entry twice + sfcmd = static_cast<CommandShowFile*>(handler); + if (stdalgo::isin(newcmds, sfcmd)) + throw ModuleException("Command " + cmdname + " is already used in a <showfile> tag"); + } + else + { + // Command doesn't exist, create it + sfcmd = new CommandShowFile(this, cmdname); + ServerInstance->Modules->AddService(*sfcmd); + } + + sfcmd->UpdateSettings(tag, reader.GetVector()); + newcmds.push_back(sfcmd); + } + + public: + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + std::vector<CommandShowFile*> newcmds; + + ConfigTagList tags = ServerInstance->Config->ConfTags("showfile"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + try + { + ReadTag(tag, newcmds); + } + catch (CoreException& ex) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error: " + ex.GetReason() + " at " + tag->getTagLocation()); + } + } + + // Remove all commands that were removed from the config + std::vector<CommandShowFile*> removed(cmds.size()); + std::sort(newcmds.begin(), newcmds.end()); + std::set_difference(cmds.begin(), cmds.end(), newcmds.begin(), newcmds.end(), removed.begin()); + + stdalgo::delete_all(removed); + cmds.swap(newcmds); + } + + ~ModuleShowFile() + { + stdalgo::delete_all(cmds); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for showing text files to users", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleShowFile) diff --git a/src/modules/m_showwhois.cpp b/src/modules/m_showwhois.cpp index 398ebf571..d01e1a83b 100644 --- a/src/modules/m_showwhois.cpp +++ b/src/modules/m_showwhois.cpp @@ -22,17 +22,21 @@ #include "inspircd.h" - -/* $ModDesc: Allows opers to set +W to see when a user uses WHOIS on them */ +#include "modules/whois.h" /** Handle user mode +W */ class SeeWhois : public SimpleUserModeHandler { public: - SeeWhois(Module* Creator, bool IsOpersOnly) : SimpleUserModeHandler(Creator, "showwhois", 'W') + SeeWhois(Module* Creator) + : SimpleUserModeHandler(Creator, "showwhois", 'W') { - oper = IsOpersOnly; + } + + void SetOperOnly(bool operonly) + { + oper = operonly; } }; @@ -46,12 +50,12 @@ class WhoisNoticeCmd : public Command void HandleFast(User* dest, User* src) { - dest->WriteServ("NOTICE %s :*** %s (%s@%s) did a /whois on you", - dest->nick.c_str(), src->nick.c_str(), src->ident.c_str(), - dest->HasPrivPermission("users/auspex") ? src->host.c_str() : src->dhost.c_str()); + dest->WriteNotice("*** " + src->nick + " (" + src->ident + "@" + + src->GetHost(dest->HasPrivPermission("users/auspex")) + + ") did a /whois on you"); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* dest = ServerInstance->FindNick(parameters[0]); if (!dest) @@ -66,49 +70,42 @@ class WhoisNoticeCmd : public Command } }; -class ModuleShowwhois : public Module +class ModuleShowwhois : public Module, public Whois::EventListener { bool ShowWhoisFromOpers; - SeeWhois* sw; + SeeWhois sw; WhoisNoticeCmd cmd; public: ModuleShowwhois() - : sw(NULL), cmd(this) + : Whois::EventListener(this) + , sw(this) + , cmd(this) { } - void init() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("showwhois"); - bool OpersOnly = tag->getBool("opersonly", true); + sw.SetOperOnly(tag->getBool("opersonly", true)); ShowWhoisFromOpers = tag->getBool("showfromopers", true); - - sw = new SeeWhois(this, OpersOnly); - ServerInstance->Modules->AddService(*sw); - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnWhois }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - ~ModuleShowwhois() - { - delete sw; - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows opers to set +W to see when a user uses WHOIS on them",VF_OPTCOMMON|VF_VENDOR); } - void OnWhois(User* source, User* dest) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - if (!dest->IsModeSet('W') || source == dest) + User* const source = whois.GetSource(); + User* const dest = whois.GetTarget(); + if (!dest->IsModeSet(sw) || whois.IsSelfWhois()) return; - if (!ShowWhoisFromOpers && IS_OPER(source)) + if (!ShowWhoisFromOpers && source->IsOper()) return; if (IS_LOCAL(dest)) @@ -117,15 +114,12 @@ class ModuleShowwhois : public Module } else { - std::vector<std::string> params; - params.push_back(dest->server); - params.push_back("WHOISNOTICE"); + CommandBase::Params params; params.push_back(dest->uuid); params.push_back(source->uuid); - ServerInstance->PI->SendEncapsulatedData(params); + ServerInstance->PI->SendEncapsulatedData(dest->server->GetName(), cmd.name, params); } } - }; MODULE_INIT(ModuleShowwhois) diff --git a/src/modules/m_shun.cpp b/src/modules/m_shun.cpp index 98e63f026..da090e4f8 100644 --- a/src/modules/m_shun.cpp +++ b/src/modules/m_shun.cpp @@ -22,54 +22,9 @@ #include "inspircd.h" #include "xline.h" +#include "modules/shun.h" +#include "modules/stats.h" -/* $ModDesc: Provides the /SHUN command, which stops a user from executing all except configured commands. */ - -class Shun : public XLine -{ -public: - std::string matchtext; - - Shun(time_t s_time, long d, const std::string& src, const std::string& re, const std::string& shunmask) - : XLine(s_time, d, src, re, "SHUN") - , matchtext(shunmask) - { - } - - ~Shun() - { - } - - bool Matches(User *u) - { - // E: overrides shun - if (u->exempt) - return false; - - if (InspIRCd::Match(u->GetFullHost(), matchtext) || InspIRCd::Match(u->GetFullRealHost(), matchtext) || InspIRCd::Match(u->nick+"!"+u->ident+"@"+u->GetIPString(), matchtext)) - return true; - - return false; - } - - bool Matches(const std::string &s) - { - if (matchtext == s) - return true; - return false; - } - - void DisplayExpiry() - { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired shun %s (set by %s %ld seconds ago)", - this->matchtext.c_str(), this->source.c_str(), (long int)(ServerInstance->Time() - this->set_time)); - } - - const char* Displayable() - { - return matchtext.c_str(); - } -}; /** An XLineFactory specialized to generate shun pointers */ @@ -80,12 +35,12 @@ class ShunFactory : public XLineFactory /** Generate a shun */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { return new Shun(set_time, duration, source, reason, xline_specific_mask); } - bool AutoApplyToUserList(XLine *x) + bool AutoApplyToUserList(XLine* x) CXX11_OVERRIDE { return false; } @@ -101,13 +56,13 @@ class CommandShun : public Command flags_needed = 'o'; this->syntax = "<nick!user@hostmask> [<shun-duration>] :<reason>"; } - CmdResult Handle(const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { /* syntax: SHUN nick!user@host time :reason goes here */ /* 'time' is a human-readable timestring, like 2d3h2s. */ std::string target = parameters[0]; - + User *find = ServerInstance->FindNick(target); if ((find) && (find->registered == REG_ALL)) target = std::string("*!*@") + find->GetIPString(); @@ -120,22 +75,22 @@ class CommandShun : public Command } else if (ServerInstance->XLines->DelLine(target.c_str(), "SHUN", user)) { - ServerInstance->SNO->WriteToSnoMask('x',"%s removed SHUN on %s",user->nick.c_str(),target.c_str()); + ServerInstance->SNO->WriteToSnoMask('x',"%s removed SHUN on %s", user->nick.c_str(), target.c_str()); } else { - user->WriteServ("NOTICE %s :*** Shun %s not found in list, try /stats H.", user->nick.c_str(), parameters[0].c_str()); + user->WriteNotice("*** Shun " + parameters[0] + " not found in list, try /stats H."); return CMD_FAILURE; } } else { // Adding - XXX todo make this respect <insane> tag perhaps.. - long duration; + unsigned long duration; std::string expr; if (parameters.size() > 2) { - duration = ServerInstance->Duration(parameters[1]); + duration = InspIRCd::Duration(parameters[1]); expr = parameters[2]; } else @@ -155,7 +110,7 @@ class CommandShun : public Command else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteToSnoMask('x', "%s added timed SHUN for %s to expire on %s: %s", user->nick.c_str(), target.c_str(), timestr.c_str(), expr.c_str()); } @@ -163,14 +118,14 @@ class CommandShun : public Command else { delete r; - user->WriteServ("NOTICE %s :*** Shun for %s already exists", user->nick.c_str(), target.c_str()); + user->WriteNotice("*** Shun for " + target + " already exists"); return CMD_FAILURE; } } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { if (IS_LOCAL(user)) return ROUTE_LOCALONLY; // spanningtree will send ADDLINE @@ -179,51 +134,48 @@ class CommandShun : public Command } }; -class ModuleShun : public Module +class ModuleShun : public Module, public Stats::EventListener { CommandShun cmd; ShunFactory f; - std::set<std::string> ShunEnabledCommands; + insp::flat_set<std::string> ShunEnabledCommands; bool NotifyOfShun; bool affectopers; public: - ModuleShun() : cmd(this) + ModuleShun() + : Stats::EventListener(this) + , cmd(this) { } - void init() + void init() CXX11_OVERRIDE { ServerInstance->XLines->RegisterFactory(&f); - ServerInstance->Modules->AddService(cmd); - - Implementation eventlist[] = { I_OnStats, I_OnPreCommand, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); } - virtual ~ModuleShun() + ~ModuleShun() { ServerInstance->XLines->DelAll("SHUN"); ServerInstance->XLines->UnregisterFactory(&f); } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { Module* alias = ServerInstance->Modules->Find("m_alias.so"); - ServerInstance->Modules->SetPriority(this, I_OnPreCommand, PRIORITY_BEFORE, &alias); + ServerInstance->Modules->SetPriority(this, I_OnPreCommand, PRIORITY_BEFORE, alias); } - virtual ModResult OnStats(char symbol, User* user, string_list& out) + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if (symbol != 'H') + if (stats.GetSymbol() != 'H') return MOD_RES_PASSTHRU; - ServerInstance->XLines->InvokeStats("SHUN", 223, user, out); + ServerInstance->XLines->InvokeStats("SHUN", 223, stats); return MOD_RES_DENY; } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("shun"); std::string cmds = tag->getString("enabledcommands"); @@ -234,10 +186,10 @@ class ModuleShun : public Module ShunEnabledCommands.clear(); - std::stringstream dcmds(cmds); + irc::spacesepstream dcmds(cmds); std::string thiscmd; - while (dcmds >> thiscmd) + while (dcmds.GetToken(thiscmd)) { ShunEnabledCommands.insert(thiscmd); } @@ -246,7 +198,7 @@ class ModuleShun : public Module affectopers = tag->getBool("affectopers", false); } - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string>& parameters, LocalUser* user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { if (validated) return MOD_RES_PASSTHRU; @@ -257,18 +209,16 @@ class ModuleShun : public Module return MOD_RES_PASSTHRU; } - if (!affectopers && IS_OPER(user)) + if (!affectopers && user->IsOper()) { /* Don't do anything if the user is an operator and affectopers isn't set */ return MOD_RES_PASSTHRU; } - std::set<std::string>::iterator i = ShunEnabledCommands.find(command); - - if (i == ShunEnabledCommands.end()) + if (!ShunEnabledCommands.count(command)) { if (NotifyOfShun) - user->WriteServ("NOTICE %s :*** Command %s not processed, as you have been blocked from issuing commands (SHUN)", user->nick.c_str(), command.c_str()); + user->WriteNotice("*** Command " + command + " not processed, as you have been blocked from issuing commands (SHUN)"); return MOD_RES_DENY; } @@ -287,11 +237,10 @@ class ModuleShun : public Module return MOD_RES_PASSTHRU; } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the /SHUN command, which stops a user from executing all except configured commands.",VF_VENDOR|VF_COMMON); } }; MODULE_INIT(ModuleShun) - diff --git a/src/modules/m_silence.cpp b/src/modules/m_silence.cpp index c82ab3f9d..165e083bb 100644 --- a/src/modules/m_silence.cpp +++ b/src/modules/m_silence.cpp @@ -23,8 +23,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the /SILENCE command */ - /* Improved drop-in replacement for the /SILENCE command * syntax: /SILENCE [+|-]<mask> <p|c|i|n|t|a|x> as in <privatemessage|channelmessage|invites|privatenotice|channelnotice|all|exclude> * @@ -47,8 +45,8 @@ // pair of hostmask and flags typedef std::pair<std::string, int> silenceset; -// deque list of pairs -typedef std::deque<silenceset> silencelist; +// list of pairs +typedef std::vector<silenceset> silencelist; // intmasks for flags static int SILENCE_PRIVATE = 0x0001; /* p private messages */ @@ -59,6 +57,17 @@ static int SILENCE_CNOTICE = 0x0010; /* t channel notices */ static int SILENCE_ALL = 0x0020; /* a all, (pcint) */ static int SILENCE_EXCLUDE = 0x0040; /* x exclude this pattern */ +enum +{ + // From ircu? + RPL_SILELIST = 271, + RPL_ENDOFSILELIST = 272, + + // InspIRCd-specific. + RPL_UNSILENCED = 950, + RPL_SILENCED = 951, + ERR_NOTSILENCED = 952 +}; class CommandSVSSilence : public Command { @@ -66,10 +75,10 @@ class CommandSVSSilence : public Command CommandSVSSilence(Module* Creator) : Command(Creator,"SVSSILENCE", 2) { syntax = "<target> {[+|-]<mask> <p|c|i|n|t|a|x>}"; - TRANSLATE4(TR_NICK, TR_TEXT, TR_TEXT, TR_END); /* we watch for a nick. not a UID. */ + TRANSLATE3(TR_NICK, TR_TEXT, TR_TEXT); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { /* * XXX: thought occurs to me @@ -78,7 +87,7 @@ class CommandSVSSilence : public Command * style command so services can modify lots of entries at once. * leaving it backwards compatible for now as it's late. -- w */ - if (!ServerInstance->ULine(user->server)) + if (!user->server->IsULine()) return CMD_FAILURE; User *u = ServerInstance->FindNick(parameters[0]); @@ -87,18 +96,16 @@ class CommandSVSSilence : public Command if (IS_LOCAL(u)) { - ServerInstance->Parser->CallHandler("SILENCE", std::vector<std::string>(parameters.begin() + 1, parameters.end()), u); + CommandBase::Params params(parameters.begin() + 1, parameters.end()); + ServerInstance->Parser.CallHandler("SILENCE", params, u); } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* target = ServerInstance->FindNick(parameters[0]); - if (target) - return ROUTE_OPT_UCAST(target->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -108,16 +115,16 @@ class CommandSilence : public Command public: SimpleExtItem<silencelist> ext; CommandSilence(Module* Creator, unsigned int &max) : Command(Creator, "SILENCE", 0), - maxsilence(max), ext("silence_list", Creator) + maxsilence(max) + , ext("silence_list", ExtensionItem::EXT_USER, Creator) { allow_empty_last_param = false; syntax = "{[+|-]<mask> <p|c|i|n|t|a|x>}"; - TRANSLATE3(TR_TEXT, TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string>& parameters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (!parameters.size()) + if (parameters.empty()) { // no parameters, show the current silence list. silencelist* sl = ext.get(user); @@ -127,17 +134,17 @@ class CommandSilence : public Command for (silencelist::const_iterator c = sl->begin(); c != sl->end(); c++) { std::string decomppattern = DecompPattern(c->second); - user->WriteNumeric(271, "%s %s %s %s",user->nick.c_str(), user->nick.c_str(),c->first.c_str(), decomppattern.c_str()); + user->WriteNumeric(RPL_SILELIST, user->nick, c->first, decomppattern); } } - user->WriteNumeric(272, "%s :End of Silence List",user->nick.c_str()); + user->WriteNumeric(RPL_ENDOFSILELIST, "End of Silence List"); return CMD_SUCCESS; } - else if (parameters.size() > 0) + else { // one or more parameters, add or delete entry from the list (only the first parameter is used) - std::string mask = parameters[0].substr(1); + std::string mask(parameters[0], 1); char action = parameters[0][0]; // Default is private and notice so clients do not break int pattern = CompilePattern("pn"); @@ -149,7 +156,7 @@ class CommandSilence : public Command if (pattern == 0) { - user->WriteServ("NOTICE %s :Bad SILENCE pattern",user->nick.c_str()); + user->WriteNotice("Bad SILENCE pattern"); return CMD_INVALID; } @@ -172,11 +179,11 @@ class CommandSilence : public Command for (silencelist::iterator i = sl->begin(); i != sl->end(); i++) { // search through for the item - irc::string listitem = i->first.c_str(); - if (listitem == mask && i->second == pattern) + const std::string& listitem = i->first; + if ((irc::equals(listitem, mask)) && (i->second == pattern)) { sl->erase(i); - user->WriteNumeric(950, "%s %s :Removed %s %s from silence list",user->nick.c_str(), user->nick.c_str(), mask.c_str(), decomppattern.c_str()); + user->WriteNumeric(RPL_UNSILENCED, user->nick, InspIRCd::Format("Removed %s %s from silence list", mask.c_str(), decomppattern.c_str())); if (!sl->size()) { ext.unset(user); @@ -185,7 +192,7 @@ class CommandSilence : public Command } } } - user->WriteNumeric(952, "%s %s :%s %s does not exist on your silence list",user->nick.c_str(), user->nick.c_str(), mask.c_str(), decomppattern.c_str()); + user->WriteNumeric(ERR_NOTSILENCED, user->nick, InspIRCd::Format("%s %s does not exist on your silence list", mask.c_str(), decomppattern.c_str())); } else if (action == '+') { @@ -198,29 +205,29 @@ class CommandSilence : public Command } if (sl->size() > maxsilence) { - user->WriteNumeric(952, "%s %s :Your silence list is full",user->nick.c_str(), user->nick.c_str()); + user->WriteNumeric(ERR_NOTSILENCED, user->nick, "Your silence list is full"); return CMD_FAILURE; } std::string decomppattern = DecompPattern(pattern); for (silencelist::iterator n = sl->begin(); n != sl->end(); n++) { - irc::string listitem = n->first.c_str(); - if (listitem == mask && n->second == pattern) + const std::string& listitem = n->first; + if ((irc::equals(listitem, mask)) && (n->second == pattern)) { - user->WriteNumeric(952, "%s %s :%s %s is already on your silence list",user->nick.c_str(), user->nick.c_str(), mask.c_str(), decomppattern.c_str()); + user->WriteNumeric(ERR_NOTSILENCED, user->nick, InspIRCd::Format("%s %s is already on your silence list", mask.c_str(), decomppattern.c_str())); return CMD_FAILURE; } } if (((pattern & SILENCE_EXCLUDE) > 0)) { - sl->push_front(silenceset(mask,pattern)); + sl->insert(sl->begin(), silenceset(mask, pattern)); } else { sl->push_back(silenceset(mask,pattern)); } - user->WriteNumeric(951, "%s %s :Added %s %s to silence list",user->nick.c_str(), user->nick.c_str(), mask.c_str(), decomppattern.c_str()); + user->WriteNumeric(RPL_SILENCED, user->nick, InspIRCd::Format("Added %s %s to silence list", mask.c_str(), decomppattern.c_str())); return CMD_SUCCESS; } } @@ -293,6 +300,7 @@ class CommandSilence : public Command class ModuleSilence : public Module { unsigned int maxsilence; + bool ExemptULine; CommandSilence cmdsilence; CommandSVSSilence cmdsvssilence; public: @@ -302,36 +310,26 @@ class ModuleSilence : public Module { } - void init() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - OnRehash(NULL); - ServerInstance->Modules->AddService(cmdsilence); - ServerInstance->Modules->AddService(cmdsvssilence); - ServerInstance->Modules->AddService(cmdsilence.ext); + ConfigTag* tag = ServerInstance->Config->ConfValue("silence"); - Implementation eventlist[] = { I_OnRehash, I_On005Numeric, I_OnUserPreNotice, I_OnUserPreMessage, I_OnUserPreInvite }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + maxsilence = tag->getUInt("maxentries", 32, 1); + ExemptULine = tag->getBool("exemptuline", true); } - void OnRehash(User* user) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - maxsilence = ServerInstance->Config->ConfValue("silence")->getInt("maxentries", 32); - if (!maxsilence) - maxsilence = 32; + tokens["ESILENCE"]; + tokens["SILENCE"] = ConvToStr(maxsilence); } - void On005Numeric(std::string &output) - { - // we don't really have a limit... - output = output + " ESILENCE SILENCE=" + ConvToStr(maxsilence); - } - - void OnBuildExemptList(MessageType message_type, Channel* chan, User* sender, char status, CUList &exempt_list, const std::string &text) + void BuildExemptList(MessageType message_type, Channel* chan, User* sender, CUList& exempt_list) { int public_silence = (message_type == MSG_PRIVMSG ? SILENCE_CHANNEL : SILENCE_CNOTICE); - const UserMembList *ulist = chan->GetUsers(); - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + const Channel::MemberMap& ulist = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { if (IS_LOCAL(i->first)) { @@ -343,43 +341,29 @@ class ModuleSilence : public Module } } - ModResult PreText(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list, int silence_type) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { - if (target_type == TYPE_USER && IS_LOCAL(((User*)dest))) + if (target.type == MessageTarget::TYPE_USER && IS_LOCAL(target.Get<User>())) { - return MatchPattern((User*)dest, user, silence_type); + return MatchPattern(target.Get<User>(), user, ((details.type == MSG_PRIVMSG) ? SILENCE_PRIVATE : SILENCE_NOTICE)); } - else if (target_type == TYPE_CHANNEL) + else if (target.type == MessageTarget::TYPE_CHANNEL) { - Channel* chan = (Channel*)dest; - if (chan) - { - this->OnBuildExemptList((silence_type == SILENCE_PRIVATE ? MSG_PRIVMSG : MSG_NOTICE), chan, user, status, exempt_list, ""); - } + Channel* chan = target.Get<Channel>(); + BuildExemptList(details.type, chan, user, details.exemptions); } return MOD_RES_PASSTHRU; } - ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return PreText(user, dest, target_type, text, status, exempt_list, SILENCE_PRIVATE); - } - - ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) - { - return PreText(user, dest, target_type, text, status, exempt_list, SILENCE_NOTICE); - } - - ModResult OnUserPreInvite(User* source,User* dest,Channel* channel, time_t timeout) + ModResult OnUserPreInvite(User* source,User* dest,Channel* channel, time_t timeout) CXX11_OVERRIDE { return MatchPattern(dest, source, SILENCE_INVITE); } ModResult MatchPattern(User* dest, User* source, int pattern) { - /* Server source */ - if (!source || !dest) - return MOD_RES_ALLOW; + if (ExemptULine && source->server->IsULine()) + return MOD_RES_PASSTHRU; silencelist* sl = cmdsilence.ext.get(dest); if (sl) @@ -393,11 +377,7 @@ class ModuleSilence : public Module return MOD_RES_PASSTHRU; } - ~ModuleSilence() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for the /SILENCE command", VF_OPTCOMMON | VF_VENDOR); } diff --git a/src/modules/m_spanningtree/addline.cpp b/src/modules/m_spanningtree/addline.cpp index 16043b2aa..00ef5b8d1 100644 --- a/src/modules/m_spanningtree/addline.cpp +++ b/src/modules/m_spanningtree/addline.cpp @@ -20,60 +20,37 @@ #include "inspircd.h" #include "xline.h" -#include "treesocket.h" #include "treeserver.h" #include "utils.h" +#include "commands.h" -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -bool TreeSocket::AddLine(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandAddLine::Handle(User* usr, Params& params) { - if (params.size() < 6) - { - std::string servername = MyRoot->GetName(); - ServerInstance->SNO->WriteToSnoMask('d', "%s sent me a malformed ADDLINE", servername.c_str()); - return true; - } - XLineFactory* xlf = ServerInstance->XLines->GetFactory(params[0]); - - std::string setter = "<unknown>"; - User* usr = ServerInstance->FindNick(prefix); - if (usr) - setter = usr->nick; - else - { - TreeServer* t = Utils->FindServer(prefix); - if (t) - setter = t->GetName(); - } + const std::string& setter = usr->nick; if (!xlf) { - ServerInstance->SNO->WriteToSnoMask('d',"%s sent me an unknown ADDLINE type (%s).",setter.c_str(),params[0].c_str()); - return true; + ServerInstance->SNO->WriteToSnoMask('x',"%s sent me an unknown ADDLINE type (%s).",setter.c_str(),params[0].c_str()); + return CMD_FAILURE; } - long created = atol(params[3].c_str()), expires = atol(params[4].c_str()); - if (created < 0 || expires < 0) - return true; - XLine* xl = NULL; try { - xl = xlf->Generate(ServerInstance->Time(), expires, params[2], params[5], params[1]); + xl = xlf->Generate(ServerInstance->Time(), ConvToInt(params[4]), params[2], params[5], params[1]); } catch (ModuleException &e) { - ServerInstance->SNO->WriteToSnoMask('d',"Unable to ADDLINE type %s from %s: %s", params[0].c_str(), setter.c_str(), e.GetReason()); - return true; + ServerInstance->SNO->WriteToSnoMask('x',"Unable to ADDLINE type %s from %s: %s", params[0].c_str(), setter.c_str(), e.GetReason().c_str()); + return CMD_FAILURE; } - xl->SetCreateTime(created); + xl->SetCreateTime(ConvToInt(params[3])); if (ServerInstance->XLines->AddLine(xl, NULL)) { if (xl->duration) { - std::string timestr = ServerInstance->TimeString(xl->expiry); + std::string timestr = InspIRCd::TimeString(xl->expiry); ServerInstance->SNO->WriteToSnoMask('X',"%s added %s%s on %s to expire on %s: %s",setter.c_str(),params[0].c_str(),params[0].length() == 1 ? "-line" : "", params[1].c_str(), timestr.c_str(), params[5].c_str()); } @@ -82,20 +59,29 @@ bool TreeSocket::AddLine(const std::string &prefix, parameterlist ¶ms) ServerInstance->SNO->WriteToSnoMask('X',"%s added permanent %s%s on %s: %s",setter.c_str(),params[0].c_str(),params[0].length() == 1 ? "-line" : "", params[1].c_str(),params[5].c_str()); } - params[5] = ":" + params[5]; - User* u = ServerInstance->FindNick(prefix); - Utils->DoOneToAllButSender(prefix, "ADDLINE", params, u ? u->server : prefix); - TreeServer *remoteserver = Utils->FindServer(u ? u->server : prefix); + TreeServer* remoteserver = TreeServer::Get(usr); - if (!remoteserver->bursting) + if (!remoteserver->IsBursting()) { ServerInstance->XLines->ApplyLines(); } + return CMD_SUCCESS; } else + { delete xl; - - return true; + return CMD_FAILURE; + } } +CommandAddLine::Builder::Builder(XLine* xline, User* user) + : CmdBuilder(user, "ADDLINE") +{ + push(xline->type); + push(xline->Displayable()); + push(xline->source); + push_int(xline->set_time); + push_int(xline->duration); + push_last(xline->reason); +} diff --git a/src/modules/m_spanningtree/away.cpp b/src/modules/m_spanningtree/away.cpp index ed97c48cd..282f52a35 100644 --- a/src/modules/m_spanningtree/away.cpp +++ b/src/modules/m_spanningtree/away.cpp @@ -21,32 +21,32 @@ #include "main.h" #include "utils.h" -#include "treeserver.h" -#include "treesocket.h" +#include "commands.h" -bool TreeSocket::Away(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandAway::HandleRemote(::RemoteUser* u, Params& params) { - User* u = ServerInstance->FindNick(prefix); - if ((!u) || (IS_SERVER(u))) - return true; - if (params.size()) + if (!params.empty()) { - FOREACH_MOD(I_OnSetAway, OnSetAway(u, params[params.size() - 1])); - if (params.size() > 1) - u->awaytime = atoi(params[0].c_str()); + u->awaytime = ConvToInt(params[0]); else u->awaytime = ServerInstance->Time(); - u->awaymsg = params[params.size() - 1]; - - params[params.size() - 1] = ":" + params[params.size() - 1]; + u->awaymsg = params.back(); + FOREACH_MOD_CUSTOM(awayevprov, Away::EventListener, OnUserAway, (u)); } else { - FOREACH_MOD(I_OnSetAway, OnSetAway(u, "")); + u->awaytime = 0; u->awaymsg.clear(); + FOREACH_MOD_CUSTOM(awayevprov, Away::EventListener, OnUserBack, (u)); } - Utils->DoOneToAllButSender(prefix,"AWAY",params,u->server); - return true; + return CMD_SUCCESS; +} + +CommandAway::Builder::Builder(User* user) + : CmdBuilder(user, "AWAY") +{ + if (user->awaymsg.empty()) + push_int(user->awaytime).push_last(user->awaymsg); } diff --git a/src/modules/m_spanningtree/cachetimer.h b/src/modules/m_spanningtree/cachetimer.h index bad1b7419..489194b86 100644 --- a/src/modules/m_spanningtree/cachetimer.h +++ b/src/modules/m_spanningtree/cachetimer.h @@ -17,25 +17,13 @@ */ -#ifndef M_SPANNINGTREE_CACHETIMER_H -#define M_SPANNINGTREE_CACHETIMER_H +#pragma once -#include "timer.h" - -class ModuleSpanningTree; -class SpanningTreeUtilities; - -/** Create a timer which recurs every second, we inherit from Timer. - * Timer is only one-shot however, so at the end of each Tick() we simply - * insert another of ourselves into the pending queue :) +/** Timer that fires when we need to refresh the IP cache of servers */ class CacheRefreshTimer : public Timer { - private: - SpanningTreeUtilities *Utils; public: - CacheRefreshTimer(SpanningTreeUtilities* Util); - virtual void Tick(time_t TIME); + CacheRefreshTimer(); + bool Tick(time_t TIME) CXX11_OVERRIDE; }; - -#endif diff --git a/src/modules/m_spanningtree/capab.cpp b/src/modules/m_spanningtree/capab.cpp index 0ab815fef..256a3d069 100644 --- a/src/modules/m_spanningtree/capab.cpp +++ b/src/modules/m_spanningtree/capab.cpp @@ -20,39 +20,61 @@ #include "inspircd.h" -#include "xline.h" -#include "treesocket.h" #include "treeserver.h" #include "utils.h" #include "link.h" #include "main.h" -#include "../hash.h" -std::string TreeSocket::MyModules(int filter) +struct CompatMod +{ + const char* name; + ModuleFlags listflag; +}; + +static CompatMod compatmods[] = { - std::vector<std::string> modlist = ServerInstance->Modules->GetAllModuleNames(filter); + { "m_watch.so", VF_OPTCOMMON } +}; - if (filter == VF_COMMON && proto_version != ProtocolVersion) - CompatAddModules(modlist); +std::string TreeSocket::MyModules(int filter) +{ + const ModuleManager::ModuleMap& modlist = ServerInstance->Modules->GetModules(); std::string capabilities; - sort(modlist.begin(),modlist.end()); - for (std::vector<std::string>::const_iterator i = modlist.begin(); i != modlist.end(); ++i) + for (ModuleManager::ModuleMap::const_iterator i = modlist.begin(); i != modlist.end(); ++i) { - if (i != modlist.begin()) - capabilities.push_back(proto_version > 1201 ? ' ' : ','); - capabilities.append(*i); - Module* m = ServerInstance->Modules->Find(*i); - if (m && proto_version > 1201) + Module* const mod = i->second; + // 3.0 advertises its settings for the benefit of services + // 2.0 would bork on this + if (proto_version < 1205 && i->second->ModuleSourceFile == "m_kicknorejoin.so") + continue; + + bool do_compat_include = false; + if (proto_version < 1205) { - Version v = m->GetVersion(); - if (!v.link_data.empty()) + for (size_t j = 0; j < sizeof(compatmods)/sizeof(compatmods[0]); j++) { - capabilities.push_back('='); - capabilities.append(v.link_data); + if ((compatmods[j].listflag & filter) && (mod->ModuleSourceFile == compatmods[j].name)) + { + do_compat_include = true; + break; + } } } + + Version v = i->second->GetVersion(); + if ((!do_compat_include) && (!(v.Flags & filter))) + continue; + + if (i != modlist.begin()) + capabilities.push_back(' '); + capabilities.append(i->first); + if (!v.link_data.empty()) + { + capabilities.push_back('='); + capabilities.append(v.link_data); + } } return capabilities; } @@ -60,23 +82,23 @@ std::string TreeSocket::MyModules(int filter) static std::string BuildModeList(ModeType type) { std::vector<std::string> modes; - for(char c='A'; c <= 'z'; c++) + const ModeParser::ModeHandlerMap& mhs = ServerInstance->Modes.GetModes(type); + for (ModeParser::ModeHandlerMap::const_iterator i = mhs.begin(); i != mhs.end(); ++i) { - ModeHandler* mh = ServerInstance->Modes->FindMode(c, type); - if (mh) + const ModeHandler* const mh = i->second; + std::string mdesc = mh->name; + mdesc.push_back('='); + const PrefixMode* const pm = mh->IsPrefixMode(); + if (pm) { - std::string mdesc = mh->name; - mdesc.push_back('='); - if (mh->GetPrefix()) - mdesc.push_back(mh->GetPrefix()); - if (mh->GetModeChar()) - mdesc.push_back(mh->GetModeChar()); - modes.push_back(mdesc); + if (pm->GetPrefix()) + mdesc.push_back(pm->GetPrefix()); } + mdesc.push_back(mh->GetModeChar()); + modes.push_back(mdesc); } - sort(modes.begin(), modes.end()); - irc::stringjoiner line(" ", modes, 0, modes.size() - 1); - return line.GetJoined(); + std::sort(modes.begin(), modes.end()); + return stdalgo::string::join(modes); } void TreeSocket::SendCapabilities(int phase) @@ -91,7 +113,7 @@ void TreeSocket::SendCapabilities(int phase) if (phase < 2) return; - char sep = proto_version > 1201 ? ' ' : ','; + const char sep = ' '; irc::sepstream modulelist(MyModules(VF_COMMON), sep); irc::sepstream optmodulelist(MyModules(VF_OPTCOMMON), sep); /* Send module names, split at 509 length */ @@ -135,13 +157,18 @@ void TreeSocket::SendCapabilities(int phase) std::string extra; /* Do we have sha256 available? If so, we send a challenge */ - if (Utils->ChallengeResponse && (ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"))) + if (ServerInstance->Modules->FindService(SERVICE_DATA, "hash/sha256")) { SetOurChallenge(ServerInstance->GenRandomStr(20)); extra = " CHALLENGE=" + this->GetOurChallenge(); } - if (proto_version < 1202) - extra += ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL) ? " HALFOP=1" : " HALFOP=0"; + + // 2.0 needs these keys. + if (proto_version == 1202) + { + extra.append(" PROTOCOL="+ConvToStr(ProtocolVersion)); + extra.append(" MAXGECOS="+ConvToStr(ServerInstance->Config->Limits.MaxReal)); + } this->WriteLine("CAPAB CAPABILITIES " /* Preprocessor does this one. */ ":NICKMAX="+ConvToStr(ServerInstance->Config->Limits.NickMax)+ @@ -151,20 +178,21 @@ void TreeSocket::SendCapabilities(int phase) " MAXQUIT="+ConvToStr(ServerInstance->Config->Limits.MaxQuit)+ " MAXTOPIC="+ConvToStr(ServerInstance->Config->Limits.MaxTopic)+ " MAXKICK="+ConvToStr(ServerInstance->Config->Limits.MaxKick)+ - " MAXGECOS="+ConvToStr(ServerInstance->Config->Limits.MaxGecos)+ + " MAXREAL="+ConvToStr(ServerInstance->Config->Limits.MaxReal)+ " MAXAWAY="+ConvToStr(ServerInstance->Config->Limits.MaxAway)+ - " IP6SUPPORT=1"+ - " PROTOCOL="+ConvToStr(ProtocolVersion)+extra+ + " MAXHOST="+ConvToStr(ServerInstance->Config->Limits.MaxHost)+ + extra+ " PREFIX="+ServerInstance->Modes->BuildPrefixes()+ - " CHANMODES="+ServerInstance->Modes->GiveModeList(MASK_CHANNEL)+ - " USERMODES="+ServerInstance->Modes->GiveModeList(MASK_USER)+ + " CHANMODES="+ServerInstance->Modes->GiveModeList(MODETYPE_CHANNEL)+ + " USERMODES="+ServerInstance->Modes->GiveModeList(MODETYPE_USER)+ + " CASEMAPPING="+ServerInstance->Config->CaseMapping+ // XXX: Advertise the presence or absence of m_globops in CAPAB CAPABILITIES. // Services want to know about it, and since m_globops was not marked as VF_(OPT)COMMON // in 2.0, we advertise it here to not break linking to previous versions. // Protocol version 1201 (1.2) does not have this issue because we advertise m_globops // to 1201 protocol servers irrespectively of its module flags. - (ServerInstance->Modules->Find("m_globops.so") != NULL ? " GLOBOPS=1" : " GLOBOPS=0")+ - " SVSPART=1"); + (ServerInstance->Modules->Find("m_globops.so") != NULL ? " GLOBOPS=1" : " GLOBOPS=0") + ); this->WriteLine("CAPAB END"); } @@ -196,7 +224,7 @@ void TreeSocket::ListDifference(const std::string &one, const std::string &two, } } -bool TreeSocket::Capab(const parameterlist ¶ms) +bool TreeSocket::Capab(const CommandBase::Params& params) { if (params.size() < 1) { @@ -209,7 +237,23 @@ bool TreeSocket::Capab(const parameterlist ¶ms) capab->OptModuleList.clear(); capab->CapKeys.clear(); if (params.size() > 1) - proto_version = atoi(params[1].c_str()); + proto_version = ConvToInt(params[1]); + + if (proto_version < MinCompatProtocol) + { + SendError("CAPAB negotiation failed: Server is using protocol version " + (proto_version ? ConvToStr(proto_version) : "1201 or older") + + " which is too old to link with this server (version " + ConvToStr(ProtocolVersion) + + (ProtocolVersion != MinCompatProtocol ? ", links with " + ConvToStr(MinCompatProtocol) + " and above)" : ")")); + return false; + } + + // Special case, may be removed in the future + if (proto_version == 1203 || proto_version == 1204) + { + SendError("CAPAB negotiation failed: InspIRCd 2.1 beta is not supported"); + return false; + } + SendCapabilities(2); } else if (params[0] == "END") @@ -219,7 +263,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) if ((this->capab->ModuleList != this->MyModules(VF_COMMON)) && (this->capab->ModuleList.length())) { std::string diffIneed, diffUneed; - ListDifference(this->capab->ModuleList, this->MyModules(VF_COMMON), proto_version > 1201 ? ' ' : ',', diffIneed, diffUneed); + ListDifference(this->capab->ModuleList, this->MyModules(VF_COMMON), ' ', diffIneed, diffUneed); if (diffIneed.length() || diffUneed.length()) { reason = "Modules incorrectly matched on these servers."; @@ -257,21 +301,6 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } } - if (this->capab->CapKeys.find("PROTOCOL") == this->capab->CapKeys.end()) - { - reason = "Protocol version not specified"; - } - else - { - proto_version = atoi(capab->CapKeys.find("PROTOCOL")->second.c_str()); - if (proto_version < MinCompatProtocol) - { - reason = "Server is using protocol version " + ConvToStr(proto_version) + - " which is too old to link with this server (version " + ConvToStr(ProtocolVersion) - + (ProtocolVersion != MinCompatProtocol ? ", links with " + ConvToStr(MinCompatProtocol) + " and above)" : ")"); - } - } - if(this->capab->CapKeys.find("PREFIX") != this->capab->CapKeys.end() && this->capab->CapKeys.find("PREFIX")->second != ServerInstance->Modes->BuildPrefixes()) reason = "One or more of the prefixes on the remote server are invalid on this server."; @@ -293,7 +322,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else if (this->capab->CapKeys.find("CHANMODES") != this->capab->CapKeys.end()) { - if (this->capab->CapKeys.find("CHANMODES")->second != ServerInstance->Modes->GiveModeList(MASK_CHANNEL)) + if (this->capab->CapKeys.find("CHANMODES")->second != ServerInstance->Modes->GiveModeList(MODETYPE_CHANNEL)) reason = "One or more of the channel modes on the remote server are invalid on this server."; } @@ -315,13 +344,31 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else if (this->capab->CapKeys.find("USERMODES") != this->capab->CapKeys.end()) { - if (this->capab->CapKeys.find("USERMODES")->second != ServerInstance->Modes->GiveModeList(MASK_USER)) + if (this->capab->CapKeys.find("USERMODES")->second != ServerInstance->Modes->GiveModeList(MODETYPE_USER)) reason = "One or more of the user modes on the remote server are invalid on this server."; } + else + { + // We default to rfc1459 here because if this key is not sent then + // the remote server is running the 2.0 protocol which uses rfc1459 + // by default. + std::string casemapping = "rfc1459"; + std::map<std::string, std::string>::iterator citer = this->capab->CapKeys.find("CASEMAPPING"); + if (citer != this->capab->CapKeys.end()) + casemapping = citer->second; + + if (casemapping != ServerInstance->Config->CaseMapping) + { + reason = "The casemapping of the remote server differs to that of the local server." + " Local casemapping: " + ServerInstance->Config->CaseMapping + + " Remote casemapping: " + casemapping; + } + + } /* Challenge response, store their challenge for our password */ std::map<std::string,std::string>::iterator n = this->capab->CapKeys.find("CHALLENGE"); - if (Utils->ChallengeResponse && (n != this->capab->CapKeys.end()) && (ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"))) + if ((n != this->capab->CapKeys.end()) && (ServerInstance->Modules->FindService(SERVICE_DATA, "hash/sha256"))) { /* Challenge-response is on now */ this->SetTheirChallenge(n->second); @@ -333,7 +380,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else { - /* They didnt specify a challenge or we don't have m_sha256.so, we use plaintext */ + // They didn't specify a challenge or we don't have sha256, we use plaintext if (this->LinkState == CONNECTING) { this->SendCapabilities(2); @@ -355,7 +402,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else { - capab->ModuleList.push_back(proto_version > 1201 ? ' ' : ','); + capab->ModuleList.push_back(' '); capab->ModuleList.append(params[1]); } } @@ -381,7 +428,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else if ((params[0] == "CAPABILITIES") && (params.size() == 2)) { - irc::tokenstream capabs(params[1]); + irc::spacesepstream capabs(params[1]); std::string item; while (capabs.GetToken(item)) { @@ -389,12 +436,11 @@ bool TreeSocket::Capab(const parameterlist ¶ms) std::string::size_type equals = item.find('='); if (equals != std::string::npos) { - std::string var = item.substr(0, equals); - std::string value = item.substr(equals+1, item.length()); + std::string var(item, 0, equals); + std::string value(item, equals+1); capab->CapKeys[var] = value; } } } return true; } - diff --git a/src/modules/m_spanningtree/commandbuilder.h b/src/modules/m_spanningtree/commandbuilder.h new file mode 100644 index 000000000..59de84052 --- /dev/null +++ b/src/modules/m_spanningtree/commandbuilder.h @@ -0,0 +1,149 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "utils.h" + +class TreeServer; + +class CmdBuilder +{ + protected: + std::string content; + + public: + CmdBuilder(const char* cmd) + : content(1, ':') + { + content.append(ServerInstance->Config->GetSID()); + push(cmd); + } + + CmdBuilder(const std::string& src, const char* cmd) + : content(1, ':') + { + content.append(src); + push(cmd); + } + + CmdBuilder(User* src, const char* cmd) + : content(1, ':') + { + content.append(src->uuid); + push(cmd); + } + + CmdBuilder& push_raw(const std::string& s) + { + content.append(s); + return *this; + } + + CmdBuilder& push_raw(const char* s) + { + content.append(s); + return *this; + } + + CmdBuilder& push_raw(char c) + { + content.push_back(c); + return *this; + } + + template <typename T> + CmdBuilder& push_raw_int(T i) + { + content.append(ConvToStr(i)); + return *this; + } + + template <typename InputIterator> + CmdBuilder& push_raw(InputIterator first, InputIterator last) + { + content.append(first, last); + return *this; + } + + CmdBuilder& push(const std::string& s) + { + content.push_back(' '); + content.append(s); + return *this; + } + + CmdBuilder& push(const char* s) + { + content.push_back(' '); + content.append(s); + return *this; + } + + CmdBuilder& push(char c) + { + content.push_back(' '); + content.push_back(c); + return *this; + } + + template <typename T> + CmdBuilder& push_int(T i) + { + content.push_back(' '); + content.append(ConvToStr(i)); + return *this; + } + + CmdBuilder& push_last(const std::string& s) + { + content.push_back(' '); + content.push_back(':'); + content.append(s); + return *this; + } + + template<typename T> + CmdBuilder& insert(const T& cont) + { + for (typename T::const_iterator i = cont.begin(); i != cont.end(); ++i) + push(*i); + return *this; + } + + void push_back(const std::string& s) { push(s); } + + const std::string& str() const { return content; } + operator const std::string&() const { return str(); } + + void Broadcast() const + { + Utils->DoOneToMany(*this); + } + + void Forward(TreeServer* omit) const + { + Utils->DoOneToAllButSender(*this, omit); + } + + void Unicast(User* target) const + { + Utils->DoOneToOne(*this, target->server); + } +}; diff --git a/src/modules/m_spanningtree/commands.h b/src/modules/m_spanningtree/commands.h index 3b5b499c1..434528e46 100644 --- a/src/modules/m_spanningtree/commands.h +++ b/src/modules/m_spanningtree/commands.h @@ -17,126 +17,390 @@ */ -#ifndef M_SPANNINGTREE_COMMANDS_H -#define M_SPANNINGTREE_COMMANDS_H +#pragma once -#include "main.h" +#include "servercommand.h" +#include "commandbuilder.h" +#include "remoteuser.h" +#include "modules/away.h" + +namespace SpanningTree +{ + class CommandAway; + class CommandNick; + class CommandPing; + class CommandPong; + class CommandServer; +} + +using SpanningTree::CommandAway; +using SpanningTree::CommandNick; +using SpanningTree::CommandPing; +using SpanningTree::CommandPong; +using SpanningTree::CommandServer; /** Handle /RCONNECT */ class CommandRConnect : public Command { - SpanningTreeUtilities* Utils; /* Utility class */ public: - CommandRConnect (Module* Callback, SpanningTreeUtilities* Util); - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + CommandRConnect(Module* Creator); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; }; class CommandRSQuit : public Command { - SpanningTreeUtilities* Utils; /* Utility class */ public: - CommandRSQuit(Module* Callback, SpanningTreeUtilities* Util); - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); - void NoticeUser(User* user, const std::string &msg); + CommandRSQuit(Module* Creator); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; }; -class CommandSVSJoin : public Command +class CommandMap : public Command { public: - CommandSVSJoin(Module* Creator) : Command(Creator, "SVSJOIN", 2) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + CommandMap(Module* Creator); + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; }; -class CommandSVSPart : public Command + +class CommandSVSJoin : public ServerCommand { public: - CommandSVSPart(Module* Creator) : Command(Creator, "SVSPART", 2) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + CommandSVSJoin(Module* Creator) : ServerCommand(Creator, "SVSJOIN", 2) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; }; -class CommandSVSNick : public Command + +class CommandSVSPart : public ServerCommand { public: - CommandSVSNick(Module* Creator) : Command(Creator, "SVSNICK", 3) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + CommandSVSPart(Module* Creator) : ServerCommand(Creator, "SVSPART", 2) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; }; -class CommandMetadata : public Command + +class CommandSVSNick : public ServerCommand { public: - CommandMetadata(Module* Creator) : Command(Creator, "METADATA", 2) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandSVSNick(Module* Creator) : ServerCommand(Creator, "SVSNICK", 3) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; }; -class CommandUID : public Command + +class CommandMetadata : public ServerCommand { public: - CommandUID(Module* Creator) : Command(Creator, "UID", 10) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandMetadata(Module* Creator) : ServerCommand(Creator, "METADATA", 2) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; + + class Builder : public CmdBuilder + { + public: + Builder(User* user, const std::string& key, const std::string& val); + Builder(Channel* chan, const std::string& key, const std::string& val); + Builder(const std::string& key, const std::string& val); + }; }; -class CommandOpertype : public Command + +class CommandUID : public ServerOnlyServerCommand<CommandUID> { public: - CommandOpertype(Module* Creator) : Command(Creator, "OPERTYPE", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandUID(Module* Creator) : ServerOnlyServerCommand<CommandUID>(Creator, "UID", 10) { } + CmdResult HandleServer(TreeServer* server, CommandBase::Params& params); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + }; }; -class CommandFJoin : public Command + +class CommandOpertype : public UserOnlyServerCommand<CommandOpertype> { public: - CommandFJoin(Module* Creator) : Command(Creator, "FJOIN", 3) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandOpertype(Module* Creator) : UserOnlyServerCommand<CommandOpertype>(Creator, "OPERTYPE", 1) { } + CmdResult HandleRemote(RemoteUser* user, Params& params); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + }; +}; + +class TreeSocket; +class FwdFJoinBuilder; +class CommandFJoin : public ServerCommand +{ /** Remove all modes from a channel, including statusmodes (+qaovh etc), simplemodes, parameter modes. * This does not update the timestamp of the target channel, this must be done seperately. */ - void RemoveStatus(User* source, parameterlist ¶ms); + static void RemoveStatus(Channel* c); + + /** + * Lowers the TS on the given channel: removes all modes, unsets all extensions, + * clears the topic and removes all pending invites. + * @param chan The target channel whose TS to lower + * @param TS The new TS to set + * @param newname The new name of the channel; must be the same or a case change of the current name + */ + static void LowerTS(Channel* chan, time_t TS, const std::string& newname); + void ProcessModeUUIDPair(const std::string& item, TreeServer* sourceserver, Channel* chan, Modes::ChangeList* modechangelist, FwdFJoinBuilder& fwdfjoin); + public: + CommandFJoin(Module* Creator) : ServerCommand(Creator, "FJOIN", 3) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_LOCALONLY; } + + class Builder : public CmdBuilder + { + /** Maximum possible Membership::Id length in decimal digits, used for determining whether a user will fit into + * a message or not + */ + static const size_t membid_max_digits = 20; + static const size_t maxline = 510; + std::string::size_type pos; + + protected: + void add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend); + bool has_room(std::string::size_type nummodes) const; + + public: + Builder(Channel* chan, TreeServer* source = Utils->TreeRoot); + void add(Membership* memb) + { + add(memb, memb->modes.begin(), memb->modes.end()); + } + + bool has_room(Membership* memb) const + { + return has_room(memb->modes.size()); + } + + void clear(); + const std::string& finalize(); + }; +}; + +class CommandFMode : public ServerCommand +{ + public: + CommandFMode(Module* Creator) : ServerCommand(Creator, "FMODE", 3) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; }; -class CommandFMode : public Command + +class CommandFTopic : public ServerCommand { public: - CommandFMode(Module* Creator) : Command(Creator, "FMODE", 3) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandFTopic(Module* Creator) : ServerCommand(Creator, "FTOPIC", 4, 5) { } + CmdResult Handle(User* user, Params& params) CXX11_OVERRIDE; + + class Builder : public CmdBuilder + { + public: + Builder(Channel* chan); + Builder(User* user, Channel* chan); + }; }; -class CommandFTopic : public Command + +class CommandFHost : public UserOnlyServerCommand<CommandFHost> +{ + public: + CommandFHost(Module* Creator) : UserOnlyServerCommand<CommandFHost>(Creator, "FHOST", 1) { } + CmdResult HandleRemote(RemoteUser* user, Params& params); +}; + +class CommandFIdent : public UserOnlyServerCommand<CommandFIdent> { public: - CommandFTopic(Module* Creator) : Command(Creator, "FTOPIC", 4) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandFIdent(Module* Creator) : UserOnlyServerCommand<CommandFIdent>(Creator, "FIDENT", 1) { } + CmdResult HandleRemote(RemoteUser* user, Params& params); }; -class CommandFHost : public Command + +class CommandFName : public UserOnlyServerCommand<CommandFName> { public: - CommandFHost(Module* Creator) : Command(Creator, "FHOST", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandFName(Module* Creator) : UserOnlyServerCommand<CommandFName>(Creator, "FNAME", 1) { } + CmdResult HandleRemote(RemoteUser* user, Params& params); }; -class CommandFIdent : public Command + +class CommandIJoin : public UserOnlyServerCommand<CommandIJoin> { public: - CommandFIdent(Module* Creator) : Command(Creator, "FIDENT", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandIJoin(Module* Creator) : UserOnlyServerCommand<CommandIJoin>(Creator, "IJOIN", 2) { } + CmdResult HandleRemote(RemoteUser* user, Params& params); }; -class CommandFName : public Command + +class CommandResync : public ServerOnlyServerCommand<CommandResync> { public: - CommandFName(Module* Creator) : Command(Creator, "FNAME", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandResync(Module* Creator) : ServerOnlyServerCommand<CommandResync>(Creator, "RESYNC", 1) { } + CmdResult HandleServer(TreeServer* server, Params& parameters); + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_LOCALONLY; } +}; + +class SpanningTree::CommandAway : public UserOnlyServerCommand<SpanningTree::CommandAway> +{ + private: + Away::EventProvider awayevprov; + + public: + CommandAway(Module* Creator) + : UserOnlyServerCommand<SpanningTree::CommandAway>(Creator, "AWAY", 0, 2) + , awayevprov(Creator) + { + } + CmdResult HandleRemote(::RemoteUser* user, Params& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + }; +}; + +class XLine; +class CommandAddLine : public ServerCommand +{ + public: + CommandAddLine(Module* Creator) : ServerCommand(Creator, "ADDLINE", 6, 6) { } + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE; + + class Builder : public CmdBuilder + { + public: + Builder(XLine* xline, User* user = ServerInstance->FakeClient); + }; +}; + +class CommandDelLine : public ServerCommand +{ + public: + CommandDelLine(Module* Creator) : ServerCommand(Creator, "DELLINE", 2, 2) { } + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE; +}; + +class CommandEncap : public ServerCommand +{ + public: + CommandEncap(Module* Creator) : ServerCommand(Creator, "ENCAP", 2) { } + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; +}; + +class CommandIdle : public UserOnlyServerCommand<CommandIdle> +{ + public: + CommandIdle(Module* Creator) : UserOnlyServerCommand<CommandIdle>(Creator, "IDLE", 1) { } + CmdResult HandleRemote(RemoteUser* user, Params& parameters); + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_UNICAST(parameters[0]); } +}; + +class SpanningTree::CommandNick : public UserOnlyServerCommand<SpanningTree::CommandNick> +{ + public: + CommandNick(Module* Creator) : UserOnlyServerCommand<SpanningTree::CommandNick>(Creator, "NICK", 2) { } + CmdResult HandleRemote(::RemoteUser* user, Params& parameters); +}; + +class SpanningTree::CommandPing : public ServerCommand +{ + public: + CommandPing(Module* Creator) : ServerCommand(Creator, "PING", 1) { } + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_UNICAST(parameters[0]); } +}; + +class SpanningTree::CommandPong : public ServerOnlyServerCommand<SpanningTree::CommandPong> +{ + public: + CommandPong(Module* Creator) : ServerOnlyServerCommand<SpanningTree::CommandPong>(Creator, "PONG", 1) { } + CmdResult HandleServer(TreeServer* server, Params& parameters); + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_UNICAST(parameters[0]); } +}; + +class CommandSave : public ServerCommand +{ + public: + /** Timestamp of the uuid nick of all users who collided and got their nick changed to uuid + */ + static const time_t SavedTimestamp = 100; + + CommandSave(Module* Creator) : ServerCommand(Creator, "SAVE", 2) { } + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE; +}; + +class SpanningTree::CommandServer : public ServerOnlyServerCommand<SpanningTree::CommandServer> +{ + static void HandleExtra(TreeServer* newserver, Params& params); + + public: + CommandServer(Module* Creator) : ServerOnlyServerCommand<SpanningTree::CommandServer>(Creator, "SERVER", 3) { } + CmdResult HandleServer(TreeServer* server, Params& parameters); + + class Builder : public CmdBuilder + { + void push_property(const char* key, const std::string& val) + { + push(key).push_raw('=').push_raw(val); + } + public: + Builder(TreeServer* server); + }; +}; + +class CommandSQuit : public ServerOnlyServerCommand<CommandSQuit> +{ + public: + CommandSQuit(Module* Creator) : ServerOnlyServerCommand<CommandSQuit>(Creator, "SQUIT", 2) { } + CmdResult HandleServer(TreeServer* server, Params& parameters); +}; + +class CommandSNONotice : public ServerCommand +{ + public: + CommandSNONotice(Module* Creator) : ServerCommand(Creator, "SNONOTICE", 2) { } + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE; +}; + +class CommandEndBurst : public ServerOnlyServerCommand<CommandEndBurst> +{ + public: + CommandEndBurst(Module* Creator) : ServerOnlyServerCommand<CommandEndBurst>(Creator, "ENDBURST") { } + CmdResult HandleServer(TreeServer* server, Params& parameters); +}; + +class CommandSInfo : public ServerOnlyServerCommand<CommandSInfo> +{ + public: + CommandSInfo(Module* Creator) : ServerOnlyServerCommand<CommandSInfo>(Creator, "SINFO", 2) { } + CmdResult HandleServer(TreeServer* server, Params& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(TreeServer* server, const char* type, const std::string& value); + }; +}; + +class CommandNum : public ServerOnlyServerCommand<CommandNum> +{ + public: + CommandNum(Module* Creator) : ServerOnlyServerCommand<CommandNum>(Creator, "NUM", 3) { } + CmdResult HandleServer(TreeServer* server, Params& parameters); + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; + + class Builder : public CmdBuilder + { + public: + Builder(SpanningTree::RemoteUser* target, const Numeric::Numeric& numeric); + }; }; class SpanningTreeCommands { public: - CommandRConnect rconnect; - CommandRSQuit rsquit; CommandSVSJoin svsjoin; CommandSVSPart svspart; CommandSVSNick svsnick; @@ -144,12 +408,27 @@ class SpanningTreeCommands CommandUID uid; CommandOpertype opertype; CommandFJoin fjoin; + CommandIJoin ijoin; + CommandResync resync; CommandFMode fmode; CommandFTopic ftopic; CommandFHost fhost; CommandFIdent fident; CommandFName fname; + SpanningTree::CommandAway away; + CommandAddLine addline; + CommandDelLine delline; + CommandEncap encap; + CommandIdle idle; + SpanningTree::CommandNick nick; + SpanningTree::CommandPing ping; + SpanningTree::CommandPong pong; + CommandSave save; + SpanningTree::CommandServer server; + CommandSQuit squit; + CommandSNONotice snonotice; + CommandEndBurst endburst; + CommandSInfo sinfo; + CommandNum num; SpanningTreeCommands(ModuleSpanningTree* module); }; - -#endif diff --git a/src/modules/m_spanningtree/compat.cpp b/src/modules/m_spanningtree/compat.cpp index ec0cdb036..b2967af3b 100644 --- a/src/modules/m_spanningtree/compat.cpp +++ b/src/modules/m_spanningtree/compat.cpp @@ -20,177 +20,561 @@ #include "inspircd.h" #include "main.h" #include "treesocket.h" +#include "treeserver.h" -static const char* const forge_common_1201[] = { - "m_allowinvite.so", - "m_alltime.so", - "m_auditorium.so", - "m_banexception.so", - "m_blockcaps.so", - "m_blockcolor.so", - "m_botmode.so", - "m_censor.so", - "m_chanfilter.so", - "m_chanhistory.so", - "m_channelban.so", - "m_chanprotect.so", - "m_chghost.so", - "m_chgname.so", - "m_commonchans.so", - "m_customtitle.so", - "m_deaf.so", - "m_delayjoin.so", - "m_delaymsg.so", - "m_exemptchanops.so", - "m_gecosban.so", - "m_globops.so", - "m_helpop.so", - "m_hidechans.so", - "m_hideoper.so", - "m_invisible.so", - "m_inviteexception.so", - "m_joinflood.so", - "m_kicknorejoin.so", - "m_knock.so", - "m_messageflood.so", - "m_muteban.so", - "m_nickflood.so", - "m_nicklock.so", - "m_noctcp.so", - "m_nokicks.so", - "m_nonicks.so", - "m_nonotice.so", - "m_nopartmsg.so", - "m_ojoin.so", - "m_operprefix.so", - "m_permchannels.so", - "m_redirect.so", - "m_regex_glob.so", - "m_regex_pcre.so", - "m_regex_posix.so", - "m_regex_tre.so", - "m_remove.so", - "m_sajoin.so", - "m_sakick.so", - "m_sanick.so", - "m_sapart.so", - "m_saquit.so", - "m_serverban.so", - "m_services_account.so", - "m_servprotect.so", - "m_setident.so", - "m_showwhois.so", - "m_silence.so", - "m_sslmodes.so", - "m_stripcolor.so", - "m_swhois.so", - "m_uninvite.so", - "m_watch.so" -}; - -static std::string wide_newline("\r\n"); static std::string newline("\n"); -void TreeSocket::CompatAddModules(std::vector<std::string>& modlist) +void TreeSocket::WriteLineNoCompat(const std::string& line) { - if (proto_version < 1202) - { - // you MUST have chgident loaded in order to be able to translate FIDENT - modlist.push_back("m_chgident.so"); - for(int i=0; i * sizeof(char*) < sizeof(forge_common_1201); i++) - { - if (ServerInstance->Modules->Find(forge_common_1201[i])) - modlist.push_back(forge_common_1201[i]); - } - // module was merged - if (ServerInstance->Modules->Find("m_operchans.so")) - { - modlist.push_back("m_operchans.so"); - modlist.push_back("m_operinvex.so"); - } - } + ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] O %s", this->GetFd(), line.c_str()); + this->WriteData(line); + this->WriteData(newline); } -void TreeSocket::WriteLine(std::string line) +void TreeSocket::WriteLine(const std::string& original_line) { if (LinkState == CONNECTED) { - if (line[0] != ':') + if (original_line.c_str()[0] != ':') { - ServerInstance->Logs->Log("m_spanningtree", DEFAULT, "Sending line without server prefix!"); - line = ":" + ServerInstance->Config->GetSID() + " " + line; + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Sending line without server prefix!"); + WriteLine(":" + ServerInstance->Config->GetSID() + " " + original_line); + return; } if (proto_version != ProtocolVersion) { + std::string line = original_line; std::string::size_type a = line.find(' '); std::string::size_type b = line.find(' ', a + 1); - std::string command = line.substr(a + 1, b-a-1); + std::string command(line, a + 1, b-a-1); // now try to find a translation entry // TODO a more efficient lookup method will be needed later - if (proto_version < 1202 && command == "FIDENT") - { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Rewriting FIDENT for 1201-protocol server"); - line = ":" + ServerInstance->Config->GetSID() + " CHGIDENT " + line.substr(1,a-1) + line.substr(b); - } - else if (proto_version < 1202 && command == "SAVE") - { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Rewriting SAVE for 1201-protocol server"); - std::string::size_type c = line.find(' ', b + 1); - std::string uid = line.substr(b, c - b); - line = ":" + ServerInstance->Config->GetSID() + " SVSNICK" + uid + line.substr(b); - } - else if (proto_version < 1202 && command == "AWAY") + if (proto_version < 1205) { - if (b != std::string::npos) + if (command == "IJOIN") { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Stripping AWAY timestamp for 1201-protocol server"); + // Convert + // :<uid> IJOIN <chan> <membid> [<ts> [<flags>]] + // to + // :<sid> FJOIN <chan> <ts> + [<flags>],<uuid> std::string::size_type c = line.find(' ', b + 1); - line.erase(b,c-b); + if (c == std::string::npos) + return; + + std::string::size_type d = line.find(' ', c + 1); + // Erase membership id first + line.erase(c, d-c); + if (d == std::string::npos) + { + // No TS or modes in the command + // :22DAAAAAB IJOIN #chan + const std::string channame(line, b+1, c-b-1); + Channel* chan = ServerInstance->FindChan(channame); + if (!chan) + return; + + line.push_back(' '); + line.append(ConvToStr(chan->age)); + line.append(" + ,"); + } + else + { + d = line.find(' ', c + 1); + if (d == std::string::npos) + { + // TS present, no modes + // :22DAAAAAC IJOIN #chan 12345 + line.append(" + ,"); + } + else + { + // Both TS and modes are present + // :22DAAAAAC IJOIN #chan 12345 ov + std::string::size_type e = line.find(' ', d + 1); + if (e != std::string::npos) + line.erase(e); + + line.insert(d, " +"); + line.push_back(','); + } + } + + // Move the uuid to the end and replace the I with an F + line.append(line.substr(1, 9)); + line.erase(4, 6); + line[5] = 'F'; } - } - else if (proto_version < 1202 && command == "ENCAP") - { - // :src ENCAP target command [args...] - // A B C D - // Therefore B and C cannot be npos in a valid command - if (b == std::string::npos) - return; - std::string::size_type c = line.find(' ', b + 1); - if (c == std::string::npos) + else if (command == "RESYNC") return; - std::string::size_type d = line.find(' ', c + 1); - std::string subcmd = line.substr(c + 1, d - c - 1); + else if (command == "METADATA") + { + // Drop TS for channel METADATA, translate METADATA operquit into an OPERQUIT command + // :sid METADATA #target TS extname ... + // A B C D + if (b == std::string::npos) + return; + + std::string::size_type c = line.find(' ', b + 1); + if (c == std::string::npos) + return; + + std::string::size_type d = line.find(' ', c + 1); + if (d == std::string::npos) + return; + + if (line[b + 1] == '#') + { + // We're sending channel metadata + line.erase(c, d-c); + } + else if (!line.compare(c, d-c, " operquit", 9)) + { + // ":22D METADATA 22DAAAAAX operquit :message" -> ":22DAAAAAX OPERQUIT :message" + line = ":" + line.substr(b+1, c-b) + "OPERQUIT" + line.substr(d); + } + } + else if (command == "FTOPIC") + { + // Drop channel TS for FTOPIC + // :sid FTOPIC #target TS TopicTS setter :newtopic + // A B C D E F + // :uid FTOPIC #target TS TopicTS :newtopic + // A B C D E + if (b == std::string::npos) + return; - if (subcmd == "CHGIDENT" && d != std::string::npos) + std::string::size_type c = line.find(' ', b + 1); + if (c == std::string::npos) + return; + + std::string::size_type d = line.find(' ', c + 1); + if (d == std::string::npos) + return; + + std::string::size_type e = line.find(' ', d + 1); + if (line[e+1] == ':') + { + line.erase(c, e-c); + line.erase(a+1, 1); + } + else + line.erase(c, d-c); + } + else if ((command == "PING") || (command == "PONG")) + { + // :22D PING 20D + if (line.length() < 13) + return; + + // Insert the source SID (and a space) between the command and the first parameter + line.insert(10, line.substr(1, 4)); + } + else if (command == "OPERTYPE") { + std::string::size_type colon = line.find(':', b); + if (colon != std::string::npos) + { + for (std::string::iterator i = line.begin()+colon; i != line.end(); ++i) + { + if (*i == ' ') + *i = '_'; + } + line.erase(colon, 1); + } + } + else if (command == "INVITE") + { + // :22D INVITE 22DAAAAAN #chan TS ExpirationTime + // A B C D E + if (b == std::string::npos) + return; + + std::string::size_type c = line.find(' ', b + 1); + if (c == std::string::npos) + return; + + std::string::size_type d = line.find(' ', c + 1); + if (d == std::string::npos) + return; + std::string::size_type e = line.find(' ', d + 1); - if (e == std::string::npos) - return; // not valid - std::string target = line.substr(d + 1, e - d - 1); + // If there is no expiration time then everything will be erased from 'd' + line.erase(d, e-d); + } + else if (command == "FJOIN") + { + // Strip membership ids + // :22D FJOIN #chan 1234 +f 4:3 :o,22DAAAAAB:15 o,22DAAAAAA:15 + // :22D FJOIN #chan 1234 +f 4:3 o,22DAAAAAB:15 + // :22D FJOIN #chan 1234 +Pf 4:3 : - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Forging acceptance of CHGIDENT from 1201-protocol server"); - recvq.insert(0, ":" + target + " FIDENT " + line.substr(e) + "\n"); + // If the last parameter is prefixed by a colon then it's a userlist which may have 0 or more users; + // if it isn't, then it is a single member + std::string::size_type spcolon = line.find(" :"); + if (spcolon != std::string::npos) + { + spcolon++; + // Loop while there is a ':' in the userlist, this is never true if the channel is empty + std::string::size_type pos = std::string::npos; + while ((pos = line.rfind(':', pos-1)) > spcolon) + { + // Find the next space after the ':' + std::string::size_type sp = line.find(' ', pos); + // Erase characters between the ':' and the next space after it, including the ':' but not the space; + // if there is no next space, everything will be erased between pos and the end of the line + line.erase(pos, sp-pos); + } + } + else + { + // Last parameter is a single member + std::string::size_type sp = line.rfind(' '); + std::string::size_type colon = line.find(':', sp); + line.erase(colon); + } } + else if (command == "KICK") + { + // Strip membership id if the KICK has one + if (b == std::string::npos) + return; + + std::string::size_type c = line.find(' ', b + 1); + if (c == std::string::npos) + return; - Command* thiscmd = ServerInstance->Parser->GetHandler(subcmd); - if (thiscmd && subcmd != "WHOISNOTICE") + std::string::size_type d = line.find(' ', c + 1); + if ((d < line.size()-1) && (original_line[d+1] != ':')) + { + // There is a third parameter which doesn't begin with a colon, erase it + std::string::size_type e = line.find(' ', d + 1); + line.erase(d, e-d); + } + } + else if (command == "SINFO") { - Version ver = thiscmd->creator->GetVersion(); - if (ver.Flags & VF_OPTCOMMON) + // :22D SINFO version :InspIRCd-3.0 + // A B C + std::string::size_type c = line.find(' ', b + 1); + if (c == std::string::npos) + return; + + // Only translating SINFO version, discard everything else + if (line.compare(b, 9, " version ", 9)) + return; + + line = line.substr(0, 5) + "VERSION" + line.substr(c); + } + else if (command == "SERVER") + { + // :001 SERVER inspircd.test 002 [<anything> ...] :description + // A B C + std::string::size_type c = line.find(' ', b + 1); + if (c == std::string::npos) + return; + + std::string::size_type d = c + 4; + std::string::size_type spcolon = line.find(" :", d); + if (spcolon == std::string::npos) + return; + + line.erase(d, spcolon-d); + line.insert(c, " * 0"); + + if (burstsent) { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Removing ENCAP on '%s' for 1201-protocol server", - subcmd.c_str()); - line.erase(a, c-a); + WriteLineNoCompat(line); + + // Synthesize a :<newserver> BURST <time> message + spcolon = line.find(" :"); + line = CmdBuilder(line.substr(spcolon-3, 3), "BURST").push_int(ServerInstance->Time()).str(); } } + else if (command == "NUM") + { + // :<sid> NUM <numeric source sid> <target uuid> <3 digit number> <params> + // Translate to + // :<sid> PUSH <target uuid> :<numeric source name> <3 digit number> <target nick> <params> + + TreeServer* const numericsource = Utils->FindServerID(line.substr(9, 3)); + if (!numericsource) + return; + + // The nick of the target is necessary for building the PUSH message + User* const target = ServerInstance->FindUUID(line.substr(13, UIDGenerator::UUID_LENGTH)); + if (!target) + return; + + std::string push = InspIRCd::Format(":%.*s PUSH %s ::%s %.*s %s", 3, line.c_str()+1, target->uuid.c_str(), numericsource->GetName().c_str(), 3, line.c_str()+23, target->nick.c_str()); + push.append(line, 26, std::string::npos); + push.swap(line); + } } + WriteLineNoCompat(line); + return; } } - ServerInstance->Logs->Log("m_spanningtree", RAWIO, "S[%d] O %s", this->GetFd(), line.c_str()); - this->WriteData(line); - if (proto_version < 1202) - this->WriteData(wide_newline); - else - this->WriteData(newline); + WriteLineNoCompat(original_line); +} + +namespace +{ + bool InsertCurrentChannelTS(CommandBase::Params& params, unsigned int chanindex = 0, unsigned int pos = 1) + { + Channel* chan = ServerInstance->FindChan(params[chanindex]); + if (!chan) + return false; + + // Insert the current TS of the channel after the pos-th parameter + params.insert(params.begin()+pos, ConvToStr(chan->age)); + return true; + } +} + +bool TreeSocket::PreProcessOldProtocolMessage(User*& who, std::string& cmd, CommandBase::Params& params) +{ + if ((cmd == "METADATA") && (params.size() >= 3) && (params[0][0] == '#')) + { + // :20D METADATA #channel extname :extdata + return InsertCurrentChannelTS(params); + } + else if ((cmd == "FTOPIC") && (params.size() >= 4)) + { + // :20D FTOPIC #channel 100 Attila :topic text + return InsertCurrentChannelTS(params); + } + else if ((cmd == "PING") || (cmd == "PONG")) + { + if (params.size() == 1) + { + // If it's a PING with 1 parameter, reply with a PONG now, if it's a PONG with 1 parameter (weird), do nothing + if (cmd[1] == 'I') + this->WriteData(":" + ServerInstance->Config->GetSID() + " PONG " + params[0] + newline); + + // Don't process this message further + return false; + } + + // :20D PING 20D 22D + // :20D PONG 20D 22D + // Drop the first parameter + params.erase(params.begin()); + + // If the target is a server name, translate it to a SID + if (!InspIRCd::IsSID(params[0])) + { + TreeServer* server = Utils->FindServer(params[0]); + if (!server) + { + // We've no idea what this is, log and stop processing + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Received a " + cmd + " with an unknown target: \"" + params[0] + "\", command dropped"); + return false; + } + + params[0] = server->GetID(); + } + } + else if ((cmd == "GLINE") || (cmd == "KLINE") || (cmd == "ELINE") || (cmd == "ZLINE") || (cmd == "QLINE")) + { + // Fix undocumented protocol usage: translate GLINE, ZLINE, etc. into ADDLINE or DELLINE + if ((params.size() != 1) && (params.size() != 3)) + return false; + + CommandBase::Params p; + p.push_back(cmd.substr(0, 1)); + p.push_back(params[0]); + + if (params.size() == 3) + { + cmd = "ADDLINE"; + p.push_back(who->nick); + p.push_back(ConvToStr(ServerInstance->Time())); + p.push_back(ConvToStr(InspIRCd::Duration(params[1]))); + p.push_back(params[2]); + } + else + cmd = "DELLINE"; + + params.swap(p); + } + else if (cmd == "SVSMODE") + { + cmd = "MODE"; + } + else if (cmd == "OPERQUIT") + { + // Translate OPERQUIT into METADATA + if (params.empty()) + return false; + + cmd = "METADATA"; + params.insert(params.begin(), who->uuid); + params.insert(params.begin()+1, "operquit"); + who = MyRoot->ServerUser; + } + else if ((cmd == "TOPIC") && (params.size() >= 2)) + { + // :20DAAAAAC TOPIC #chan :new topic + cmd = "FTOPIC"; + if (!InsertCurrentChannelTS(params)) + return false; + + params.insert(params.begin()+2, ConvToStr(ServerInstance->Time())); + } + else if (cmd == "MODENOTICE") + { + // MODENOTICE is always supported by 2.0 but it's optional in 3.0. + params.insert(params.begin(), "*"); + params.insert(params.begin()+1, cmd); + cmd = "ENCAP"; + } + else if (cmd == "RULES") + { + return false; + } + else if (cmd == "INVITE") + { + // :20D INVITE 22DAAABBB #chan + // :20D INVITE 22DAAABBB #chan 123456789 + // Insert channel timestamp after the channel name; the 3rd parameter, if there, is the invite expiration time + return InsertCurrentChannelTS(params, 1, 2); + } + else if (cmd == "VERSION") + { + // :20D VERSION :InspIRCd-2.0 + // change to + // :20D SINFO version :InspIRCd-2.0 + cmd = "SINFO"; + params.insert(params.begin(), "version"); + } + else if (cmd == "JOIN") + { + // 2.0 allows and forwards legacy JOINs but we don't, so translate them to FJOINs before processing + if ((params.size() != 1) || (IS_SERVER(who))) + return false; // Huh? + + cmd = "FJOIN"; + Channel* chan = ServerInstance->FindChan(params[0]); + params.push_back(ConvToStr(chan ? chan->age : ServerInstance->Time())); + params.push_back("+"); + params.push_back(","); + params.back().append(who->uuid); + who = TreeServer::Get(who)->ServerUser; + } + else if ((cmd == "FMODE") && (params.size() >= 2)) + { + // Translate user mode changes with timestamp to MODE + if (params[0][0] != '#') + { + User* user = ServerInstance->FindUUID(params[0]); + if (!user) + return false; + + // Emulate the old nonsensical behavior + if (user->age < ServerCommand::ExtractTS(params[1])) + return false; + + cmd = "MODE"; + params.erase(params.begin()+1); + } + } + else if ((cmd == "SERVER") && (params.size() > 4)) + { + // This does not affect the initial SERVER line as it is sent before the link state is CONNECTED + // :20D SERVER <name> * 0 <sid> <desc> + // change to + // :20D SERVER <name> <sid> <desc> + + params[1].swap(params[3]); + params.erase(params.begin()+2, params.begin()+4); + + // If the source of this SERVER message is not bursting, then new servers it introduces are bursting + TreeServer* server = TreeServer::Get(who); + if (!server->IsBursting()) + params.insert(params.begin()+2, "burst=" + ConvToStr(((uint64_t)ServerInstance->Time())*1000)); + } + else if (cmd == "BURST") + { + // A server is introducing another one, drop unnecessary BURST + return false; + } + else if (cmd == "SVSWATCH") + { + // SVSWATCH was removed because nothing was using it, but better be sure + return false; + } + else if (cmd == "PUSH") + { + if ((params.size() != 2) || (!this->MyRoot)) + return false; // Huh? + + irc::tokenstream ts(params.back()); + + std::string srcstr; + ts.GetMiddle(srcstr); + srcstr.erase(0, 1); + + std::string token; + ts.GetMiddle(token); + + // See if it's a numeric being sent to the target via PUSH + unsigned int numeric_number = 0; + if (token.length() == 3) + numeric_number = ConvToInt(token); + + if ((numeric_number > 0) && (numeric_number < 1000)) + { + // It's a numeric, translate to NUM + + // srcstr must be a valid server name + TreeServer* const numericsource = Utils->FindServer(srcstr); + if (!numericsource) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unable to translate PUSH numeric %s to user %s from 1202 protocol server %s: source \"%s\" doesn't exist", token.c_str(), params[0].c_str(), this->MyRoot->GetName().c_str(), srcstr.c_str()); + return false; + } + + cmd = "NUM"; + + // Second parameter becomes the target uuid + params[0].swap(params[1]); + // Replace first param (now the PUSH payload, not needed) with the source sid + params[0] = numericsource->GetID(); + + params.push_back(InspIRCd::Format("%03u", numeric_number)); + + // Ignore the nickname in the numeric in PUSH + ts.GetMiddle(token); + + // Rest of the tokens are the numeric parameters, add them to NUM + while (ts.GetTrailing(token)) + params.push_back(token); + } + else if ((token == "PRIVMSG") || (token == "NOTICE")) + { + // Command is a PRIVMSG/NOTICE + cmd.swap(token); + + // Check if the PRIVMSG/NOTICE target is a nickname + ts.GetMiddle(token); + if (token.c_str()[0] == '#') + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unable to translate PUSH %s to user %s from 1202 protocol server %s, target \"%s\"", cmd.c_str(), params[0].c_str(), this->MyRoot->GetName().c_str(), token.c_str()); + return false; + } + + // Replace second parameter with the message + ts.GetTrailing(params[1]); + } + else + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Unable to translate PUSH to user %s from 1202 protocol server %s", params[0].c_str(), this->MyRoot->GetName().c_str()); + return false; + } + + return true; + } + + return true; // Passthru } diff --git a/src/modules/m_spanningtree/delline.cpp b/src/modules/m_spanningtree/delline.cpp index 540ca5079..c64bec654 100644 --- a/src/modules/m_spanningtree/delline.cpp +++ b/src/modules/m_spanningtree/delline.cpp @@ -20,38 +20,18 @@ #include "inspircd.h" #include "xline.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" +#include "commands.h" -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - - -bool TreeSocket::DelLine(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandDelLine::Handle(User* user, Params& params) { - if (params.size() < 2) - return true; - - std::string setter = "<unknown>"; - - User* user = ServerInstance->FindNick(prefix); - if (user) - setter = user->nick; - else - { - TreeServer* t = Utils->FindServer(prefix); - if (t) - setter = t->GetName(); - } + const std::string& setter = user->nick; - - /* NOTE: No check needed on 'user', this function safely handles NULL */ + // XLineManager::DelLine() returns true if the xline existed, false if it didn't if (ServerInstance->XLines->DelLine(params[1].c_str(), params[0], user)) { ServerInstance->SNO->WriteToSnoMask('X',"%s removed %s%s on %s", setter.c_str(), params[0].c_str(), params[0].length() == 1 ? "-line" : "", params[1].c_str()); - Utils->DoOneToAllButSender(prefix,"DELLINE", params, prefix); + return CMD_SUCCESS; } - return true; + return CMD_FAILURE; } - diff --git a/src/modules/m_spanningtree/encap.cpp b/src/modules/m_spanningtree/encap.cpp index dabfc086b..4bc321065 100644 --- a/src/modules/m_spanningtree/encap.cpp +++ b/src/modules/m_spanningtree/encap.cpp @@ -18,32 +18,39 @@ #include "inspircd.h" -#include "xline.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" +#include "commands.h" +#include "main.h" /** ENCAP */ -void TreeSocket::Encap(User* who, parameterlist ¶ms) +CmdResult CommandEncap::Handle(User* user, Params& params) { - if (params.size() > 1) + if (ServerInstance->Config->GetSID() == params[0] || InspIRCd::Match(ServerInstance->Config->ServerName, params[0])) { - if (ServerInstance->Config->GetSID() == params[0] || InspIRCd::Match(ServerInstance->Config->ServerName, params[0])) - { - parameterlist plist(params.begin() + 2, params.end()); - ServerInstance->Parser->CallHandler(params[1], plist, who); - // discard return value, ENCAP shall succeed even if the command does not exist - } - - params[params.size() - 1] = ":" + params[params.size() - 1]; + CommandBase::Params plist(params.begin() + 2, params.end()); - if (params[0].find_first_of("*?") != std::string::npos) + // XXX: Workaround for SVS* commands provided by spanningtree not being registered in the core + if ((params[1] == "SVSNICK") || (params[1] == "SVSJOIN") || (params[1] == "SVSPART")) { - Utils->DoOneToAllButSender(who->uuid, "ENCAP", params, who->server); + ServerCommand* const scmd = Utils->Creator->CmdManager.GetHandler(params[1]); + if (scmd) + scmd->Handle(user, plist); + return CMD_SUCCESS; } - else - Utils->DoOneToOne(who->uuid, "ENCAP", params, params[0]); + + Command* cmd = NULL; + ServerInstance->Parser.CallHandler(params[1], plist, user, &cmd); + // Discard return value, ENCAP shall succeed even if the command does not exist + + if ((cmd) && (cmd->force_manual_route)) + return CMD_FAILURE; } + return CMD_SUCCESS; } +RouteDescriptor CommandEncap::GetRouting(User* user, const Params& params) +{ + if (params[0].find_first_of("*?") != std::string::npos) + return ROUTE_BROADCAST; + return ROUTE_UNICAST(params[0]); +} diff --git a/src/modules/m_spanningtree/fjoin.cpp b/src/modules/m_spanningtree/fjoin.cpp index 4ec6e1dbb..a6c52e41b 100644 --- a/src/modules/m_spanningtree/fjoin.cpp +++ b/src/modules/m_spanningtree/fjoin.cpp @@ -25,11 +25,26 @@ #include "treeserver.h" #include "treesocket.h" +/** FJOIN builder for rebuilding incoming FJOINs and splitting them up into multiple messages if necessary + */ +class FwdFJoinBuilder : public CommandFJoin::Builder +{ + TreeServer* const sourceserver; + + public: + FwdFJoinBuilder(Channel* chan, TreeServer* server) + : CommandFJoin::Builder(chan, server) + , sourceserver(server) + { + } + + void add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend); +}; + /** FJOIN, almost identical to TS6 SJOIN, except for nicklist handling. */ -CmdResult CommandFJoin::Handle(const std::vector<std::string>& params, User *srcuser) +CmdResult CommandFJoin::Handle(User* srcuser, Params& params) { - SpanningTreeUtilities* Utils = ((ModuleSpanningTree*)(Module*)creator)->Utils; - /* 1.1 FJOIN works as follows: + /* 1.1+ FJOIN works as follows: * * Each FJOIN is sent along with a timestamp, and the side with the lowest * timestamp 'wins'. From this point on we will refer to this side as the @@ -54,204 +69,277 @@ CmdResult CommandFJoin::Handle(const std::vector<std::string>& params, User *src * The winning side on the other hand will ignore all user modes from the * losing side, so only its own modes get applied. Life is simple for those * who succeed at internets. :-) + * + * Outside of netbursts, the winning side also resyncs the losing side if it + * detects that the other side recreated the channel. + * + * Syntax: + * :<sid> FJOIN <chan> <TS> <modes> :[<member> [<member> ...]] + * The last parameter is a list consisting of zero or more channel members + * (permanent channels may have zero users). Each entry on the list is in the + * following format: + * [[<modes>,]<uuid>[:<membid>] + * <modes> is a concatenation of the mode letters the user has on the channel + * (e.g.: "ov" if the user is opped and voiced). The order of the mode letters + * are not important but if a server ecounters an unknown mode letter, it will + * drop the link to avoid desync. + * + * InspIRCd 2.0 and older required a comma before the uuid even if the user + * had no prefix modes on the channel, InspIRCd 3.0 and later does not require + * a comma in this case anymore. + * + * <membid> is a positive integer representing the id of the membership. + * If not present (in FJOINs coming from pre-1205 servers), 0 is assumed. + * + * Forwarding: + * FJOIN messages are forwarded with the new TS and modes. Prefix modes of + * members on the losing side are not forwarded. + * This is required to only have one server on each side of the network who + * decides the fate of a channel during a network merge. Otherwise, if the + * clock of a server is slightly off it may make a different decision than + * the rest of the network and desync. + * The prefix modes are always forwarded as-is, or not at all. + * One incoming FJOIN may result in more than one FJOIN being generated + * and forwarded mainly due to compatibility reasons with non-InspIRCd + * servers that don't handle more than 512 char long lines. + * + * Forwarding examples: + * Existing channel #chan with TS 1000, modes +n. + * Incoming: :220 FJOIN #chan 1000 +t :o,220AAAAAB:0 + * Forwarded: :220 FJOIN #chan 1000 +nt :o,220AAAAAB:0 + * Merge modes and forward the result. Forward their prefix modes as well. + * + * Existing channel #chan with TS 1000, modes +nt. + * Incoming: :220 FJOIN #CHAN 2000 +i :ov,220AAAAAB:0 o,220AAAAAC:20 + * Forwarded: :220 FJOIN #chan 1000 +nt :,220AAAAAB:0 ,220AAAAAC:20 + * Drop their modes, forward our modes and TS, use our channel name + * capitalization. Don't forward prefix modes. + * */ - irc::modestacker modestack(true); /* Modes to apply from the users in the user list */ - User* who = NULL; /* User we are currently checking */ - std::string channel = params[0]; /* Channel name, as a string */ - time_t TS = atoi(params[1].c_str()); /* Timestamp given to us for remote side */ - irc::tokenstream users((params.size() > 3) ? params[params.size() - 1] : ""); /* users from the user list */ - bool apply_other_sides_modes = true; /* True if we are accepting the other side's modes */ - Channel* chan = ServerInstance->FindChan(channel); /* The channel we're sending joins to */ - bool created = !chan; /* True if the channel doesnt exist here yet */ - std::string item; /* One item in the list of nicks */ - - TreeServer* src_server = Utils->FindServer(srcuser->server); - TreeSocket* src_socket = src_server->GetRoute()->GetSocket(); + time_t TS = ServerCommand::ExtractTS(params[1]); - if (!TS) - { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"*** BUG? *** TS of 0 sent to FJOIN. Are some services authors smoking craq, or is it 1970 again?. Dropped."); - ServerInstance->SNO->WriteToSnoMask('d', "WARNING: The server %s is sending FJOIN with a TS of zero. Total craq. Command was dropped.", srcuser->server.c_str()); - return CMD_INVALID; - } + const std::string& channel = params[0]; + Channel* chan = ServerInstance->FindChan(channel); + bool apply_other_sides_modes = true; + TreeServer* const sourceserver = TreeServer::Get(srcuser); - if (created) + if (!chan) { chan = new Channel(channel, TS); - ServerInstance->SNO->WriteToSnoMask('d', "Creation FJOIN received for %s, timestamp: %lu", chan->name.c_str(), (unsigned long)TS); } else { time_t ourTS = chan->age; - if (TS != ourTS) - ServerInstance->SNO->WriteToSnoMask('d', "Merge FJOIN received for %s, ourTS: %lu, TS: %lu, difference: %ld", - chan->name.c_str(), (unsigned long)ourTS, (unsigned long)TS, (long)(ourTS - TS)); - /* If our TS is less than theirs, we dont accept their modes */ - if (ourTS < TS) { - ServerInstance->SNO->WriteToSnoMask('d', "NOT Applying modes from other side"); - apply_other_sides_modes = false; - } - else if (ourTS > TS) - { - /* Our TS greater than theirs, clear all our modes from the channel, accept theirs. */ - ServerInstance->SNO->WriteToSnoMask('d', "Removing our modes, accepting remote"); - parameterlist param_list; - if (Utils->AnnounceTSChange) - chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :TS for %s changed from %lu to %lu", chan->name.c_str(), channel.c_str(), (unsigned long) ourTS, (unsigned long) TS); - // while the name is equal in case-insensitive compare, it might differ in case; use the remote version - chan->name = channel; - chan->age = TS; - chan->ClearInvites(); - param_list.push_back(channel); - this->RemoveStatus(ServerInstance->FakeClient, param_list); - - // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it. - // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then - // deleted later) as soon as the permchan mode is removed from them. - if (ServerInstance->FindChan(channel) == NULL) + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Merge FJOIN received for %s, ourTS: %lu, TS: %lu, difference: %ld", + chan->name.c_str(), (unsigned long)ourTS, (unsigned long)TS, (long)(ourTS - TS)); + /* If our TS is less than theirs, we dont accept their modes */ + if (ourTS < TS) { - chan = new Channel(channel, TS); + // If the source server isn't bursting then this FJOIN is the result of them recreating the channel with a higher TS. + // This happens if the last user on the channel hops and before the PART propagates a user on another server joins. Fix it by doing a resync. + // Servers behind us won't react this way because the forwarded FJOIN will have the correct TS. + if (!sourceserver->IsBursting()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Server %s recreated channel %s with higher TS, resyncing", sourceserver->GetName().c_str(), chan->name.c_str()); + sourceserver->GetSocket()->SyncChannel(chan); + } + apply_other_sides_modes = false; + } + else if (ourTS > TS) + { + // Our TS is greater than theirs, remove all modes, extensions, etc. from the channel + LowerTS(chan, TS, channel); + + // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it. + // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then + // deleted later) as soon as the permchan mode is removed from them. + if (ServerInstance->FindChan(channel) == NULL) + { + chan = new Channel(channel, TS); + } } } - // The silent case here is ourTS == TS, we don't need to remove modes here, just to merge them later on. } - /* First up, apply their modes if they won the TS war */ + // Apply their channel modes if we have to + Modes::ChangeList modechangelist; if (apply_other_sides_modes) { - // Need to use a modestacker here due to maxmodes - irc::modestacker stack(true); - std::vector<std::string>::const_iterator paramit = params.begin() + 3; - const std::vector<std::string>::const_iterator lastparamit = ((params.size() > 3) ? (params.end() - 1) : params.end()); - for (std::string::const_iterator i = params[2].begin(); i != params[2].end(); ++i) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL); - if (!mh) - continue; + ServerInstance->Modes.ModeParamsToChangeList(srcuser, MODETYPE_CHANNEL, params, modechangelist, 2, params.size() - 1); + ServerInstance->Modes->Process(srcuser, chan, NULL, modechangelist, ModeParser::MODE_LOCALONLY | ModeParser::MODE_MERGE); + // Reuse for prefix modes + modechangelist.clear(); + } - std::string modeparam; - if ((paramit != lastparamit) && (mh->GetNumParams(true))) - { - modeparam = *paramit; - ++paramit; - } + // Build a new FJOIN for forwarding. Put the correct TS in it and the current modes of the channel + // after applying theirs. If they lost, the prefix modes from their message are not forwarded. + FwdFJoinBuilder fwdfjoin(chan, sourceserver); - stack.Push(*i, modeparam); - } + // Process every member in the message + irc::spacesepstream users(params.back()); + std::string item; + Modes::ChangeList* modechangelistptr = (apply_other_sides_modes ? &modechangelist : NULL); + while (users.GetToken(item)) + { + ProcessModeUUIDPair(item, sourceserver, chan, modechangelistptr, fwdfjoin); + } - std::vector<std::string> modelist; + fwdfjoin.finalize(); + fwdfjoin.Forward(sourceserver->GetRoute()); - // Mode parser needs to know what channel to act on. - modelist.push_back(params[0]); + // Set prefix modes on their users if we lost the FJOIN or had equal TS + if (apply_other_sides_modes) + ServerInstance->Modes->Process(srcuser, chan, NULL, modechangelist, ModeParser::MODE_LOCALONLY); - while (stack.GetStackedLine(modelist)) - { - ServerInstance->Modes->Process(modelist, srcuser, true); - modelist.erase(modelist.begin() + 1, modelist.end()); - } + return CMD_SUCCESS; +} + +void CommandFJoin::ProcessModeUUIDPair(const std::string& item, TreeServer* sourceserver, Channel* chan, Modes::ChangeList* modechangelist, FwdFJoinBuilder& fwdfjoin) +{ + std::string::size_type comma = item.find(','); - ServerInstance->Modes->Process(modelist, srcuser, true); + // Comma not required anymore if the user has no modes + const std::string::size_type ubegin = (comma == std::string::npos ? 0 : comma+1); + std::string uuid(item, ubegin, UIDGenerator::UUID_LENGTH); + User* who = ServerInstance->FindUUID(uuid); + if (!who) + { + // Probably KILLed, ignore + return; } - /* Now, process every 'modes,nick' pair */ - while (users.GetToken(item)) + TreeSocket* src_socket = sourceserver->GetSocket(); + /* Check that the user's 'direction' is correct */ + TreeServer* route_back_again = TreeServer::Get(who); + if (route_back_again->GetSocket() != src_socket) { - const char* usr = item.c_str(); - if (usr && *usr) + return; + } + + std::string::const_iterator modeendit = item.begin(); // End of the "ov" mode string + /* Check if the user received at least one mode */ + if ((modechangelist) && (comma != std::string::npos)) + { + modeendit += comma; + /* Iterate through the modes and see if they are valid here, if so, apply */ + for (std::string::const_iterator i = item.begin(); i != modeendit; ++i) { - const char* unparsedmodes = usr; - std::string modes; + ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL); + if (!mh) + throw ProtocolException("Unrecognised mode '" + std::string(1, *i) + "'"); + /* Add any modes this user had to the mode stack */ + modechangelist->push_add(mh, who->nick); + } + } - /* Iterate through all modes for this user and check they are valid. */ - while ((*unparsedmodes) && (*unparsedmodes != ',')) - { - ModeHandler *mh = ServerInstance->Modes->FindMode(*unparsedmodes, MODETYPE_CHANNEL); - if (!mh) - { - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised mode %c, dropping link", *unparsedmodes); - return CMD_INVALID; - } + Membership* memb = chan->ForceJoin(who, NULL, sourceserver->IsBursting()); + if (!memb) + { + // User was already on the channel, forward because of the modes they potentially got + memb = chan->GetUser(who); + if (memb) + fwdfjoin.add(memb, item.begin(), modeendit); + return; + } - modes += *unparsedmodes; - usr++; - unparsedmodes++; - } + // Assign the id to the new Membership + Membership::Id membid = 0; + const std::string::size_type colon = item.rfind(':'); + if (colon != std::string::npos) + membid = Membership::IdFromString(item.substr(colon+1)); + memb->id = membid; - /* Advance past the comma, to the nick */ - usr++; + // Add member to fwdfjoin with prefix modes + fwdfjoin.add(memb, item.begin(), modeendit); +} - /* Check the user actually exists */ - who = ServerInstance->FindUUID(usr); - if (who) - { - /* Check that the user's 'direction' is correct */ - TreeServer* route_back_again = Utils->BestRouteTo(who->server); - if ((!route_back_again) || (route_back_again->GetSocket() != src_socket)) - continue; +void CommandFJoin::RemoveStatus(Channel* c) +{ + Modes::ChangeList changelist; - /* Add any modes this user had to the mode stack */ - for (std::string::iterator x = modes.begin(); x != modes.end(); ++x) - modestack.Push(*x, who->nick); + const ModeParser::ModeHandlerMap& mhs = ServerInstance->Modes->GetModes(MODETYPE_CHANNEL); + for (ModeParser::ModeHandlerMap::const_iterator i = mhs.begin(); i != mhs.end(); ++i) + { + ModeHandler* mh = i->second; - Channel::JoinUser(who, channel.c_str(), true, "", src_server->bursting, TS); - } - else - { - ServerInstance->Logs->Log("m_spanningtree",SPARSE, "Ignored nonexistent user %s in fjoin to %s (probably quit?)", usr, channel.c_str()); - continue; - } - } + // Add the removal of this mode to the changelist. This handles all kinds of modes, including prefix modes. + mh->RemoveMode(c, changelist); } - /* Flush mode stacker if we lost the FJOIN or had equal TS */ - if (apply_other_sides_modes) - { - parameterlist stackresult; - stackresult.push_back(channel); + ServerInstance->Modes->Process(ServerInstance->FakeClient, c, NULL, changelist, ModeParser::MODE_LOCALONLY); +} - while (modestack.GetStackedLine(stackresult)) - { - ServerInstance->SendMode(stackresult, srcuser); - stackresult.erase(stackresult.begin() + 1, stackresult.end()); - } - } - return CMD_SUCCESS; +void CommandFJoin::LowerTS(Channel* chan, time_t TS, const std::string& newname) +{ + if (Utils->AnnounceTSChange) + chan->WriteNotice(InspIRCd::Format("Creation time of %s changed from %s to %s", newname.c_str(), + InspIRCd::TimeString(chan->age).c_str(), InspIRCd::TimeString(TS).c_str())); + + // While the name is equal in case-insensitive compare, it might differ in case; use the remote version + chan->name = newname; + chan->age = TS; + + // Clear all modes + CommandFJoin::RemoveStatus(chan); + + // Unset all extensions + chan->FreeAllExtItems(); + + // Clear the topic + chan->SetTopic(ServerInstance->FakeClient, std::string(), 0); + chan->setby.clear(); } -void CommandFJoin::RemoveStatus(User* srcuser, parameterlist ¶ms) +CommandFJoin::Builder::Builder(Channel* chan, TreeServer* source) + : CmdBuilder(source->GetID(), "FJOIN") { - if (params.size() < 1) - return; + push(chan->name).push_int(chan->age).push_raw(" +"); + pos = str().size(); + push_raw(chan->ChanModes(true)).push_raw(" :"); +} - Channel* c = ServerInstance->FindChan(params[0]); +void CommandFJoin::Builder::add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend) +{ + push_raw(mbegin, mend).push_raw(',').push_raw(memb->user->uuid); + push_raw(':').push_raw_int(memb->id); + push_raw(' '); +} - if (c) - { - irc::modestacker stack(false); - parameterlist stackresult; - stackresult.push_back(c->name); +bool CommandFJoin::Builder::has_room(std::string::size_type nummodes) const +{ + return ((str().size() + nummodes + UIDGenerator::UUID_LENGTH + 2 + membid_max_digits + 1) <= maxline); +} - for (char modeletter = 'A'; modeletter <= 'z'; ++modeletter) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL); - - /* Passing a pointer to a modestacker here causes the mode to be put onto the mode stack, - * rather than applied immediately. Module unloads require this to be done immediately, - * for this function we require tidyness instead. Fixes bug #493 - */ - if (mh) - mh->RemoveMode(c, &stack); - } +void CommandFJoin::Builder::clear() +{ + content.erase(pos); + push_raw(" :"); +} - while (stack.GetStackedLine(stackresult)) - { - ServerInstance->SendMode(stackresult, srcuser); - stackresult.erase(stackresult.begin() + 1, stackresult.end()); - } - } +const std::string& CommandFJoin::Builder::finalize() +{ + if (*content.rbegin() == ' ') + content.erase(content.size()-1); + return str(); } +void FwdFJoinBuilder::add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend) +{ + // Pseudoserver compatibility: + // Some pseudoservers do not handle lines longer than 512 so we split long FJOINs into multiple messages. + // The forwarded FJOIN can end up being longer than the original one if we have more modes set and won, for example. + + // Check if the member fits into the current message. If not, send it and prepare a new one. + if (!has_room(std::distance(mbegin, mend))) + { + finalize(); + Forward(sourceserver); + clear(); + } + // Add the member and their modes exactly as they sent them + CommandFJoin::Builder::add(memb, mbegin, mend); +} diff --git a/src/modules/m_spanningtree/fmode.cpp b/src/modules/m_spanningtree/fmode.cpp index c1e452db6..a15b5ddc2 100644 --- a/src/modules/m_spanningtree/fmode.cpp +++ b/src/modules/m_spanningtree/fmode.cpp @@ -21,73 +21,35 @@ #include "inspircd.h" #include "commands.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - -/** FMODE command - server mode with timestamp checks */ -CmdResult CommandFMode::Handle(const std::vector<std::string>& params, User *who) +/** FMODE command - channel mode change with timestamp checks */ +CmdResult CommandFMode::Handle(User* who, Params& params) { - std::string sourceserv = who->server; - - std::vector<std::string> modelist; - time_t TS = 0; - for (unsigned int q = 0; (q < params.size()) && (q < 64); q++) - { - if (q == 1) - { - /* The timestamp is in this position. - * We don't want to pass that up to the - * server->client protocol! - */ - TS = atoi(params[q].c_str()); - } - else - { - /* Everything else is fine to append to the modelist */ - modelist.push_back(params[q]); - } + time_t TS = ServerCommand::ExtractTS(params[1]); - } - /* Extract the TS value of the object, either User or Channel */ - User* dst = ServerInstance->FindNick(params[0]); - Channel* chan = NULL; - time_t ourTS = 0; + Channel* const chan = ServerInstance->FindChan(params[0]); + if (!chan) + // Channel doesn't exist + return CMD_FAILURE; - if (dst) - { - ourTS = dst->age; - } - else - { - chan = ServerInstance->FindChan(params[0]); - if (chan) - { - ourTS = chan->age; - } - else - /* Oops, channel doesnt exist! */ - return CMD_FAILURE; - } + // Extract the TS of the channel in question + time_t ourTS = chan->age; - if (!TS) - { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"*** BUG? *** TS of 0 sent to FMODE. Are some services authors smoking craq, or is it 1970 again?. Dropped."); - ServerInstance->SNO->WriteToSnoMask('d', "WARNING: The server %s is sending FMODE with a TS of zero. Total craq. Mode was dropped.", sourceserv.c_str()); - return CMD_INVALID; - } - - /* TS is equal or less: Merge the mode changes into ours and pass on. + /* If the TS is greater than ours, we drop the mode and don't pass it anywhere. */ - if (TS <= ourTS) - { - bool merge = (TS == ourTS) && IS_SERVER(who); - ServerInstance->Modes->Process(modelist, who, merge); - return CMD_SUCCESS; - } - /* If the TS is greater than ours, we drop the mode and dont pass it anywhere. + if (TS > ourTS) + return CMD_FAILURE; + + /* TS is equal or less: apply the mode change locally and forward the message */ - return CMD_FAILURE; -} + // Turn modes into a Modes::ChangeList; may have more elements than max modes + Modes::ChangeList changelist; + ServerInstance->Modes.ModeParamsToChangeList(who, MODETYPE_CHANNEL, params, changelist, 2); + + ModeParser::ModeProcessFlag flags = ModeParser::MODE_LOCALONLY; + if ((TS == ourTS) && IS_SERVER(who)) + flags |= ModeParser::MODE_MERGE; + ServerInstance->Modes->Process(who, chan, NULL, changelist, flags); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/ftopic.cpp b/src/modules/m_spanningtree/ftopic.cpp index d559c6ae5..01826e8f6 100644 --- a/src/modules/m_spanningtree/ftopic.cpp +++ b/src/modules/m_spanningtree/ftopic.cpp @@ -21,31 +21,69 @@ #include "inspircd.h" #include "commands.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - /** FTOPIC command */ -CmdResult CommandFTopic::Handle(const std::vector<std::string>& params, User *user) +CmdResult CommandFTopic::Handle(User* user, Params& params) { - time_t ts = atoi(params[1].c_str()); Channel* c = ServerInstance->FindChan(params[0]); - if (c) + if (!c) + return CMD_FAILURE; + + if (c->age < ServerCommand::ExtractTS(params[1])) + // Our channel TS is older, nothing to do + return CMD_FAILURE; + + // Channel::topicset is initialized to 0 on channel creation, so their ts will always win if we never had a topic + time_t ts = ServerCommand::ExtractTS(params[2]); + if (ts < c->topicset) + return CMD_FAILURE; + + // The topic text is always the last parameter + const std::string& newtopic = params.back(); + + // If there is a setter in the message use that, otherwise use the message source + const std::string& setter = ((params.size() > 4) ? params[3] : (ServerInstance->Config->FullHostInTopic ? user->GetFullHost() : user->nick)); + + /* + * If the topics were updated at the exact same second, accept + * the remote only when it's "bigger" than ours as defined by + * string comparision, so non-empty topics always overridde + * empty topics if their timestamps are equal + * + * Similarly, if the topic texts are equal too, keep one topic + * setter and discard the other + */ + if (ts == c->topicset) { - if ((ts >= c->topicset) || (c->topic.empty())) - { - if (c->topic != params[3]) - { - // Update topic only when it differs from current topic - c->topic.assign(params[3], 0, ServerInstance->Config->Limits.MaxTopic); - c->WriteChannel(user, "TOPIC %s :%s", c->name.c_str(), c->topic.c_str()); - } - - // Always update setter and settime. - c->setby.assign(params[2], 0, 127); - c->topicset = ts; - } + // Discard if their topic text is "smaller" + if (c->topic > newtopic) + return CMD_FAILURE; + + // If the texts are equal in addition to the timestamps, decide which setter to keep + if ((c->topic == newtopic) && (c->setby >= setter)) + return CMD_FAILURE; } + + c->SetTopic(user, newtopic, ts, &setter); return CMD_SUCCESS; } +// Used when bursting and in reply to RESYNC, contains topic setter as the 4th parameter +CommandFTopic::Builder::Builder(Channel* chan) + : CmdBuilder("FTOPIC") +{ + push(chan->name); + push_int(chan->age); + push_int(chan->topicset); + push(chan->setby); + push_last(chan->topic); +} + +// Used when changing the topic, the setter is the message source +CommandFTopic::Builder::Builder(User* user, Channel* chan) + : CmdBuilder(user, "FTOPIC") +{ + push(chan->name); + push_int(chan->age); + push_int(chan->topicset); + push_last(chan->topic); +} diff --git a/src/modules/m_spanningtree/hmac.cpp b/src/modules/m_spanningtree/hmac.cpp index d990e1fbf..2001d560d 100644 --- a/src/modules/m_spanningtree/hmac.cpp +++ b/src/modules/m_spanningtree/hmac.cpp @@ -19,18 +19,12 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "../hash.h" -#include "../ssl.h" -#include "socketengine.h" +#include "modules/hash.h" +#include "modules/ssl.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" #include "link.h" #include "treesocket.h" -#include "resolvers.h" const std::string& TreeSocket::GetOurChallenge() { @@ -57,44 +51,15 @@ std::string TreeSocket::MakePass(const std::string &password, const std::string /* This is a simple (maybe a bit hacky?) HMAC algorithm, thanks to jilles for * suggesting the use of HMAC to secure the password against various attacks. * - * Note: If m_sha256.so is not loaded, we MUST fall back to plaintext with no + * Note: If an sha256 provider is not available, we MUST fall back to plaintext with no * HMAC challenge/response. */ HashProvider* sha256 = ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"); - if (Utils->ChallengeResponse && sha256 && !challenge.empty()) - { - if (proto_version < 1202) - { - /* This is how HMAC is done in InspIRCd 1.2: - * - * sha256( (pass xor 0x5c) + sha256((pass xor 0x36) + m) ) - * - * 5c and 36 were chosen as part of the HMAC standard, because they - * flip the bits in a way likely to strengthen the function. - */ - std::string hmac1, hmac2; - - for (size_t n = 0; n < password.length(); n++) - { - hmac1.push_back(static_cast<char>(password[n] ^ 0x5C)); - hmac2.push_back(static_cast<char>(password[n] ^ 0x36)); - } - - hmac2.append(challenge); - hmac2 = sha256->hexsum(hmac2); - - std::string hmac = hmac1 + hmac2; - hmac = sha256->hexsum(hmac); - - return "HMAC-SHA256:"+ hmac; - } - else - { - return "AUTH:" + BinToBase64(sha256->hmac(password, challenge)); - } - } - else if (!challenge.empty() && !sha256) - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Not authenticating to server using SHA256/HMAC because we don't have m_sha256 loaded!"); + if (sha256 && !challenge.empty()) + return "AUTH:" + BinToBase64(sha256->hmac(password, challenge)); + + if (!challenge.empty() && !sha256) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Not authenticating to server using SHA256/HMAC because we don't have an SHA256 provider (e.g. m_sha256) loaded!"); return password; } @@ -104,13 +69,16 @@ bool TreeSocket::ComparePass(const Link& link, const std::string &theirs) capab->auth_fingerprint = !link.Fingerprint.empty(); capab->auth_challenge = !capab->ourchallenge.empty() && !capab->theirchallenge.empty(); - std::string fp; - if (GetIOHook()) + std::string fp = SSLClientCert::GetFingerprint(this); + if (capab->auth_fingerprint) { - SocketCertificateRequest req(this, Utils->Creator); - if (req.cert) + /* Require fingerprint to exist and match */ + if (link.Fingerprint != fp) { - fp = req.cert->GetFingerprint(); + ServerInstance->SNO->WriteToSnoMask('l',"Invalid SSL certificate fingerprint on link %s: need \"%s\" got \"%s\"", + link.Name.c_str(), link.Fingerprint.c_str(), fp.c_str()); + SendError("Invalid SSL certificate fingerprint " + fp + " - expected " + link.Fingerprint); + return false; } } @@ -118,32 +86,24 @@ bool TreeSocket::ComparePass(const Link& link, const std::string &theirs) { std::string our_hmac = MakePass(link.RecvPass, capab->ourchallenge); - /* Straight string compare of hashes */ - if (our_hmac != theirs) + // Use the timing-safe compare function to compare the hashes + if (!InspIRCd::TimingSafeCompare(our_hmac, theirs)) return false; } else { - /* Straight string compare of plaintext */ - if (link.RecvPass != theirs) + // Use the timing-safe compare function to compare the passwords + if (!InspIRCd::TimingSafeCompare(link.RecvPass, theirs)) return false; } - if (capab->auth_fingerprint) + // Tell opers to set up fingerprint verification if it's not already set up and the SSL mod gave us a fingerprint + // this time + if ((!capab->auth_fingerprint) && (!fp.empty())) { - /* Require fingerprint to exist and match */ - if (link.Fingerprint != fp) - { - ServerInstance->SNO->WriteToSnoMask('l',"Invalid SSL fingerprint on link %s: need \"%s\" got \"%s\"", - link.Name.c_str(), link.Fingerprint.c_str(), fp.c_str()); - SendError("Provided invalid SSL fingerprint " + fp + " - expected " + link.Fingerprint); - return false; - } - } - else if (!fp.empty()) - { - ServerInstance->SNO->WriteToSnoMask('l', "SSL fingerprint for link %s is \"%s\". " + ServerInstance->SNO->WriteToSnoMask('l', "SSL certificate fingerprint for link %s is \"%s\". " "You can improve security by specifying this in <link:fingerprint>.", link.Name.c_str(), fp.c_str()); } + return true; } diff --git a/src/modules/m_spanningtree/idle.cpp b/src/modules/m_spanningtree/idle.cpp index 18aeb0ad5..11e665531 100644 --- a/src/modules/m_spanningtree/idle.cpp +++ b/src/modules/m_spanningtree/idle.cpp @@ -18,67 +18,53 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" - -#include "main.h" #include "utils.h" -#include "treeserver.h" -#include "treesocket.h" +#include "commands.h" -bool TreeSocket::Whois(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandIdle::HandleRemote(RemoteUser* issuer, Params& params) { - if (params.size() < 1) - return true; - User* u = ServerInstance->FindNick(prefix); - if (u) + /** + * There are two forms of IDLE: request and reply. Requests have one parameter, + * replies have more than one. + * + * If this is a request, 'issuer' did a /whois and its server wants to learn the + * idle time of the user in params[0]. + * + * If this is a reply, params[0] is the user who did the whois and params.back() is + * the number of seconds 'issuer' has been idle. + */ + + User* target = ServerInstance->FindUUID(params[0]); + if ((!target) || (target->registered != REG_ALL)) + return CMD_FAILURE; + + LocalUser* localtarget = IS_LOCAL(target); + if (!localtarget) { - // an incoming request - if (params.size() == 1) - { - User* x = ServerInstance->FindNick(params[0]); - if ((x) && (IS_LOCAL(x))) - { - long idle = labs((long)((x->idle_lastmsg) - ServerInstance->Time())); - parameterlist par; - par.push_back(prefix); - par.push_back(ConvToStr(x->signon)); - par.push_back(ConvToStr(idle)); - // ours, we're done, pass it BACK - Utils->DoOneToOne(params[0], "IDLE", par, u->server); - } - else - { - // not ours pass it on - if (x) - Utils->DoOneToOne(prefix, "IDLE", params, x->server); - } - } - else if (params.size() == 3) - { - std::string who_did_the_whois = params[0]; - User* who_to_send_to = ServerInstance->FindNick(who_did_the_whois); - if ((who_to_send_to) && (IS_LOCAL(who_to_send_to)) && (who_to_send_to->registered == REG_ALL)) - { - // an incoming reply to a whois we sent out - std::string nick_whoised = prefix; - unsigned long signon = atoi(params[1].c_str()); - unsigned long idle = atoi(params[2].c_str()); - if ((who_to_send_to) && (IS_LOCAL(who_to_send_to))) - { - ServerInstance->DoWhois(who_to_send_to, u, signon, idle, nick_whoised.c_str()); - } - } - else - { - // not ours, pass it on - if (who_to_send_to) - Utils->DoOneToOne(prefix, "IDLE", params, who_to_send_to->server); - } - } + // Forward to target's server + return CMD_SUCCESS; } - return true; -} + if (params.size() >= 2) + { + ServerInstance->Parser.CallHandler("WHOIS", params, issuer); + } + else + { + // A server is asking us the idle time of our user + unsigned int idle; + if (localtarget->idle_lastmsg >= ServerInstance->Time()) + // Possible case when our clock ticked backwards + idle = 0; + else + idle = ((unsigned int) (ServerInstance->Time() - localtarget->idle_lastmsg)); + + CmdBuilder reply(params[0], "IDLE"); + reply.push_back(issuer->uuid); + reply.push_back(ConvToStr(target->signon)); + reply.push_back(ConvToStr(idle)); + reply.Unicast(issuer); + } + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/ijoin.cpp b/src/modules/m_spanningtree/ijoin.cpp new file mode 100644 index 000000000..d33ef3d4e --- /dev/null +++ b/src/modules/m_spanningtree/ijoin.cpp @@ -0,0 +1,75 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2012-2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "commands.h" +#include "utils.h" +#include "treeserver.h" +#include "treesocket.h" + +CmdResult CommandIJoin::HandleRemote(RemoteUser* user, Params& params) +{ + Channel* chan = ServerInstance->FindChan(params[0]); + if (!chan) + { + // Desync detected, recover + // Ignore the join and send RESYNC, this will result in the remote server sending all channel data to us + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received IJOIN for non-existant channel: " + params[0]); + + CmdBuilder("RESYNC").push(params[0]).Unicast(user); + + return CMD_FAILURE; + } + + bool apply_modes; + if (params.size() > 3) + { + time_t RemoteTS = ServerCommand::ExtractTS(params[2]); + apply_modes = (RemoteTS <= chan->age); + } + else + apply_modes = false; + + // Join the user and set the membership id to what they sent + Membership* memb = chan->ForceJoin(user, apply_modes ? ¶ms[3] : NULL); + if (!memb) + return CMD_FAILURE; + + memb->id = Membership::IdFromString(params[1]); + return CMD_SUCCESS; +} + +CmdResult CommandResync::HandleServer(TreeServer* server, CommandBase::Params& params) +{ + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Resyncing " + params[0]); + Channel* chan = ServerInstance->FindChan(params[0]); + if (!chan) + { + // This can happen for a number of reasons, safe to ignore + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Channel does not exist"); + return CMD_FAILURE; + } + + if (!server->IsLocal()) + throw ProtocolException("RESYNC from a server that is not directly connected"); + + // Send all known information about the channel + server->GetSocket()->SyncChannel(chan); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/link.h b/src/modules/m_spanningtree/link.h index 797f108d8..5b9361fcd 100644 --- a/src/modules/m_spanningtree/link.h +++ b/src/modules/m_spanningtree/link.h @@ -18,23 +18,22 @@ */ -#ifndef M_SPANNINGTREE_LINK_H -#define M_SPANNINGTREE_LINK_H +#pragma once class Link : public refcountbase { public: reference<ConfigTag> tag; - irc::string Name; + std::string Name; std::string IPAddr; - int Port; + unsigned int Port; std::string SendPass; std::string RecvPass; std::string Fingerprint; - std::string AllowMask; + std::vector<std::string> AllowMasks; bool HiddenFromStats; std::string Hook; - int Timeout; + unsigned int Timeout; std::string Bind; bool Hidden; Link(ConfigTag* Tag) : tag(Tag) {} @@ -51,5 +50,3 @@ class Autoconnect : public refcountbase int position; Autoconnect(ConfigTag* Tag) : tag(Tag) {} }; - -#endif diff --git a/src/modules/m_spanningtree/main.cpp b/src/modules/m_spanningtree/main.cpp index 78d202c47..0ff180a83 100644 --- a/src/modules/m_spanningtree/main.cpp +++ b/src/modules/m_spanningtree/main.cpp @@ -21,13 +21,12 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" #include "socket.h" #include "xline.h" +#include "iohook.h" +#include "modules/server.h" -#include "cachetimer.h" #include "resolvers.h" #include "main.h" #include "utils.h" @@ -35,60 +34,71 @@ #include "link.h" #include "treesocket.h" #include "commands.h" -#include "protocolinterface.h" +#include "translate.h" ModuleSpanningTree::ModuleSpanningTree() - : KeepNickTS(false) + : Away::EventListener(this) + , Stats::EventListener(this) + , rconnect(this) + , rsquit(this) + , map(this) + , commands(this) + , currmembid(0) + , eventprov(this, "event/server") + , DNS(this, "DNS") + , loopCall(false) { - Utils = new SpanningTreeUtilities(this); - commands = new SpanningTreeCommands(this); - RefreshTimer = NULL; } SpanningTreeCommands::SpanningTreeCommands(ModuleSpanningTree* module) - : rconnect(module, module->Utils), rsquit(module, module->Utils), - svsjoin(module), svspart(module), svsnick(module), metadata(module), - uid(module), opertype(module), fjoin(module), fmode(module), ftopic(module), - fhost(module), fident(module), fname(module) + : svsjoin(module), svspart(module), svsnick(module), metadata(module), + uid(module), opertype(module), fjoin(module), ijoin(module), resync(module), + fmode(module), ftopic(module), fhost(module), fident(module), fname(module), + away(module), addline(module), delline(module), encap(module), idle(module), + nick(module), ping(module), pong(module), save(module), + server(module), squit(module), snonotice(module), + endburst(module), sinfo(module), num(module) { } +namespace +{ + void SetLocalUsersServer(Server* newserver) + { + // Does not change the server of quitting users because those are not in the list + + ServerInstance->FakeClient->server = newserver; + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + (*i)->server = newserver; + } + + void ResetMembershipIds() + { + // Set all membership ids to 0 + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* user = *i; + for (User::ChanList::iterator j = user->chans.begin(); j != user->chans.end(); ++j) + (*j)->id = 0; + } + } +} + void ModuleSpanningTree::init() { - ServerInstance->Modules->AddService(commands->rconnect); - ServerInstance->Modules->AddService(commands->rsquit); - ServerInstance->Modules->AddService(commands->svsjoin); - ServerInstance->Modules->AddService(commands->svspart); - ServerInstance->Modules->AddService(commands->svsnick); - ServerInstance->Modules->AddService(commands->metadata); - ServerInstance->Modules->AddService(commands->uid); - ServerInstance->Modules->AddService(commands->opertype); - ServerInstance->Modules->AddService(commands->fjoin); - ServerInstance->Modules->AddService(commands->fmode); - ServerInstance->Modules->AddService(commands->ftopic); - ServerInstance->Modules->AddService(commands->fhost); - ServerInstance->Modules->AddService(commands->fident); - ServerInstance->Modules->AddService(commands->fname); - RefreshTimer = new CacheRefreshTimer(Utils); - ServerInstance->Timers->AddTimer(RefreshTimer); - - Implementation eventlist[] = - { - I_OnPreCommand, I_OnGetServerDescription, I_OnUserInvite, I_OnPostTopicChange, - I_OnWallops, I_OnUserNotice, I_OnUserMessage, I_OnBackgroundTimer, I_OnUserJoin, - I_OnChangeHost, I_OnChangeName, I_OnChangeIdent, I_OnUserPart, I_OnUnloadModule, - I_OnUserQuit, I_OnUserPostNick, I_OnUserKick, I_OnRemoteKill, I_OnRehash, I_OnPreRehash, - I_OnOper, I_OnAddLine, I_OnDelLine, I_OnMode, I_OnLoadModule, I_OnStats, - I_OnSetAway, I_OnPostCommand, I_OnUserConnect, I_OnAcceptConnection - }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - delete ServerInstance->PI; - ServerInstance->PI = new SpanningTreeProtocolInterface(Utils); - loopCall = false; - - // update our local user count - Utils->TreeRoot->SetUserCount(ServerInstance->Users->LocalUserCount()); + ServerInstance->SNO->EnableSnomask('l', "LINK"); + + ResetMembershipIds(); + + Utils = new SpanningTreeUtilities(this); + Utils->TreeRoot = new TreeServer; + + ServerInstance->PI = &protocolinterface; + + delete ServerInstance->FakeClient->server; + SetLocalUsersServer(Utils->TreeRoot); } void ModuleSpanningTree::ShowLinks(TreeServer* Current, User* user, int hops) @@ -98,44 +108,39 @@ void ModuleSpanningTree::ShowLinks(TreeServer* Current, User* user, int hops) { Parent = Current->GetParent()->GetName(); } - for (unsigned int q = 0; q < Current->ChildCount(); q++) + + const TreeServer::ChildServers& children = Current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = children.begin(); i != children.end(); ++i) { - if ((Current->GetChild(q)->Hidden) || ((Utils->HideULines) && (ServerInstance->ULine(Current->GetChild(q)->GetName())))) + TreeServer* server = *i; + if ((server->Hidden) || ((Utils->HideULines) && (server->IsULine()))) { - if (IS_OPER(user)) + if (user->IsOper()) { - ShowLinks(Current->GetChild(q),user,hops+1); + ShowLinks(server, user, hops+1); } } else { - ShowLinks(Current->GetChild(q),user,hops+1); + ShowLinks(server, user, hops+1); } } /* Don't display the line if its a uline, hide ulines is on, and the user isnt an oper */ - if ((Utils->HideULines) && (ServerInstance->ULine(Current->GetName())) && (!IS_OPER(user))) + if ((Utils->HideULines) && (Current->IsULine()) && (!user->IsOper())) return; /* Or if the server is hidden and they're not an oper */ - else if ((Current->Hidden) && (!IS_OPER(user))) + else if ((Current->Hidden) && (!user->IsOper())) return; - std::string servername = Current->GetName(); - user->WriteNumeric(364, "%s %s %s :%d %s", user->nick.c_str(), servername.c_str(), - (Utils->FlatLinks && (!IS_OPER(user))) ? ServerInstance->Config->ServerName.c_str() : Parent.c_str(), - (Utils->FlatLinks && (!IS_OPER(user))) ? 0 : hops, - Current->GetDesc().c_str()); + user->WriteNumeric(RPL_LINKS, Current->GetName(), + (((Utils->FlatLinks) && (!user->IsOper())) ? ServerInstance->Config->ServerName : Parent), + InspIRCd::Format("%d %s", (((Utils->FlatLinks) && (!user->IsOper())) ? 0 : hops), Current->GetDesc().c_str())); } -int ModuleSpanningTree::CountServs() -{ - return Utils->serverlist.size(); -} - -void ModuleSpanningTree::HandleLinks(const std::vector<std::string>& parameters, User* user) +void ModuleSpanningTree::HandleLinks(const CommandBase::Params& parameters, User* user) { ShowLinks(Utils->TreeRoot,user,0); - user->WriteNumeric(365, "%s * :End of /LINKS list.",user->nick.c_str()); - return; + user->WriteNumeric(RPL_ENDOFLINKS, '*', "End of /LINKS list."); } std::string ModuleSpanningTree::TimeToStr(time_t secs) @@ -152,79 +157,6 @@ std::string ModuleSpanningTree::TimeToStr(time_t secs) + ConvToStr(secs) + "s"); } -void ModuleSpanningTree::DoPingChecks(time_t curtime) -{ - /* - * Cancel remote burst mode on any servers which still have it enabled due to latency/lack of data. - * This prevents lost REMOTECONNECT notices - */ - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - -restart: - for (server_hash::iterator i = Utils->serverlist.begin(); i != Utils->serverlist.end(); i++) - { - TreeServer *s = i->second; - - if (s->GetSocket() && s->GetSocket()->GetLinkState() == DYING) - { - s->GetSocket()->Close(); - goto restart; - } - - // Fix for bug #792, do not ping servers that are not connected yet! - // Remote servers have Socket == NULL and local connected servers have - // Socket->LinkState == CONNECTED - if (s->GetSocket() && s->GetSocket()->GetLinkState() != CONNECTED) - continue; - - // Now do PING checks on all servers - TreeServer *mts = Utils->BestRouteTo(s->GetID()); - - if (mts) - { - // Only ping if this server needs one - if (curtime >= s->NextPingTime()) - { - // And if they answered the last - if (s->AnsweredLastPing()) - { - // They did, send a ping to them - s->SetNextPingTime(curtime + Utils->PingFreq); - TreeSocket *tsock = mts->GetSocket(); - - // ... if we can find a proper route to them - if (tsock) - { - tsock->WriteLine(":" + ServerInstance->Config->GetSID() + " PING " + - ServerInstance->Config->GetSID() + " " + s->GetID()); - s->LastPingMsec = ts; - } - } - else - { - // They didn't answer the last ping, if they are locally connected, get rid of them. - TreeSocket *sock = s->GetSocket(); - if (sock) - { - sock->SendError("Ping timeout"); - sock->Close(); - goto restart; - } - } - } - - // If warn on ping enabled and not warned and the difference is sufficient and they didn't answer the last ping... - if ((Utils->PingWarnTime) && (!s->Warned) && (curtime >= s->NextPingTime() - (Utils->PingFreq - Utils->PingWarnTime)) && (!s->AnsweredLastPing())) - { - /* The server hasnt responded, send a warning to opers */ - std::string servername = s->GetName(); - ServerInstance->SNO->WriteToSnoMask('l',"Server \002%s\002 has not responded to PING for %d seconds, high latency.", servername.c_str(), Utils->PingWarnTime); - s->Warned = true; - } - } - } -} - void ModuleSpanningTree::ConnectServer(Autoconnect* a, bool on_timer) { if (!a) @@ -266,14 +198,21 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y) { bool ipvalid = true; - if (InspIRCd::Match(ServerInstance->Config->ServerName, assign(x->Name), rfc_case_insensitive_map)) + if (InspIRCd::Match(ServerInstance->Config->ServerName, x->Name, ascii_case_insensitive_map)) { ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Not connecting to myself."); return; } - QueryType start_type = DNS_QUERY_AAAA; - if (strchr(x->IPAddr.c_str(),':')) +#ifndef _WIN32 + if (x->IPAddr.find('/') != std::string::npos) + { + struct stat sb; + if (stat(x->IPAddr.c_str(), &sb) == -1 || !S_ISSOCK(sb.st_mode)) + ipvalid = false; + } +#endif + if (x->IPAddr.find(':') != std::string::npos) { in6_addr n; if (inet_pton(AF_INET6, x->IPAddr.c_str(), &n) < 1) @@ -282,15 +221,15 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y) else { in_addr n; - if (inet_aton(x->IPAddr.c_str(),&n) < 1) + if (inet_pton(AF_INET, x->IPAddr.c_str(),&n) < 1) ipvalid = false; } /* Do we already have an IP? If so, no need to resolve it. */ if (ipvalid) { - /* Gave a hook, but it wasnt one we know */ - TreeSocket* newsocket = new TreeSocket(Utils, x, y, x->IPAddr); + // Create a TreeServer object that will start connecting immediately in the background + TreeSocket* newsocket = new TreeSocket(x, y, x->IPAddr); if (newsocket->GetFd() > -1) { /* Handled automatically on success */ @@ -302,17 +241,30 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y) ServerInstance->GlobalCulls.AddItem(newsocket); } } + else if (!DNS) + { + ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: Hostname given and core_dns is not loaded, unable to resolve.", x->Name.c_str()); + } else { + // Guess start_type from bindip aftype + DNS::QueryType start_type = DNS::QUERY_AAAA; + irc::sockets::sockaddrs bind; + if ((!x->Bind.empty()) && (irc::sockets::aptosa(x->Bind, 0, bind))) + { + if (bind.family() == AF_INET) + start_type = DNS::QUERY_A; + } + + ServernameResolver* snr = new ServernameResolver(*DNS, x->IPAddr, x, start_type, y); try { - bool cached = false; - ServernameResolver* snr = new ServernameResolver(Utils, x->IPAddr, x, cached, start_type, y); - ServerInstance->AddResolver(snr, cached); + DNS->Process(snr); } - catch (ModuleException& e) + catch (DNS::Exception& e) { - ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: %s.",x->Name.c_str(), e.GetReason()); + delete snr; + ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: %s.",x->Name.c_str(), e.GetReason().c_str()); ConnectServer(y, false); } } @@ -333,12 +285,12 @@ void ModuleSpanningTree::AutoConnectServers(time_t curtime) void ModuleSpanningTree::DoConnectTimeout(time_t curtime) { - std::map<TreeSocket*, std::pair<std::string, int> >::iterator i = Utils->timeoutlist.begin(); + SpanningTreeUtilities::TimeoutList::iterator i = Utils->timeoutlist.begin(); while (i != Utils->timeoutlist.end()) { TreeSocket* s = i->first; - std::pair<std::string, int> p = i->second; - std::map<TreeSocket*, std::pair<std::string, int> >::iterator me = i; + std::pair<std::string, unsigned int> p = i->second; + SpanningTreeUtilities::TimeoutList::iterator me = i; i++; if (s->GetLinkState() == DYING) { @@ -347,233 +299,139 @@ void ModuleSpanningTree::DoConnectTimeout(time_t curtime) } else if (curtime > s->age + p.second) { - ServerInstance->SNO->WriteToSnoMask('l',"CONNECT: Error connecting \002%s\002 (timeout of %d seconds)",p.first.c_str(),p.second); + ServerInstance->SNO->WriteToSnoMask('l',"CONNECT: Error connecting \002%s\002 (timeout of %u seconds)",p.first.c_str(),p.second); Utils->timeoutlist.erase(me); s->Close(); } } } -ModResult ModuleSpanningTree::HandleVersion(const std::vector<std::string>& parameters, User* user) +ModResult ModuleSpanningTree::HandleVersion(const CommandBase::Params& parameters, User* user) { - // we've already checked if pcnt > 0, so this is safe + // We've already confirmed that !parameters.empty(), so this is safe TreeServer* found = Utils->FindServerMask(parameters[0]); if (found) { - std::string Version = found->GetVersion(); - user->WriteNumeric(351, "%s :%s",user->nick.c_str(),Version.c_str()); if (found == Utils->TreeRoot) { - ServerInstance->Config->Send005(user); + // Pass to default VERSION handler. + return MOD_RES_PASSTHRU; } + + // If an oper wants to see the version then show the full version string instead of the normal, + // but only if it is non-empty. + // If it's empty it might be that the server is still syncing (full version hasn't arrived yet) + // or the server is a 2.0 server and does not send a full version. + bool showfull = ((user->IsOper()) && (!found->GetFullVersion().empty())); + const std::string& Version = (showfull ? found->GetFullVersion() : found->GetVersion()); + user->WriteNumeric(RPL_VERSION, Version); } else { - user->WriteNumeric(402, "%s %s :No such server",user->nick.c_str(),parameters[0].c_str()); + user->WriteNumeric(ERR_NOSUCHSERVER, parameters[0], "No such server"); } return MOD_RES_DENY; } -/* This method will attempt to get a message to a remote user. - */ -void ModuleSpanningTree::RemoteMessage(User* user, const char* format, ...) -{ - char text[MAXBUF]; - va_list argsPtr; - - va_start(argsPtr, format); - vsnprintf(text, MAXBUF, format, argsPtr); - va_end(argsPtr); - - if (IS_LOCAL(user)) - user->WriteServ("NOTICE %s :%s", user->nick.c_str(), text); - else - ServerInstance->PI->SendUserNotice(user, text); -} - -ModResult ModuleSpanningTree::HandleConnect(const std::vector<std::string>& parameters, User* user) +ModResult ModuleSpanningTree::HandleConnect(const CommandBase::Params& parameters, User* user) { for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) { Link* x = *i; - if (InspIRCd::Match(x->Name.c_str(),parameters[0], rfc_case_insensitive_map)) + if (InspIRCd::Match(x->Name, parameters[0], ascii_case_insensitive_map)) { - if (InspIRCd::Match(ServerInstance->Config->ServerName, assign(x->Name), rfc_case_insensitive_map)) + if (InspIRCd::Match(ServerInstance->Config->ServerName, x->Name, ascii_case_insensitive_map)) { - RemoteMessage(user, "*** CONNECT: Server \002%s\002 is ME, not connecting.",x->Name.c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: Server \002%s\002 is ME, not connecting.", x->Name.c_str())); return MOD_RES_DENY; } - TreeServer* CheckDupe = Utils->FindServer(x->Name.c_str()); + TreeServer* CheckDupe = Utils->FindServer(x->Name); if (!CheckDupe) { - RemoteMessage(user, "*** CONNECT: Connecting to server: \002%s\002 (%s:%d)",x->Name.c_str(),(x->HiddenFromStats ? "<hidden>" : x->IPAddr.c_str()),x->Port); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: Connecting to server: \002%s\002 (%s:%d)", x->Name.c_str(), (x->HiddenFromStats ? "<hidden>" : x->IPAddr.c_str()), x->Port)); ConnectServer(x); return MOD_RES_DENY; } else { - std::string servername = CheckDupe->GetParent()->GetName(); - RemoteMessage(user, "*** CONNECT: Server \002%s\002 already exists on the network and is connected via \002%s\002", x->Name.c_str(), servername.c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: Server \002%s\002 already exists on the network and is connected via \002%s\002", x->Name.c_str(), CheckDupe->GetParent()->GetName().c_str())); return MOD_RES_DENY; } } } - RemoteMessage(user, "*** CONNECT: No server matching \002%s\002 could be found in the config file.",parameters[0].c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: No server matching \002%s\002 could be found in the config file.", parameters[0].c_str())); return MOD_RES_DENY; } -void ModuleSpanningTree::OnGetServerDescription(const std::string &servername,std::string &description) -{ - TreeServer* s = Utils->FindServer(servername); - if (s) - { - description = s->GetDesc(); - } -} - -void ModuleSpanningTree::OnUserInvite(User* source,User* dest,Channel* channel, time_t expiry) +void ModuleSpanningTree::OnUserInvite(User* source, User* dest, Channel* channel, time_t expiry, unsigned int notifyrank, CUList& notifyexcepts) { if (IS_LOCAL(source)) { - parameterlist params; + CmdBuilder params(source, "INVITE"); params.push_back(dest->uuid); params.push_back(channel->name); + params.push_int(channel->age); params.push_back(ConvToStr(expiry)); - Utils->DoOneToMany(source->uuid,"INVITE",params); + params.Broadcast(); } } -void ModuleSpanningTree::OnPostTopicChange(User* user, Channel* chan, const std::string &topic) -{ - // Drop remote events on the floor. - if (!IS_LOCAL(user)) - return; - - parameterlist params; - params.push_back(chan->name); - params.push_back(":"+topic); - Utils->DoOneToMany(user->uuid,"TOPIC",params); -} - -void ModuleSpanningTree::OnWallops(User* user, const std::string &text) +ModResult ModuleSpanningTree::OnPreTopicChange(User* user, Channel* chan, const std::string& topic) { - if (IS_LOCAL(user)) + // XXX: Deny topic changes if the current topic set time is the current time or is in the future because + // other servers will drop our FTOPIC. This restriction will be removed when the protocol is updated. + if ((chan->topicset >= ServerInstance->Time()) && (Utils->serverlist.size() > 1)) { - parameterlist params; - params.push_back(":"+text); - Utils->DoOneToMany(user->uuid,"WALLOPS",params); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, "Retry topic change later"); + return MOD_RES_DENY; } + return MOD_RES_PASSTHRU; } -void ModuleSpanningTree::OnUserNotice(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list) +void ModuleSpanningTree::OnPostTopicChange(User* user, Channel* chan, const std::string &topic) { - /* Server origin */ - if (user == NULL) + // Drop remote events on the floor. + if (!IS_LOCAL(user)) return; - if (target_type == TYPE_USER) - { - User* d = (User*)dest; - if (!IS_LOCAL(d) && IS_LOCAL(user)) - { - parameterlist params; - params.push_back(d->uuid); - params.push_back(":"+text); - Utils->DoOneToOne(user->uuid,"NOTICE",params,d->server); - } - } - else if (target_type == TYPE_CHANNEL) - { - if (IS_LOCAL(user)) - { - Channel *c = (Channel*)dest; - if (c) - { - std::string cname = c->name; - if (status) - cname = status + cname; - TreeServerList list; - Utils->GetListOfServersForChannel(c,list,status,exempt_list); - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (Sock) - Sock->WriteLine(":"+std::string(user->uuid)+" NOTICE "+cname+" :"+text); - } - } - } - } - else if (target_type == TYPE_SERVER) - { - if (IS_LOCAL(user)) - { - char* target = (char*)dest; - parameterlist par; - par.push_back(target); - par.push_back(":"+text); - Utils->DoOneToMany(user->uuid,"NOTICE",par); - } - } + CommandFTopic::Builder(user, chan).Broadcast(); } -void ModuleSpanningTree::OnUserMessage(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list) +void ModuleSpanningTree::OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) { - /* Server origin */ - if (user == NULL) + if (!IS_LOCAL(user)) return; - if (target_type == TYPE_USER) + const char* message_type = (details.type == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); + if (target.type == MessageTarget::TYPE_USER) { - // route private messages which are targetted at clients only to the server - // which needs to receive them - User* d = (User*)dest; - if (!IS_LOCAL(d) && (IS_LOCAL(user))) + User* d = target.Get<User>(); + if (!IS_LOCAL(d)) { - parameterlist params; + CmdBuilder params(user, message_type); params.push_back(d->uuid); - params.push_back(":"+text); - Utils->DoOneToOne(user->uuid,"PRIVMSG",params,d->server); + params.push_last(details.text); + params.Unicast(d); } } - else if (target_type == TYPE_CHANNEL) + else if (target.type == MessageTarget::TYPE_CHANNEL) { - if (IS_LOCAL(user)) - { - Channel *c = (Channel*)dest; - if (c) - { - std::string cname = c->name; - if (status) - cname = status + cname; - TreeServerList list; - Utils->GetListOfServersForChannel(c,list,status,exempt_list); - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (Sock) - Sock->WriteLine(":"+std::string(user->uuid)+" PRIVMSG "+cname+" :"+text); - } - } - } + Utils->SendChannelMessage(user->uuid, target.Get<Channel>(), details.text, target.status, details.exemptions, message_type); } - else if (target_type == TYPE_SERVER) + else if (target.type == MessageTarget::TYPE_SERVER) { - if (IS_LOCAL(user)) - { - char* target = (char*)dest; - parameterlist par; - par.push_back(target); - par.push_back(":"+text); - Utils->DoOneToMany(user->uuid,"PRIVMSG",par); - } + const std::string* serverglob = target.Get<std::string>(); + CmdBuilder par(user, message_type); + par.push_back(*serverglob); + par.push_last(details.text); + par.Broadcast(); } } void ModuleSpanningTree::OnBackgroundTimer(time_t curtime) { AutoConnectServers(curtime); - DoPingChecks(curtime); DoConnectTimeout(curtime); } @@ -582,25 +440,10 @@ void ModuleSpanningTree::OnUserConnect(LocalUser* user) if (user->quitting) return; - parameterlist params; - params.push_back(user->uuid); - params.push_back(ConvToStr(user->age)); - params.push_back(user->nick); - params.push_back(user->host); - params.push_back(user->dhost); - params.push_back(user->ident); - params.push_back(user->GetIPString()); - params.push_back(ConvToStr(user->signon)); - params.push_back("+"+std::string(user->FormatModes(true))); - params.push_back(":"+user->fullname); - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "UID", params); + CommandUID::Builder(user).Broadcast(); - if (IS_OPER(user)) - { - params.clear(); - params.push_back(user->oper->name); - Utils->DoOneToMany(user->uuid,"OPERTYPE",params); - } + if (user->IsOper()) + CommandOpertype::Builder(user).Broadcast(); for(Extensible::ExtensibleStore::const_iterator i = user->GetExtList().begin(); i != user->GetExtList().end(); i++) { @@ -610,23 +453,36 @@ void ModuleSpanningTree::OnUserConnect(LocalUser* user) ServerInstance->PI->SendMetaData(user, item->name, value); } - Utils->TreeRoot->SetUserCount(1); // increment by 1 + Utils->TreeRoot->UserCount++; } -void ModuleSpanningTree::OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) +void ModuleSpanningTree::OnUserJoin(Membership* memb, bool sync, bool created_by_local, CUList& excepts) { // Only do this for local users - if (IS_LOCAL(memb->user)) + if (!IS_LOCAL(memb->user)) + return; + + // Assign the current membership id to the new Membership and increase it + memb->id = currmembid++; + + if (created_by_local) + { + CommandFJoin::Builder params(memb->chan); + params.add(memb); + params.finalize(); + params.Broadcast(); + } + else { - parameterlist params; - // set up their permissions and the channel TS with FJOIN. - // All users are FJOINed now, because a module may specify - // new joining permissions for the user. + CmdBuilder params(memb->user, "IJOIN"); params.push_back(memb->chan->name); - params.push_back(ConvToStr(memb->chan->age)); - params.push_back(std::string("+") + memb->chan->ChanModes(true)); - params.push_back(memb->modes+","+memb->user->uuid); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FJOIN",params); + params.push_int(memb->id); + if (!memb->modes.empty()) + { + params.push_back(ConvToStr(memb->chan->age)); + params.push_back(memb->modes); + } + params.Broadcast(); } } @@ -635,19 +491,15 @@ void ModuleSpanningTree::OnChangeHost(User* user, const std::string &newhost) if (user->registered != REG_ALL || !IS_LOCAL(user)) return; - parameterlist params; - params.push_back(newhost); - Utils->DoOneToMany(user->uuid,"FHOST",params); + CmdBuilder(user, "FHOST").push(newhost).Broadcast(); } -void ModuleSpanningTree::OnChangeName(User* user, const std::string &gecos) +void ModuleSpanningTree::OnChangeRealName(User* user, const std::string& real) { if (user->registered != REG_ALL || !IS_LOCAL(user)) return; - parameterlist params; - params.push_back(":" + gecos); - Utils->DoOneToMany(user->uuid,"FNAME",params); + CmdBuilder(user, "FNAME").push_last(real).Broadcast(); } void ModuleSpanningTree::OnChangeIdent(User* user, const std::string &ident) @@ -655,101 +507,77 @@ void ModuleSpanningTree::OnChangeIdent(User* user, const std::string &ident) if ((user->registered != REG_ALL) || (!IS_LOCAL(user))) return; - parameterlist params; - params.push_back(ident); - Utils->DoOneToMany(user->uuid,"FIDENT",params); + CmdBuilder(user, "FIDENT").push(ident).Broadcast(); } void ModuleSpanningTree::OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) { if (IS_LOCAL(memb->user)) { - parameterlist params; + CmdBuilder params(memb->user, "PART"); params.push_back(memb->chan->name); if (!partmessage.empty()) - params.push_back(":"+partmessage); - Utils->DoOneToMany(memb->user->uuid,"PART",params); + params.push_last(partmessage); + params.Broadcast(); } } void ModuleSpanningTree::OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) { - if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) + if (IS_LOCAL(user)) { - parameterlist params; - if (oper_message != reason) + ServerInstance->PI->SendMetaData(user, "operquit", oper_message); + + CmdBuilder(user, "QUIT").push_last(reason).Broadcast(); + } + else + { + // Hide the message if one of the following is true: + // - User is being quit due to a netsplit and quietbursts is on + // - Server is a silent uline + TreeServer* server = TreeServer::Get(user); + bool hide = (((server->IsDead()) && (Utils->quiet_bursts)) || (server->IsSilentULine())); + if (!hide) { - params.push_back(":"+oper_message); - Utils->DoOneToMany(user->uuid,"OPERQUIT",params); + ServerInstance->SNO->WriteToSnoMask('Q', "Client exiting on server %s: %s (%s) [%s]", + user->server->GetName().c_str(), user->GetFullRealHost().c_str(), user->GetIPString().c_str(), oper_message.c_str()); } - params.clear(); - params.push_back(":"+reason); - Utils->DoOneToMany(user->uuid,"QUIT",params); } - // Regardless, We need to modify the user Counts.. - TreeServer* SourceServer = Utils->FindServer(user->server); - if (SourceServer) - { - SourceServer->SetUserCount(-1); // decrement by 1 - } + // Regardless, update the UserCount + TreeServer::Get(user)->UserCount--; } void ModuleSpanningTree::OnUserPostNick(User* user, const std::string &oldnick) { if (IS_LOCAL(user)) { - parameterlist params; + // The nick TS is updated by the core, we don't do it + CmdBuilder params(user, "NICK"); params.push_back(user->nick); - - /** IMPORTANT: We don't update the TS if the oldnick is just a case change of the newnick! - */ - if ((irc::string(user->nick.c_str()) != assign(oldnick)) && (!this->KeepNickTS)) - user->age = ServerInstance->Time(); - params.push_back(ConvToStr(user->age)); - Utils->DoOneToMany(user->uuid,"NICK",params); - this->KeepNickTS = false; + params.Broadcast(); } - else if (!loopCall && user->nick == user->uuid) + else if (!loopCall) { - parameterlist params; - params.push_back(user->uuid); - params.push_back(ConvToStr(user->age)); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"SAVE",params); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: Changed nick of remote user %s from %s to %s TS %lu by ourselves!", user->uuid.c_str(), oldnick.c_str(), user->nick.c_str(), (unsigned long) user->age); } } void ModuleSpanningTree::OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) { - parameterlist params; + if ((!IS_LOCAL(source)) && (source != ServerInstance->FakeClient)) + return; + + CmdBuilder params(source, "KICK"); params.push_back(memb->chan->name); params.push_back(memb->user->uuid); - params.push_back(":"+reason); - if (IS_LOCAL(source)) - { - Utils->DoOneToMany(source->uuid,"KICK",params); - } - else if (source == ServerInstance->FakeClient) - { - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"KICK",params); - } -} - -void ModuleSpanningTree::OnRemoteKill(User* source, User* dest, const std::string &reason, const std::string &operreason) -{ - if (!IS_LOCAL(source)) - return; // Only start routing if we're origin. - - ServerInstance->OperQuit.set(dest, operreason); - parameterlist params; - params.push_back(":"+operreason); - Utils->DoOneToMany(dest->uuid,"OPERQUIT",params); - params.clear(); - params.push_back(dest->uuid); - params.push_back(":"+reason); - Utils->DoOneToMany(source->uuid,"KILL",params); + // If a remote user is being kicked by us then send the membership id in the kick too + if (!IS_LOCAL(memb->user)) + params.push_int(memb->id); + params.push_last(reason); + params.Broadcast(); } void ModuleSpanningTree::OnPreRehash(User* user, const std::string ¶meter) @@ -757,19 +585,29 @@ void ModuleSpanningTree::OnPreRehash(User* user, const std::string ¶meter) if (loopCall) return; // Don't generate a REHASH here if we're in the middle of processing a message that generated this one - ServerInstance->Logs->Log("remoterehash", DEBUG, "called with param %s", parameter.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "OnPreRehash called with param %s", parameter.c_str()); // Send out to other servers if (!parameter.empty() && parameter[0] != '-') { - parameterlist params; + CmdBuilder params((user ? user->uuid : ServerInstance->Config->GetSID()), "REHASH"); params.push_back(parameter); - Utils->DoOneToAllButSender(user ? user->uuid : ServerInstance->Config->GetSID(), "REHASH", params, user ? user->server : ServerInstance->Config->ServerName); + params.Forward(user ? TreeServer::Get(user)->GetRoute() : NULL); } } -void ModuleSpanningTree::OnRehash(User* user) +void ModuleSpanningTree::ReadConfig(ConfigStatus& status) { + // Did this rehash change the description of this server? + const std::string& newdesc = ServerInstance->Config->ServerDesc; + if (newdesc != Utils->TreeRoot->GetDesc()) + { + // Broadcast a SINFO desc message to let the network know about the new description. This is the description + // string that is sent in the SERVER message initially and shown for example in WHOIS. + // We don't need to update the field itself in the Server object - the core does that. + CommandSInfo::Builder(Utils->TreeRoot, "desc", newdesc).Broadcast(); + } + // Re-read config stuff try { @@ -783,8 +621,8 @@ void ModuleSpanningTree::OnRehash(User* user) std::string msg = "Error in configuration: "; msg.append(e.GetReason()); ServerInstance->SNO->WriteToSnoMask('l', msg); - if (user && !IS_LOCAL(user)) - ServerInstance->PI->SendSNONotice("L", msg); + if (status.srcuser && !IS_LOCAL(status.srcuser)) + ServerInstance->PI->SendSNONotice('L', msg); } } @@ -799,24 +637,41 @@ void ModuleSpanningTree::OnLoadModule(Module* mod) data.push_back('='); data.append(v.link_data); } - ServerInstance->PI->SendMetaData(NULL, "modules", data); + ServerInstance->PI->SendMetaData("modules", data); } void ModuleSpanningTree::OnUnloadModule(Module* mod) { - ServerInstance->PI->SendMetaData(NULL, "modules", "-" + mod->ModuleSourceFile); + if (!Utils) + return; + ServerInstance->PI->SendMetaData("modules", "-" + mod->ModuleSourceFile); + + if (mod == this) + { + // We are being unloaded, inform modules about all servers splitting which cannot be done later when the servers are actually disconnected + const server_hash& servers = Utils->serverlist; + for (server_hash::const_iterator i = servers.begin(); i != servers.end(); ++i) + { + TreeServer* server = i->second; + if (!server->IsRoot()) + FOREACH_MOD_CUSTOM(GetEventProvider(), ServerEventListener, OnServerSplit, (server)); + } + return; + } + + // Some other module is being unloaded. If it provides an IOHook we use, we must close that server connection now. restart: - unsigned int items = Utils->TreeRoot->ChildCount(); - for(unsigned int x = 0; x < items; x++) + // Close all connections which use an IO hook provided by this module + const TreeServer::ChildServers& list = Utils->TreeRoot->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = list.begin(); i != list.end(); ++i) { - TreeServer* srv = Utils->TreeRoot->GetChild(x); - TreeSocket* sock = srv->GetSocket(); - if (sock && sock->GetIOHook() == mod) + TreeSocket* sock = (*i)->GetSocket(); + if (sock->GetModHook(mod)) { sock->SendError("SSL module unloaded"); sock->Close(); - // XXX: The list we're iterating is modified by TreeSocket::Squit() which is called by Close() + // XXX: The list we're iterating is modified by TreeServer::SQuit() which is called by Close() goto restart; } } @@ -824,169 +679,100 @@ restart: for (SpanningTreeUtilities::TimeoutList::const_iterator i = Utils->timeoutlist.begin(); i != Utils->timeoutlist.end(); ++i) { TreeSocket* sock = i->first; - if (sock->GetIOHook() == mod) + if (sock->GetModHook(mod)) sock->Close(); } } -// note: the protocol does not allow direct umode +o except -// via NICK with 8 params. sending OPERTYPE infers +o modechange -// locally. void ModuleSpanningTree::OnOper(User* user, const std::string &opertype) { if (user->registered != REG_ALL || !IS_LOCAL(user)) return; - parameterlist params; - params.push_back(opertype); - Utils->DoOneToMany(user->uuid,"OPERTYPE",params); + + // Note: The protocol does not allow direct umode +o; + // sending OPERTYPE infers +o modechange locally. + CommandOpertype::Builder(user).Broadcast(); } void ModuleSpanningTree::OnAddLine(User* user, XLine *x) { - if (!x->IsBurstable() || loopCall) + if (!x->IsBurstable() || loopCall || (user && !IS_LOCAL(user))) return; - parameterlist params; - params.push_back(x->type); - params.push_back(x->Displayable()); - params.push_back(x->source); - params.push_back(ConvToStr(x->set_time)); - params.push_back(ConvToStr(x->duration)); - params.push_back(":" + x->reason); - if (!user) - { - /* Server-set lines */ - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "ADDLINE", params); - } - else if (IS_LOCAL(user)) - { - /* User-set lines */ - Utils->DoOneToMany(user->uuid, "ADDLINE", params); - } + user = ServerInstance->FakeClient; + + CommandAddLine::Builder(x, user).Broadcast(); } void ModuleSpanningTree::OnDelLine(User* user, XLine *x) { - if (!x->IsBurstable() || loopCall) + if (!x->IsBurstable() || loopCall || (user && !IS_LOCAL(user))) return; - parameterlist params; - params.push_back(x->type); - params.push_back(x->Displayable()); - if (!user) - { - /* Server-unset lines */ - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "DELLINE", params); - } - else if (IS_LOCAL(user)) - { - /* User-unset lines */ - Utils->DoOneToMany(user->uuid, "DELLINE", params); - } -} - -void ModuleSpanningTree::OnMode(User* user, void* dest, int target_type, const parameterlist &text, const std::vector<TranslateType> &translate) -{ - if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) - { - parameterlist params; - std::string output_text; - - ServerInstance->Parser->TranslateUIDs(translate, text, output_text); + user = ServerInstance->FakeClient; - if (target_type == TYPE_USER) - { - User* u = (User*)dest; - params.push_back(u->uuid); - params.push_back(output_text); - Utils->DoOneToMany(user->uuid, "MODE", params); - } - else - { - Channel* c = (Channel*)dest; - params.push_back(c->name); - params.push_back(ConvToStr(c->age)); - params.push_back(output_text); - Utils->DoOneToMany(user->uuid, "FMODE", params); - } - } + CmdBuilder params(user, "DELLINE"); + params.push_back(x->type); + params.push_back(x->Displayable()); + params.Broadcast(); } -ModResult ModuleSpanningTree::OnSetAway(User* user, const std::string &awaymsg) +void ModuleSpanningTree::OnUserAway(User* user) { if (IS_LOCAL(user)) - { - parameterlist params; - if (!awaymsg.empty()) - { - params.push_back(ConvToStr(ServerInstance->Time())); - params.push_back(":" + awaymsg); - } - Utils->DoOneToMany(user->uuid, "AWAY", params); - } - - return MOD_RES_PASSTHRU; + CommandAway::Builder(user).Broadcast(); } -void ModuleSpanningTree::OnRequest(Request& request) +void ModuleSpanningTree::OnUserBack(User* user) { - if (!strcmp(request.id, "rehash")) - Utils->Rehash(); + if (IS_LOCAL(user)) + CommandAway::Builder(user).Broadcast(); } -void ModuleSpanningTree::ProtoSendMode(void* opaque, TargetTypeFlags target_type, void* target, const parameterlist &modeline, const std::vector<TranslateType> &translate) +void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) { - TreeSocket* s = (TreeSocket*)opaque; - std::string output_text; + if (processflags & ModeParser::MODE_LOCALONLY) + return; - ServerInstance->Parser->TranslateUIDs(translate, modeline, output_text); + if (u) + { + if (u->registered != REG_ALL) + return; - if (target) + CmdBuilder params(source, "MODE"); + params.push(u->uuid); + params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes)); + params.push_raw(Translate::ModeChangeListToParams(modes.getlist())); + params.Broadcast(); + } + else { - if (target_type == TYPE_USER) - { - User* u = (User*)target; - s->WriteLine(":"+ServerInstance->Config->GetSID()+" MODE "+u->uuid+" "+output_text); - } - else if (target_type == TYPE_CHANNEL) - { - Channel* c = (Channel*)target; - s->WriteLine(":"+ServerInstance->Config->GetSID()+" FMODE "+c->name+" "+ConvToStr(c->age)+" "+output_text); - } + CmdBuilder params(source, "FMODE"); + params.push(c->name); + params.push_int(c->age); + params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes)); + params.push_raw(Translate::ModeChangeListToParams(modes.getlist())); + params.Broadcast(); } } -void ModuleSpanningTree::ProtoSendMetaData(void* opaque, Extensible* target, const std::string &extname, const std::string &extdata) -{ - TreeSocket* s = static_cast<TreeSocket*>(opaque); - User* u = dynamic_cast<User*>(target); - Channel* c = dynamic_cast<Channel*>(target); - if (u) - s->WriteLine(":"+ServerInstance->Config->GetSID()+" METADATA "+u->uuid+" "+extname+" :"+extdata); - else if (c) - s->WriteLine(":"+ServerInstance->Config->GetSID()+" METADATA "+c->name+" "+extname+" :"+extdata); - else if (!target) - s->WriteLine(":"+ServerInstance->Config->GetSID()+" METADATA * "+extname+" :"+extdata); -} - CullResult ModuleSpanningTree::cull() { - Utils->cull(); - ServerInstance->Timers->DelTimer(RefreshTimer); + if (Utils) + Utils->cull(); return this->Module::cull(); } ModuleSpanningTree::~ModuleSpanningTree() { - delete ServerInstance->PI; - ServerInstance->PI = new ProtocolInterface; + ServerInstance->PI = &ServerInstance->DefaultProtocolInterface; - /* This will also free the listeners */ - delete Utils; + Server* newsrv = new Server(ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc); + SetLocalUsersServer(newsrv); - delete commands; + delete Utils; } Version ModuleSpanningTree::GetVersion() @@ -998,12 +784,13 @@ Version ModuleSpanningTree::GetVersion() * so that any activity it sees is FINAL, e.g. we arent going to send out * a NICK message before m_cloaking has finished putting the +x on the user, * etc etc. - * Therefore, we return PRIORITY_LAST to make sure we end up at the END of + * Therefore, we set our priority to PRIORITY_LAST to make sure we end up at the END of * the module call queue. */ void ModuleSpanningTree::Prioritize() { ServerInstance->Modules->SetPriority(this, PRIORITY_LAST); + ServerInstance->Modules.SetPriority(this, I_OnPreTopicChange, PRIORITY_FIRST); } MODULE_INIT(ModuleSpanningTree) diff --git a/src/modules/m_spanningtree/main.h b/src/modules/m_spanningtree/main.h index 3e0a83111..d619c43bc 100644 --- a/src/modules/m_spanningtree/main.h +++ b/src/modules/m_spanningtree/main.h @@ -21,11 +21,15 @@ */ -#ifndef M_SPANNINGTREE_MAIN_H -#define M_SPANNINGTREE_MAIN_H +#pragma once #include "inspircd.h" -#include <stdarg.h> +#include "event.h" +#include "modules/dns.h" +#include "modules/stats.h" +#include "servercommand.h" +#include "commands.h" +#include "protocolinterface.h" /** If you make a change which breaks the protocol, increment this. * If you completely change the protocol, completely change the number. @@ -36,12 +40,11 @@ * Failure to document your protocol changes will result in a painfully * painful death by pain. You have been warned. */ -const long ProtocolVersion = 1202; -const long MinCompatProtocol = 1201; +const long ProtocolVersion = 1205; +const long MinCompatProtocol = 1202; /** Forward declarations */ -class SpanningTreeCommands; class SpanningTreeUtilities; class CacheRefreshTimer; class TreeServer; @@ -50,60 +53,63 @@ class Autoconnect; /** This is the main class for the spanningtree module */ -class ModuleSpanningTree : public Module +class ModuleSpanningTree + : public Module + , public Away::EventListener + , public Stats::EventListener { - SpanningTreeCommands* commands; + /** Client to server commands, registered in the core + */ + CommandRConnect rconnect; + CommandRSQuit rsquit; + CommandMap map; + + /** Server to server only commands, not registered in the core + */ + SpanningTreeCommands commands; + + /** Next membership id assigned when a local user joins a channel + */ + Membership::Id currmembid; + + /** The specialized ProtocolInterface that is assigned to ServerInstance->PI on load + */ + SpanningTreeProtocolInterface protocolinterface; + + /** Event provider for our events + */ + Events::ModuleEventProvider eventprov; public: - SpanningTreeUtilities* Utils; + dynamic_reference<DNS::Manager> DNS; + + ServerCommandManager CmdManager; - CacheRefreshTimer *RefreshTimer; /** Set to true if inside a spanningtree call, to prevent sending * xlines and other things back to their source */ bool loopCall; - /** If true OnUserPostNick() won't update the nick TS before sending the NICK, - * used when handling SVSNICK. - */ - bool KeepNickTS; - /** Constructor */ ModuleSpanningTree(); - void init(); + void init() CXX11_OVERRIDE; /** Shows /LINKS */ void ShowLinks(TreeServer* Current, User* user, int hops); - /** Counts local and remote servers - */ - int CountServs(); - /** Handle LINKS command */ - void HandleLinks(const std::vector<std::string>& parameters, User* user); - - /** Show MAP output to a user (recursive) - */ - void ShowMap(TreeServer* Current, User* user, int depth, int &line, char* names, int &maxnamew, char* stats); - - /** Handle MAP command - */ - bool HandleMap(const std::vector<std::string>& parameters, User* user); + void HandleLinks(const CommandBase::Params& parameters, User* user); /** Handle SQUIT */ - ModResult HandleSquit(const std::vector<std::string>& parameters, User* user); + ModResult HandleSquit(const CommandBase::Params& parameters, User* user); /** Handle remote WHOIS */ - ModResult HandleRemoteWhois(const std::vector<std::string>& parameters, User* user); - - /** Ping all local servers - */ - void DoPingChecks(time_t curtime); + ModResult HandleRemoteWhois(const CommandBase::Params& parameters, User* user); /** Connect a server locally */ @@ -123,66 +129,52 @@ class ModuleSpanningTree : public Module /** Handle remote VERSON */ - ModResult HandleVersion(const std::vector<std::string>& parameters, User* user); + ModResult HandleVersion(const CommandBase::Params& parameters, User* user); /** Handle CONNECT */ - ModResult HandleConnect(const std::vector<std::string>& parameters, User* user); - - /** Attempt to send a message to a user - */ - void RemoteMessage(User* user, const char* format, ...) CUSTOM_PRINTF(3, 4); - - /** Returns oper-specific MAP information - */ - const std::string MapOperInfo(TreeServer* Current); + ModResult HandleConnect(const CommandBase::Params& parameters, User* user); /** Display a time as a human readable string */ - std::string TimeToStr(time_t secs); + static std::string TimeToStr(time_t secs); + + const Events::ModuleEventProvider& GetEventProvider() const { return eventprov; } /** ** *** MODULE EVENTS *** **/ - ModResult OnPreCommand(std::string &command, std::vector<std::string>& parameters, LocalUser *user, bool validated, const std::string &original_line); - void OnPostCommand(const std::string &command, const std::vector<std::string>& parameters, LocalUser *user, CmdResult result, const std::string &original_line); - void OnGetServerDescription(const std::string &servername,std::string &description); - void OnUserConnect(LocalUser* source); - void OnUserInvite(User* source,User* dest,Channel* channel, time_t); - void OnPostTopicChange(User* user, Channel* chan, const std::string &topic); - void OnWallops(User* user, const std::string &text); - void OnUserNotice(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list); - void OnUserMessage(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list); - void OnBackgroundTimer(time_t curtime); - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts); - void OnChangeHost(User* user, const std::string &newhost); - void OnChangeName(User* user, const std::string &gecos); - void OnChangeIdent(User* user, const std::string &ident); - void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts); - void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message); - void OnUserPostNick(User* user, const std::string &oldnick); - void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts); - void OnRemoteKill(User* source, User* dest, const std::string &reason, const std::string &operreason); - void OnPreRehash(User* user, const std::string ¶meter); - void OnRehash(User* user); - void OnOper(User* user, const std::string &opertype); - void OnLine(User* source, const std::string &host, bool adding, char linetype, long duration, const std::string &reason); - void OnAddLine(User *u, XLine *x); - void OnDelLine(User *u, XLine *x); - void OnMode(User* user, void* dest, int target_type, const std::vector<std::string> &text, const std::vector<TranslateType> &translate); - ModResult OnStats(char statschar, User* user, string_list &results); - ModResult OnSetAway(User* user, const std::string &awaymsg); - void ProtoSendMode(void* opaque, TargetTypeFlags target_type, void* target, const std::vector<std::string> &modeline, const std::vector<TranslateType> &translate); - void ProtoSendMetaData(void* opaque, Extensible* target, const std::string &extname, const std::string &extdata); - void OnLoadModule(Module* mod); - void OnUnloadModule(Module* mod); - ModResult OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); - void OnRequest(Request& request); - CullResult cull(); + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE; + void OnPostCommand(Command*, const CommandBase::Params& parameters, LocalUser* user, CmdResult result, bool loop) CXX11_OVERRIDE; + void OnUserConnect(LocalUser* source) CXX11_OVERRIDE; + void OnUserInvite(User* source, User* dest, Channel* channel, time_t timeout, unsigned int notifyrank, CUList& notifyexcepts) CXX11_OVERRIDE; + ModResult OnPreTopicChange(User* user, Channel* chan, const std::string& topic) CXX11_OVERRIDE; + void OnPostTopicChange(User* user, Channel* chan, const std::string &topic) CXX11_OVERRIDE; + void OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE; + void OnBackgroundTimer(time_t curtime) CXX11_OVERRIDE; + void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE; + void OnChangeHost(User* user, const std::string &newhost) CXX11_OVERRIDE; + void OnChangeRealName(User* user, const std::string& real) CXX11_OVERRIDE; + void OnChangeIdent(User* user, const std::string &ident) CXX11_OVERRIDE; + void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE; + void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE; + void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE; + void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) CXX11_OVERRIDE; + void OnPreRehash(User* user, const std::string ¶meter) CXX11_OVERRIDE; + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE; + void OnOper(User* user, const std::string &opertype) CXX11_OVERRIDE; + void OnAddLine(User *u, XLine *x) CXX11_OVERRIDE; + void OnDelLine(User *u, XLine *x) CXX11_OVERRIDE; + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE; + void OnUserAway(User* user) CXX11_OVERRIDE; + void OnUserBack(User* user) CXX11_OVERRIDE; + void OnLoadModule(Module* mod) CXX11_OVERRIDE; + void OnUnloadModule(Module* mod) CXX11_OVERRIDE; + ModResult OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE; + void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE; + CullResult cull() CXX11_OVERRIDE; ~ModuleSpanningTree(); - Version GetVersion(); - void Prioritize(); + Version GetVersion() CXX11_OVERRIDE; + void Prioritize() CXX11_OVERRIDE; }; - -#endif diff --git a/src/modules/m_spanningtree/metadata.cpp b/src/modules/m_spanningtree/metadata.cpp index a584f8fa8..52267c522 100644 --- a/src/modules/m_spanningtree/metadata.cpp +++ b/src/modules/m_spanningtree/metadata.cpp @@ -21,39 +21,76 @@ #include "inspircd.h" #include "commands.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - -CmdResult CommandMetadata::Handle(const std::vector<std::string>& params, User *srcuser) +CmdResult CommandMetadata::Handle(User* srcuser, Params& params) { - std::string value = params.size() < 3 ? "" : params[2]; - ExtensionItem* item = ServerInstance->Extensions.GetItem(params[1]); if (params[0] == "*") { - FOREACH_MOD(I_OnDecodeMetaData,OnDecodeMetaData(NULL,params[1],value)); + std::string value = params.size() < 3 ? "" : params[2]; + FOREACH_MOD(OnDecodeMetaData, (NULL,params[1],value)); + return CMD_SUCCESS; } - else if (*(params[0].c_str()) == '#') + + if (params[0][0] == '#') { + // Channel METADATA has an additional parameter: the channel TS + // :22D METADATA #channel 12345 extname :extdata + if (params.size() < 3) + throw ProtocolException("Insufficient parameters for channel METADATA"); + Channel* c = ServerInstance->FindChan(params[0]); - if (c) - { - if (item) - item->unserialize(FORMAT_NETWORK, c, value); - FOREACH_MOD(I_OnDecodeMetaData,OnDecodeMetaData(c,params[1],value)); - } + if (!c) + return CMD_FAILURE; + + time_t ChanTS = ServerCommand::ExtractTS(params[1]); + if (c->age < ChanTS) + // Their TS is newer than ours, discard this command and do not propagate + return CMD_FAILURE; + + std::string value = params.size() < 4 ? "" : params[3]; + + ExtensionItem* item = ServerInstance->Extensions.GetItem(params[2]); + if ((item) && (item->type == ExtensionItem::EXT_CHANNEL)) + item->unserialize(FORMAT_NETWORK, c, value); + FOREACH_MOD(OnDecodeMetaData, (c,params[2],value)); } - else if (*(params[0].c_str()) != '#') + else { User* u = ServerInstance->FindUUID(params[0]); - if ((u) && (!IS_SERVER(u))) + if (u) { - if (item) + ExtensionItem* item = ServerInstance->Extensions.GetItem(params[1]); + std::string value = params.size() < 3 ? "" : params[2]; + + if ((item) && (item->type == ExtensionItem::EXT_USER)) item->unserialize(FORMAT_NETWORK, u, value); - FOREACH_MOD(I_OnDecodeMetaData,OnDecodeMetaData(u,params[1],value)); + FOREACH_MOD(OnDecodeMetaData, (u,params[1],value)); } } return CMD_SUCCESS; } +CommandMetadata::Builder::Builder(User* user, const std::string& key, const std::string& val) + : CmdBuilder("METADATA") +{ + push(user->uuid); + push(key); + push_last(val); +} + +CommandMetadata::Builder::Builder(Channel* chan, const std::string& key, const std::string& val) + : CmdBuilder("METADATA") +{ + push(chan->name); + push_int(chan->age); + push(key); + push_last(val); +} + +CommandMetadata::Builder::Builder(const std::string& key, const std::string& val) + : CmdBuilder("METADATA") +{ + push("*"); + push(key); + push_last(val); +} diff --git a/src/modules/m_spanningtree/misccommands.cpp b/src/modules/m_spanningtree/misccommands.cpp new file mode 100644 index 000000000..8fc1b178f --- /dev/null +++ b/src/modules/m_spanningtree/misccommands.cpp @@ -0,0 +1,42 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2007-2008, 2012 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "main.h" +#include "commands.h" +#include "treeserver.h" + +CmdResult CommandSNONotice::Handle(User* user, Params& params) +{ + ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + user->nick + ": " + params[1]); + return CMD_SUCCESS; +} + +CmdResult CommandEndBurst::HandleServer(TreeServer* server, Params& params) +{ + server->FinishBurst(); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/netburst.cpp b/src/modules/m_spanningtree/netburst.cpp index 3bce90eda..30a37baee 100644 --- a/src/modules/m_spanningtree/netburst.cpp +++ b/src/modules/m_spanningtree/netburst.cpp @@ -21,11 +21,80 @@ #include "inspircd.h" #include "xline.h" +#include "listmode.h" #include "treesocket.h" #include "treeserver.h" -#include "utils.h" #include "main.h" +#include "commands.h" +#include "modules/server.h" + +/** + * Creates FMODE messages, used only when syncing channels + */ +class FModeBuilder : public CmdBuilder +{ + static const size_t maxline = 480; + std::string params; + unsigned int modes; + std::string::size_type startpos; + + public: + FModeBuilder(Channel* chan) + : CmdBuilder("FMODE"), modes(0) + { + push(chan->name).push_int(chan->age).push_raw(" +"); + startpos = str().size(); + } + + /** Add a mode to the message + */ + void push_mode(const char modeletter, const std::string& mask) + { + push_raw(modeletter); + params.push_back(' '); + params.append(mask); + modes++; + } + + /** Remove all modes from the message + */ + void clear() + { + content.erase(startpos); + params.clear(); + modes = 0; + } + + /** Prepare the message for sending, next mode can only be added after clear() + */ + const std::string& finalize() + { + return push_raw(params); + } + + /** Returns true if the given mask can be added to the message, false if the message + * has no room for the mask + */ + bool has_room(const std::string& mask) const + { + return ((str().size() + params.size() + mask.size() + 2 <= maxline) && + (modes < ServerInstance->Config->Limits.MaxModes)); + } + + /** Returns true if this message is empty (has no modes) + */ + bool empty() const + { + return (modes == 0); + } +}; + +struct TreeSocket::BurstState +{ + SpanningTreeProtocolInterface::Server server; + BurstState(TreeSocket* sock) : server(sock) { } +}; /** This function is called when we want to send a netburst to a local * server. There is a set order we must do this, because for example @@ -34,157 +103,101 @@ */ void TreeSocket::DoBurst(TreeServer* s) { - std::string servername = s->GetName(); ServerInstance->SNO->WriteToSnoMask('l',"Bursting to \2%s\2 (Authentication: %s%s).", - servername.c_str(), - capab->auth_fingerprint ? "SSL Fingerprint and " : "", + s->GetName().c_str(), + capab->auth_fingerprint ? "SSL certificate fingerprint and " : "", capab->auth_challenge ? "challenge-response" : "plaintext password"); this->CleanNegotiationInfo(); - this->WriteLine(":" + ServerInstance->Config->GetSID() + " BURST " + ConvToStr(ServerInstance->Time())); - /* send our version string */ - this->WriteLine(":" + ServerInstance->Config->GetSID() + " VERSION :"+ServerInstance->GetVersionString()); - /* Send server tree */ - this->SendServers(Utils->TreeRoot,s,1); - /* Send users and their oper status */ - this->SendUsers(); - /* Send everything else (channel modes, xlines etc) */ - this->SendChannelModes(); + this->WriteLine(CmdBuilder("BURST").push_int(ServerInstance->Time())); + // Introduce all servers behind us + this->SendServers(Utils->TreeRoot, s); + + BurstState bs(this); + // Introduce all users + this->SendUsers(bs); + + // Sync all channels + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) + SyncChannel(i->second, bs); + + // Send all xlines this->SendXLines(); - FOREACH_MOD(I_OnSyncNetwork,OnSyncNetwork(Utils->Creator,(void*)this)); - this->WriteLine(":" + ServerInstance->Config->GetSID() + " ENDBURST"); + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), ServerEventListener, OnSyncNetwork, (bs.server)); + this->WriteLine(CmdBuilder("ENDBURST")); ServerInstance->SNO->WriteToSnoMask('l',"Finished bursting to \2"+ s->GetName()+"\2."); + + this->burstsent = true; +} + +void TreeSocket::SendServerInfo(TreeServer* from) +{ + // Send public version string + this->WriteLine(CommandSInfo::Builder(from, "version", from->GetVersion())); + + // Send full version string that contains more information and is shown to opers + this->WriteLine(CommandSInfo::Builder(from, "fullversion", from->GetFullVersion())); + + // Send the raw version string that just contains the base info + this->WriteLine(CommandSInfo::Builder(from, "rawversion", from->GetRawVersion())); } -/** Recursively send the server tree with distances as hops. +/** Recursively send the server tree. * This is used during network burst to inform the other server * (and any of ITS servers too) of what servers we know about. * If at any point any of these servers already exist on the other - * end, our connection may be terminated. The hopcounts given - * by this function are relative, this doesn't matter so long as - * they are all >1, as all the remote servers re-calculate them - * to be relative too, with themselves as hop 0. + * end, our connection may be terminated. */ -void TreeSocket::SendServers(TreeServer* Current, TreeServer* s, int hops) +void TreeSocket::SendServers(TreeServer* Current, TreeServer* s) { - char command[MAXBUF]; - for (unsigned int q = 0; q < Current->ChildCount(); q++) + SendServerInfo(Current); + + const TreeServer::ChildServers& children = Current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = children.begin(); i != children.end(); ++i) { - TreeServer* recursive_server = Current->GetChild(q); + TreeServer* recursive_server = *i; if (recursive_server != s) { - std::string recursive_servername = recursive_server->GetName(); - snprintf(command, MAXBUF, ":%s SERVER %s * %d %s :%s", Current->GetID().c_str(), recursive_servername.c_str(), hops, - recursive_server->GetID().c_str(), - recursive_server->GetDesc().c_str()); - this->WriteLine(command); - this->WriteLine(":"+recursive_server->GetID()+" VERSION :"+recursive_server->GetVersion()); + this->WriteLine(CommandServer::Builder(recursive_server)); /* down to next level */ - this->SendServers(recursive_server, s, hops+1); + this->SendServers(recursive_server, s); } } } /** Send one or more FJOINs for a channel of users. - * If the length of a single line is more than 480-NICKMAX - * in length, it is split over multiple lines. + * If the length of a single line is too long, it is split over multiple lines. */ void TreeSocket::SendFJoins(Channel* c) { - std::string buffer; - char list[MAXBUF]; - - size_t curlen, headlen; - curlen = headlen = snprintf(list,MAXBUF,":%s FJOIN %s %lu +%s :", - ServerInstance->Config->GetSID().c_str(), c->name.c_str(), (unsigned long)c->age, c->ChanModes(true)); - int numusers = 0; - char* ptr = list + curlen; - bool looped_once = false; - - const UserMembList *ulist = c->GetUsers(); - std::string modes; - std::string params; + CommandFJoin::Builder fjoin(c); - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + const Channel::MemberMap& ulist = c->GetUsers(); + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { - size_t ptrlen = 0; - std::string modestr = i->second->modes; - - if ((curlen + modestr.length() + i->first->uuid.length() + 4) > 480) + Membership* memb = i->second; + if (!fjoin.has_room(memb)) { - // remove the final space - if (ptr[-1] == ' ') - ptr[-1] = '\0'; - buffer.append(list).append("\r\n"); - curlen = headlen; - ptr = list + headlen; - numusers = 0; + // No room for this user, send the line and prepare a new one + this->WriteLine(fjoin.finalize()); + fjoin.clear(); } - - ptrlen = snprintf(ptr, MAXBUF-curlen, "%s,%s ", modestr.c_str(), i->first->uuid.c_str()); - - looped_once = true; - - curlen += ptrlen; - ptr += ptrlen; - - numusers++; + fjoin.add(memb); } - - // Okay, permanent channels will (of course) need this \r\n anyway, numusers check is if there - // actually were people in the channel (looped_once == true) - if (!looped_once || numusers > 0) - { - // remove the final space - if (ptr[-1] == ' ') - ptr[-1] = '\0'; - buffer.append(list).append("\r\n"); - } - - unsigned int linesize = 1; - for (BanList::iterator b = c->bans.begin(); b != c->bans.end(); b++) - { - unsigned int size = b->data.length() + 2; // "b" and " " - unsigned int nextsize = linesize + size; - - if ((modes.length() >= ServerInstance->Config->Limits.MaxModes) || (nextsize > FMODE_MAX_LENGTH)) - { - /* Wrap */ - buffer.append(":").append(ServerInstance->Config->GetSID()).append(" FMODE ").append(c->name).append(" ").append(ConvToStr(c->age)).append(" +").append(modes).append(params).append("\r\n"); - - modes.clear(); - params.clear(); - linesize = 1; - } - - modes.push_back('b'); - - params.push_back(' '); - params.append(b->data); - - linesize += size; - } - - /* Only send these if there are any */ - if (!modes.empty()) - buffer.append(":").append(ServerInstance->Config->GetSID()).append(" FMODE ").append(c->name).append(" ").append(ConvToStr(c->age)).append(" +").append(modes).append(params); - - this->WriteLine(buffer); + this->WriteLine(fjoin.finalize()); } /** Send all XLines we know about */ void TreeSocket::SendXLines() { - char data[MAXBUF]; - std::string n = ServerInstance->Config->GetSID(); - const char* sn = n.c_str(); - std::vector<std::string> types = ServerInstance->XLines->GetAllTypes(); - time_t current = ServerInstance->Time(); - for (std::vector<std::string>::iterator it = types.begin(); it != types.end(); ++it) + for (std::vector<std::string>::const_iterator it = types.begin(); it != types.end(); ++it) { + /* Expired lines are removed in XLineManager::GetAll() */ XLineLookup* lookup = ServerInstance->XLines->GetAll(*it); + /* lookup cannot be NULL in this case but a check won't hurt */ if (lookup) { for (LookupIter i = lookup->begin(); i != lookup->end(); ++i) @@ -195,96 +208,99 @@ void TreeSocket::SendXLines() if (!i->second->IsBurstable()) break; - /* If it's expired, don't bother to burst it - */ - if (i->second->duration && current > i->second->expiry) - continue; - - snprintf(data,MAXBUF,":%s ADDLINE %s %s %s %lu %lu :%s",sn, it->c_str(), i->second->Displayable(), - i->second->source.c_str(), - (unsigned long)i->second->set_time, - (unsigned long)i->second->duration, - i->second->reason.c_str()); - this->WriteLine(data); + this->WriteLine(CommandAddLine::Builder(i->second)); } } } } -/** Send channel topic, modes and metadata */ -void TreeSocket::SendChannelModes() +void TreeSocket::SendListModes(Channel* chan) { - char data[MAXBUF]; - std::string n = ServerInstance->Config->GetSID(); - const char* sn = n.c_str(); - - for (chan_hash::iterator c = ServerInstance->chanlist->begin(); c != ServerInstance->chanlist->end(); c++) + FModeBuilder fmode(chan); + const ModeParser::ListModeList& listmodes = ServerInstance->Modes->GetListModes(); + for (ModeParser::ListModeList::const_iterator i = listmodes.begin(); i != listmodes.end(); ++i) { - SendFJoins(c->second); - if (!c->second->topic.empty()) + ListModeBase* mh = *i; + ListModeBase::ModeList* list = mh->GetList(chan); + if (!list) + continue; + + // Add all items on the list to the FMODE, send it whenever it becomes too long + const char modeletter = mh->GetModeChar(); + for (ListModeBase::ModeList::const_iterator j = list->begin(); j != list->end(); ++j) { - snprintf(data,MAXBUF,":%s FTOPIC %s %lu %s :%s", sn, c->second->name.c_str(), (unsigned long)c->second->topicset, c->second->setby.c_str(), c->second->topic.c_str()); - this->WriteLine(data); + const std::string& mask = j->mask; + if (!fmode.has_room(mask)) + { + // No room for this mask, send the current line as-is then add the mask to a + // new, empty FMODE message + this->WriteLine(fmode.finalize()); + fmode.clear(); + } + fmode.push_mode(modeletter, mask); } + } - for(Extensible::ExtensibleStore::const_iterator i = c->second->GetExtList().begin(); i != c->second->GetExtList().end(); i++) - { - ExtensionItem* item = i->first; - std::string value = item->serialize(FORMAT_NETWORK, c->second, i->second); - if (!value.empty()) - Utils->Creator->ProtoSendMetaData(this, c->second, item->name, value); - } + if (!fmode.empty()) + this->WriteLine(fmode.finalize()); +} + +/** Send channel users, topic, modes and global metadata */ +void TreeSocket::SyncChannel(Channel* chan, BurstState& bs) +{ + SendFJoins(chan); - FOREACH_MOD(I_OnSyncChannel,OnSyncChannel(c->second,Utils->Creator,this)); + // If the topic was ever set, send it, even if it's empty now + // because a new empty topic should override an old non-empty topic + if (chan->topicset != 0) + this->WriteLine(CommandFTopic::Builder(chan)); + + SendListModes(chan); + + for (Extensible::ExtensibleStore::const_iterator i = chan->GetExtList().begin(); i != chan->GetExtList().end(); i++) + { + ExtensionItem* item = i->first; + std::string value = item->serialize(FORMAT_NETWORK, chan, i->second); + if (!value.empty()) + this->WriteLine(CommandMetadata::Builder(chan, item->name, value)); } + + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), ServerEventListener, OnSyncChannel, (chan, bs.server)); } -/** send all users and their oper state/modes */ -void TreeSocket::SendUsers() +void TreeSocket::SyncChannel(Channel* chan) { - char data[MAXBUF]; - for (user_hash::iterator u = ServerInstance->Users->clientlist->begin(); u != ServerInstance->Users->clientlist->end(); u++) + BurstState bs(this); + SyncChannel(chan, bs); +} + +/** Send all users and their state, including oper and away status and global metadata */ +void TreeSocket::SendUsers(BurstState& bs) +{ + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator u = users.begin(); u != users.end(); ++u) { - if (u->second->registered == REG_ALL) - { - TreeServer* theirserver = Utils->FindServer(u->second->server); - if (theirserver) - { - snprintf(data,MAXBUF,":%s UID %s %lu %s %s %s %s %s %lu +%s :%s", - theirserver->GetID().c_str(), /* Prefix: SID */ - u->second->uuid.c_str(), /* 0: UUID */ - (unsigned long)u->second->age, /* 1: TS */ - u->second->nick.c_str(), /* 2: Nick */ - u->second->host.c_str(), /* 3: Displayed Host */ - u->second->dhost.c_str(), /* 4: Real host */ - u->second->ident.c_str(), /* 5: Ident */ - u->second->GetIPString(), /* 6: IP string */ - (unsigned long)u->second->signon, /* 7: Signon time for WHOWAS */ - u->second->FormatModes(true), /* 8...n: Modes and params */ - u->second->fullname.c_str()); /* size-1: GECOS */ - this->WriteLine(data); - if (IS_OPER(u->second)) - { - snprintf(data,MAXBUF,":%s OPERTYPE %s", u->second->uuid.c_str(), u->second->oper->name.c_str()); - this->WriteLine(data); - } - if (IS_AWAY(u->second)) - { - snprintf(data,MAXBUF,":%s AWAY %ld :%s", u->second->uuid.c_str(), (long)u->second->awaytime, u->second->awaymsg.c_str()); - this->WriteLine(data); - } - } + User* user = u->second; + if (user->registered != REG_ALL) + continue; - for(Extensible::ExtensibleStore::const_iterator i = u->second->GetExtList().begin(); i != u->second->GetExtList().end(); i++) - { - ExtensionItem* item = i->first; - std::string value = item->serialize(FORMAT_NETWORK, u->second, i->second); - if (!value.empty()) - Utils->Creator->ProtoSendMetaData(this, u->second, item->name, value); - } + this->WriteLine(CommandUID::Builder(user)); + + if (user->IsOper()) + this->WriteLine(CommandOpertype::Builder(user)); + + if (user->IsAway()) + this->WriteLine(CommandAway::Builder(user)); - FOREACH_MOD(I_OnSyncUser,OnSyncUser(u->second,Utils->Creator,this)); + const Extensible::ExtensibleStore& exts = user->GetExtList(); + for (Extensible::ExtensibleStore::const_iterator i = exts.begin(); i != exts.end(); ++i) + { + ExtensionItem* item = i->first; + std::string value = item->serialize(FORMAT_NETWORK, u->second, i->second); + if (!value.empty()) + this->WriteLine(CommandMetadata::Builder(user, item->name, value)); } + + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), ServerEventListener, OnSyncUser, (user, bs.server)); } } - diff --git a/src/modules/m_spanningtree/nick.cpp b/src/modules/m_spanningtree/nick.cpp new file mode 100644 index 000000000..4f53941ce --- /dev/null +++ b/src/modules/m_spanningtree/nick.cpp @@ -0,0 +1,64 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2007-2008, 2012 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "main.h" +#include "utils.h" +#include "commands.h" +#include "treeserver.h" + +CmdResult CommandNick::HandleRemote(::RemoteUser* user, Params& params) +{ + if ((isdigit(params[0][0])) && (params[0] != user->uuid)) + throw ProtocolException("Attempted to change nick to an invalid or non-matching UUID"); + + // Timestamp of the new nick + time_t newts = ServerCommand::ExtractTS(params[1]); + + /* + * On nick messages, check that the nick doesn't already exist here. + * If it does, perform collision logic. + */ + User* x = ServerInstance->FindNickOnly(params[0]); + if ((x) && (x != user) && (x->registered == REG_ALL)) + { + // 'x' is the already existing user using the same nick as params[0] + // 'user' is the user trying to change nick to the in use nick + bool they_change = Utils->DoCollision(x, TreeServer::Get(user), newts, user->ident, user->GetIPString(), user->uuid, "NICK"); + if (they_change) + { + // Remote client lost, or both lost, rewrite this nick change as a change to uuid before + // calling ChangeNick() and forwarding the message + params[0] = user->uuid; + params[1] = ConvToStr(CommandSave::SavedTimestamp); + newts = CommandSave::SavedTimestamp; + } + } + + user->ChangeNick(params[0], newts); + + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/nickcollide.cpp b/src/modules/m_spanningtree/nickcollide.cpp index 38d59affb..62e200921 100644 --- a/src/modules/m_spanningtree/nickcollide.cpp +++ b/src/modules/m_spanningtree/nickcollide.cpp @@ -19,23 +19,25 @@ #include "inspircd.h" -#include "xline.h" #include "treesocket.h" #include "treeserver.h" #include "utils.h" - -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - +#include "commandbuilder.h" +#include "commands.h" /* * Yes, this function looks a little ugly. * However, in some circumstances we may not have a User, so we need to do things this way. - * Returns 1 if colliding local client, 2 if colliding remote, 3 if colliding both. - * Sends SAVEs as appropriate and forces nickchanges too. + * Returns true if remote or both lost, false otherwise. + * Sends SAVEs as appropriate and forces nick change of the user 'u' if our side loses or if both lose. + * Does not change the nick of the user that is trying to claim the nick of 'u', i.e. the "remote" user. */ -int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remoteident, const std::string &remoteip, const std::string &remoteuid) +bool SpanningTreeUtilities::DoCollision(User* u, TreeServer* server, time_t remotets, const std::string& remoteident, const std::string& remoteip, const std::string& remoteuid, const char* collidecmd) { + // At this point we're sure that a collision happened, increment the counter regardless of who wins + ServerInstance->stats.Collisions++; + /* * Under old protocol rules, we would have had to kill both clients. * Really, this sucks. @@ -56,21 +58,14 @@ int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remotei bool bChangeLocal = true; bool bChangeRemote = true; - /* for brevity, don't use the User - use defines to avoid any copy */ - #define localts u->age - #define localident u->ident - #define localip u->GetIPString() - - /* mmk. let's do this again. */ - if (remotets == localts) + // If the timestamps are not equal only one of the users has to change nick, + // otherwise both have to change + const time_t localts = u->age; + if (remotets != localts) { - /* equal. fuck them both! do nada, let the handler at the bottom figure this out. */ - } - else - { - /* fuck. now it gets complex. */ - /* first, let's see if ident@host matches. */ + const std::string& localident = u->ident; + const std::string& localip = u->GetIPString(); bool SamePerson = (localident == remoteident) && (localip == remoteip); @@ -81,19 +76,22 @@ int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remotei if((SamePerson && remotets < localts) || (!SamePerson && remotets > localts)) { - /* remote needs to change */ + // Only remote needs to change bChangeLocal = false; } else { - /* ours needs to change */ + // Only ours needs to change bChangeRemote = false; } } + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Nick collision on \"%s\" caused by %s: %s/%lu/%s@%s %d <-> %s/%lu/%s@%s %d", u->nick.c_str(), collidecmd, + u->uuid.c_str(), (unsigned long)localts, u->ident.c_str(), u->GetIPString().c_str(), bChangeLocal, + remoteuid.c_str(), (unsigned long)remotets, remoteident.c_str(), remoteip.c_str(), bChangeRemote); + /* - * Cheat a little here. Instead of a dedicated command to change UID, - * use SAVE and accept the losing client with its UID (as we know the SAVE will + * Send SAVE and accept the losing client with its UID (as we know the SAVE will * not fail under any circumstances -- UIDs are netwide exclusive). * * This means that each side of a collide will generate one extra NICK back to where @@ -107,38 +105,23 @@ int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remotei { /* * Local-side nick needs to change. Just in case we are hub, and - * this "local" nick is actually behind us, send an SAVE out. + * this "local" nick is actually behind us, send a SAVE out. */ - parameterlist params; + CmdBuilder params("SAVE"); params.push_back(u->uuid); params.push_back(ConvToStr(u->age)); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"SAVE",params); - - u->ForceNickChange(u->uuid.c_str()); + params.Broadcast(); - if (!bChangeRemote) - return 1; + u->ChangeNick(u->uuid, CommandSave::SavedTimestamp); } if (bChangeRemote) { - User *remote = ServerInstance->FindUUID(remoteuid); /* - * remote side needs to change. If this happens, we will modify - * the UID or halt the propagation of the nick change command, - * so other servers don't need to see the SAVE + * Remote side needs to change. If this happens, we modify the UID or NICK and + * send back a SAVE to the source. */ - WriteLine(":"+ServerInstance->Config->GetSID()+" SAVE "+remoteuid+" "+ ConvToStr(remotets)); - - if (remote) - { - /* nick change collide. Force change their nick. */ - remote->ForceNickChange(remoteuid.c_str()); - } - - if (!bChangeLocal) - return 2; + CmdBuilder("SAVE").push(remoteuid).push_int(remotets).Unicast(server->ServerUser); } - return 3; + return bChangeRemote; } - diff --git a/src/modules/m_spanningtree/num.cpp b/src/modules/m_spanningtree/num.cpp new file mode 100644 index 000000000..f83f91286 --- /dev/null +++ b/src/modules/m_spanningtree/num.cpp @@ -0,0 +1,62 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "utils.h" +#include "commands.h" +#include "remoteuser.h" + +CmdResult CommandNum::HandleServer(TreeServer* server, CommandBase::Params& params) +{ + User* const target = ServerInstance->FindUUID(params[1]); + if (!target) + return CMD_FAILURE; + + LocalUser* const localtarget = IS_LOCAL(target); + if (!localtarget) + return CMD_SUCCESS; + + Numeric::Numeric numeric(ConvToInt(params[2])); + // Passing NULL is ok, in that case the numeric source becomes this server + numeric.SetServer(Utils->FindServerID(params[0])); + numeric.GetParams().insert(numeric.GetParams().end(), params.begin()+3, params.end()); + + localtarget->WriteNumeric(numeric); + return CMD_SUCCESS; +} + +RouteDescriptor CommandNum::GetRouting(User* user, const Params& params) +{ + return ROUTE_UNICAST(params[1]); +} + +CommandNum::Builder::Builder(SpanningTree::RemoteUser* target, const Numeric::Numeric& numeric) + : CmdBuilder("NUM") +{ + TreeServer* const server = (numeric.GetServer() ? (static_cast<TreeServer*>(numeric.GetServer())) : Utils->TreeRoot); + push(server->GetID()).push(target->uuid).push(InspIRCd::Format("%03u", numeric.GetNumeric())); + const CommandBase::Params& params = numeric.GetParams(); + if (!params.empty()) + { + for (CommandBase::Params::const_iterator i = params.begin(); i != params.end()-1; ++i) + push(*i); + push_last(params.back()); + } +} diff --git a/src/modules/m_spanningtree/opertype.cpp b/src/modules/m_spanningtree/opertype.cpp index 97a4de8c2..473cdb857 100644 --- a/src/modules/m_spanningtree/opertype.cpp +++ b/src/modules/m_spanningtree/opertype.cpp @@ -26,21 +26,20 @@ /** Because the core won't let users or even SERVERS set +o, * we use the OPERTYPE command to do this. */ -CmdResult CommandOpertype::Handle(const std::vector<std::string>& params, User *u) +CmdResult CommandOpertype::HandleRemote(RemoteUser* u, CommandBase::Params& params) { - SpanningTreeUtilities* Utils = ((ModuleSpanningTree*)(Module*)creator)->Utils; - std::string opertype = params[0]; - if (!IS_OPER(u)) + const std::string& opertype = params[0]; + if (!u->IsOper()) ServerInstance->Users->all_opers.push_back(u); - u->modes[UM_OPERATOR] = 1; - OperIndex::iterator iter = ServerInstance->Config->oper_blocks.find(" " + opertype); - if (iter != ServerInstance->Config->oper_blocks.end()) + + ModeHandler* opermh = ServerInstance->Modes->FindMode('o', MODETYPE_USER); + u->SetMode(opermh, true); + + ServerConfig::OperIndex::const_iterator iter = ServerInstance->Config->OperTypes.find(opertype); + if (iter != ServerInstance->Config->OperTypes.end()) u->oper = iter->second; else - { - u->oper = new OperInfo; - u->oper->name = opertype; - } + u->oper = new OperInfo(opertype); if (Utils->quiet_bursts) { @@ -48,12 +47,17 @@ CmdResult CommandOpertype::Handle(const std::vector<std::string>& params, User * * If quiet bursts are enabled, and server is bursting or silent uline (i.e. services), * then do nothing. -- w00t */ - TreeServer* remoteserver = Utils->FindServer(u->server); - if (remoteserver->bursting || ServerInstance->SilentULine(u->server)) + TreeServer* remoteserver = TreeServer::Get(u); + if (remoteserver->IsBehindBursting() || remoteserver->IsSilentULine()) return CMD_SUCCESS; } - ServerInstance->SNO->WriteToSnoMask('O',"From %s: User %s (%s@%s) is now an IRC operator of type %s",u->server.c_str(), u->nick.c_str(),u->ident.c_str(), u->host.c_str(), irc::Spacify(opertype.c_str())); + ServerInstance->SNO->WriteToSnoMask('O',"From %s: User %s (%s@%s) is now an IRC operator of type %s",u->server->GetName().c_str(), u->nick.c_str(),u->ident.c_str(), u->GetRealHost().c_str(), opertype.c_str()); return CMD_SUCCESS; } +CommandOpertype::Builder::Builder(User* user) + : CmdBuilder(user, "OPERTYPE") +{ + push_last(user->oper->name); +} diff --git a/src/modules/m_spanningtree/override_map.cpp b/src/modules/m_spanningtree/override_map.cpp index 04fa4bcab..693b07bad 100644 --- a/src/modules/m_spanningtree/override_map.cpp +++ b/src/modules/m_spanningtree/override_map.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2014 Adam <Adam@anope.org> * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> @@ -19,178 +20,204 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" -const std::string ModuleSpanningTree::MapOperInfo(TreeServer* Current) +CommandMap::CommandMap(Module* Creator) + : Command(Creator, "MAP", 0, 1) { - time_t secs_up = ServerInstance->Time() - Current->age; - return " [Up: " + TimeToStr(secs_up) + (Current->rtt == 0 ? "]" : " Lag: " + ConvToStr(Current->rtt) + "ms]"); + Penalty = 2; } -void ModuleSpanningTree::ShowMap(TreeServer* Current, User* user, int depth, int &line, char* names, int &maxnamew, char* stats) +static inline bool IsHidden(User* user, TreeServer* server) { - ServerInstance->Logs->Log("map",DEBUG,"ShowMap depth %d on line %d", depth, line); - float percent; - - if (ServerInstance->Users->clientlist->size() == 0) + if (!user->IsOper()) { - // If there are no users, WHO THE HELL DID THE /MAP?!?!?! - percent = 0; + if (server->Hidden) + return true; + if (Utils->HideULines && server->IsULine()) + return true; } - else + + return false; +} + +// Calculate the map depth the servers go, and the longest server name +static void GetDepthAndLen(TreeServer* current, unsigned int depth, unsigned int& max_depth, unsigned int& max_len) +{ + if (depth > max_depth) + max_depth = depth; + if (current->GetName().length() > max_len) + max_len = current->GetName().length(); + + const TreeServer::ChildServers& servers = current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = servers.begin(); i != servers.end(); ++i) { - percent = Current->GetUserCount() * 100.0 / ServerInstance->Users->clientlist->size(); + TreeServer* child = *i; + GetDepthAndLen(child, depth + 1, max_depth, max_len); } +} - const std::string operdata = IS_OPER(user) ? MapOperInfo(Current) : ""; - - char* myname = names + 100 * line; - char* mystat = stats + 50 * line; - memset(myname, ' ', depth); - int w = depth; +static std::vector<std::string> GetMap(User* user, TreeServer* current, unsigned int max_len, unsigned int depth) +{ + float percent = 0; - std::string servername = Current->GetName(); - if (IS_OPER(user)) + const user_hash& users = ServerInstance->Users->GetUsers(); + if (!users.empty()) { - w += snprintf(myname + depth, 99 - depth, "%s (%s)", servername.c_str(), Current->GetID().c_str()); + // If there are no users, WHO THE HELL DID THE /MAP?!?!?! + percent = current->UserCount * 100.0 / users.size(); } - else + + std::string buffer = current->GetName(); + if (user->IsOper()) { - w += snprintf(myname + depth, 99 - depth, "%s", servername.c_str()); + buffer += " (" + current->GetID(); + + const std::string& cur_vers = current->GetRawVersion(); + if (!cur_vers.empty()) + buffer += " " + cur_vers; + + buffer += ")"; } - memset(myname + w, ' ', 100 - w); - if (w > maxnamew) - maxnamew = w; - snprintf(mystat, 49, "%5d [%5.2f%%]%s", Current->GetUserCount(), percent, operdata.c_str()); - line++; + // Pad with spaces until its at max len, max_len must always be >= my names length + buffer.append(max_len - current->GetName().length(), ' '); - if (IS_OPER(user) || !Utils->FlatLinks) - depth = depth + 2; - for (unsigned int q = 0; q < Current->ChildCount(); q++) + buffer += InspIRCd::Format("%5d [%5.2f%%]", current->UserCount, percent); + + if (user->IsOper()) { - TreeServer* child = Current->GetChild(q); - if (!IS_OPER(user)) { - if (child->Hidden) - continue; - if ((Utils->HideULines) && (ServerInstance->ULine(child->GetName()))) - continue; - } - ShowMap(child, user, depth, line, names, maxnamew, stats); + time_t secs_up = ServerInstance->Time() - current->age; + buffer += " [Up: " + ModuleSpanningTree::TimeToStr(secs_up) + (current->rtt == 0 ? "]" : " Lag: " + ConvToStr(current->rtt) + "ms]"); } -} + std::vector<std::string> map; + map.push_back(buffer); -// Ok, prepare to be confused. -// After much mulling over how to approach this, it struck me that -// the 'usual' way of doing a /MAP isnt the best way. Instead of -// keeping track of a ton of ascii characters, and line by line -// under recursion working out where to place them using multiplications -// and divisons, we instead render the map onto a backplane of characters -// (a character matrix), then draw the branches as a series of "L" shapes -// from the nodes. This is not only friendlier on CPU it uses less stack. -bool ModuleSpanningTree::HandleMap(const std::vector<std::string>& parameters, User* user) -{ - if (parameters.size() > 0) + const TreeServer::ChildServers& servers = current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = servers.begin(); i != servers.end(); ++i) { - /* Remote MAP, the server is within the 1st parameter */ - TreeServer* s = Utils->FindServerMask(parameters[0]); - bool ret = false; - if (!s) + TreeServer* child = *i; + + if (IsHidden(user, child)) + continue; + + bool last = true; + for (TreeServer::ChildServers::const_iterator j = i + 1; last && j != servers.end(); ++j) + if (!IsHidden(user, *j)) + last = false; + + unsigned int next_len; + + if (user->IsOper() || !Utils->FlatLinks) { - user->WriteNumeric(ERR_NOSUCHSERVER, "%s %s :No such server", user->nick.c_str(), parameters[0].c_str()); - ret = true; + // This child is indented by us, so remove the depth from the max length to align the users properly + next_len = max_len - 2; } - else if (s && s != Utils->TreeRoot) + else { - parameterlist params; - params.push_back(parameters[0]); - - params[0] = s->GetName(); - Utils->DoOneToOne(user->uuid, "MAP", params, s->GetName()); - ret = true; + // This user can not see depth, so max_len remains constant + next_len = max_len; } - // Don't return if s == Utils->TreeRoot (us) - if (ret) - return true; - } + // Build the map for this child + std::vector<std::string> child_map = GetMap(user, child, next_len, depth + 1); - // These arrays represent a virtual screen which we will - // "scratch" draw to, as the console device of an irc - // client does not provide for a proper terminal. - int totusers = ServerInstance->Users->clientlist->size(); - int totservers = this->CountServs(); - int maxnamew = 0; - int line = 0; - char* names = new char[totservers * 100]; - char* stats = new char[totservers * 50]; - - // The only recursive bit is called here. - ShowMap(Utils->TreeRoot,user,0,line,names,maxnamew,stats); - - // Process each line one by one. - for (int l = 1; l < line; l++) - { - char* myname = names + 100 * l; - // scan across the line looking for the start of the - // servername (the recursive part of the algorithm has placed - // the servers at indented positions depending on what they - // are related to) - int first_nonspace = 0; - - while (myname[first_nonspace] == ' ') + for (std::vector<std::string>::const_iterator j = child_map.begin(); j != child_map.end(); ++j) { - first_nonspace++; + const char* prefix; + + if (user->IsOper() || !Utils->FlatLinks) + { + // If this server is not the root child + if (j != child_map.begin()) + { + // If this child is not my last child, then add | + // to be able to "link" the next server in my list to me, and to indent this childs servers + if (!last) + prefix = "| "; + // Otherwise this is my last child, so just use a space as theres nothing else linked to me below this + else + prefix = " "; + } + // If we get here, this server must be the root child + else + { + // If this is the last child, it gets a `- + if (last) + prefix = "`-"; + // Otherwise this isn't the last child, so it gets |- + else + prefix = "|-"; + } + } + else + // User can't see depth, so use no prefix + prefix = ""; + + // Add line to the map + map.push_back(prefix + *j); } + } - first_nonspace--; - - // Draw the `- (corner) section: this may be overwritten by - // another L shape passing along the same vertical pane, becoming - // a |- (branch) section instead. - - myname[first_nonspace] = '-'; - myname[first_nonspace-1] = '`'; - int l2 = l - 1; + return map; +} - // Draw upwards until we hit the parent server, causing possibly - // other corners (`-) to become branches (|-) - while ((names[l2 * 100 + first_nonspace-1] == ' ') || (names[l2 * 100 + first_nonspace-1] == '`')) +CmdResult CommandMap::Handle(User* user, const Params& parameters) +{ + if (parameters.size() > 0) + { + // Remote MAP, the target server is the 1st parameter + TreeServer* s = Utils->FindServerMask(parameters[0]); + if (!s) { - names[l2 * 100 + first_nonspace-1] = '|'; - l2--; + user->WriteNumeric(ERR_NOSUCHSERVER, parameters[0], "No such server"); + return CMD_FAILURE; } + + if (!s->IsRoot()) + return CMD_SUCCESS; } - float avg_users = totusers * 1.0 / line; + // Max depth and max server name length + unsigned int max_depth = 0; + unsigned int max_len = 0; + GetDepthAndLen(Utils->TreeRoot, 0, max_depth, max_len); - ServerInstance->Logs->Log("map",DEBUG,"local"); - for (int t = 0; t < line; t++) + unsigned int max; + if (user->IsOper() || !Utils->FlatLinks) { - // terminate the string at maxnamew characters - names[100 * t + maxnamew] = '\0'; - user->SendText(":%s %03d %s :%s %s", ServerInstance->Config->ServerName.c_str(), - RPL_MAP, user->nick.c_str(), names + 100 * t, stats + 50 * t); + // Each level of the map is indented by 2 characters, making the max possible line (max_depth * 2) + max_len + max = (max_depth * 2) + max_len; } - user->SendText(":%s %03d %s :%d server%s and %d user%s, average %.2f users per server", - ServerInstance->Config->ServerName.c_str(), RPL_MAPUSERS, user->nick.c_str(), - line, (line > 1 ? "s" : ""), totusers, (totusers > 1 ? "s" : ""), avg_users); - user->SendText(":%s %03d %s :End of /MAP", ServerInstance->Config->ServerName.c_str(), - RPL_ENDMAP, user->nick.c_str()); + else + { + // This user can't see any depth + max = max_len; + } + + std::vector<std::string> map = GetMap(user, Utils->TreeRoot, max, 0); + for (std::vector<std::string>::const_iterator i = map.begin(); i != map.end(); ++i) + user->WriteRemoteNumeric(RPL_MAP, *i); - delete[] names; - delete[] stats; + size_t totusers = ServerInstance->Users->GetUsers().size(); + float avg_users = (float) totusers / Utils->serverlist.size(); - return true; + user->WriteRemoteNumeric(RPL_MAPUSERS, InspIRCd::Format("%u server%s and %u user%s, average %.2f users per server", + (unsigned int)Utils->serverlist.size(), (Utils->serverlist.size() > 1 ? "s" : ""), (unsigned int)totusers, (totusers > 1 ? "s" : ""), avg_users)); + user->WriteRemoteNumeric(RPL_ENDMAP, "End of /MAP"); + + return CMD_SUCCESS; } +RouteDescriptor CommandMap::GetRouting(User* user, const Params& parameters) +{ + if (!parameters.empty()) + return ROUTE_UNICAST(parameters[0]); + return ROUTE_LOCALONLY; +} diff --git a/src/modules/m_spanningtree/override_squit.cpp b/src/modules/m_spanningtree/override_squit.cpp index 7d01c8149..eb224660d 100644 --- a/src/modules/m_spanningtree/override_squit.cpp +++ b/src/modules/m_spanningtree/override_squit.cpp @@ -17,48 +17,38 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" #include "socket.h" -#include "xline.h" #include "main.h" #include "utils.h" #include "treeserver.h" #include "treesocket.h" -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -ModResult ModuleSpanningTree::HandleSquit(const std::vector<std::string>& parameters, User* user) +ModResult ModuleSpanningTree::HandleSquit(const CommandBase::Params& parameters, User* user) { TreeServer* s = Utils->FindServerMask(parameters[0]); if (s) { - if (s == Utils->TreeRoot) + if (s->IsRoot()) { - user->WriteServ("NOTICE %s :*** SQUIT: Foolish mortal, you cannot make a server SQUIT itself! (%s matches local server name)",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** SQUIT: Foolish mortal, you cannot make a server SQUIT itself! (" + parameters[0] + " matches local server name)"); return MOD_RES_DENY; } - TreeSocket* sock = s->GetSocket(); - - if (sock) + if (s->IsLocal()) { ServerInstance->SNO->WriteToSnoMask('l',"SQUIT: Server \002%s\002 removed from network by %s",parameters[0].c_str(),user->nick.c_str()); - sock->Squit(s,"Server quit by " + user->GetFullRealHost()); - ServerInstance->SE->DelFd(sock); - sock->Close(); + s->SQuit("Server quit by " + user->GetFullRealHost()); } else { - user->WriteServ("NOTICE %s :*** SQUIT may not be used to remove remote servers. Please use RSQUIT instead.",user->nick.c_str()); + user->WriteNotice("*** SQUIT may not be used to remove remote servers. Please use RSQUIT instead."); } } else { - user->WriteServ("NOTICE %s :*** SQUIT: The server \002%s\002 does not exist on the network.",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** SQUIT: The server \002" + parameters[0] + "\002 does not exist on the network."); } return MOD_RES_DENY; } - diff --git a/src/modules/m_spanningtree/override_stats.cpp b/src/modules/m_spanningtree/override_stats.cpp index 688661b80..9b73837cb 100644 --- a/src/modules/m_spanningtree/override_stats.cpp +++ b/src/modules/m_spanningtree/override_stats.cpp @@ -18,30 +18,42 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" #include "main.h" #include "utils.h" -#include "treeserver.h" #include "link.h" -#include "treesocket.h" -ModResult ModuleSpanningTree::OnStats(char statschar, User* user, string_list &results) +ModResult ModuleSpanningTree::OnStats(Stats::Context& stats) { - if ((statschar == 'c') || (statschar == 'n')) + if ((stats.GetSymbol() == 'c') || (stats.GetSymbol() == 'n')) { for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i != Utils->LinkBlocks.end(); ++i) { Link* L = *i; - results.push_back(std::string(ServerInstance->Config->ServerName)+" 213 "+user->nick+" "+statschar+" *@"+(L->HiddenFromStats ? "<hidden>" : L->IPAddr)+" * "+(*i)->Name.c_str()+" "+ConvToStr(L->Port)+" "+(L->Hook.empty() ? "plaintext" : L->Hook)); - if (statschar == 'c') - results.push_back(std::string(ServerInstance->Config->ServerName)+" 244 "+user->nick+" H * * "+L->Name.c_str()); + std::string ipaddr = "*@"; + if (L->HiddenFromStats) + ipaddr.append("<hidden>"); + else + ipaddr.append(L->IPAddr); + + const std::string hook = (L->Hook.empty() ? "plaintext" : L->Hook); + stats.AddRow(213, stats.GetSymbol(), ipaddr, '*', L->Name, L->Port, hook); + if (stats.GetSymbol() == 'c') + stats.AddRow(244, 'H', '*', '*', L->Name); + } + return MOD_RES_DENY; + } + else if (stats.GetSymbol() == 'U') + { + ConfigTagList tags = ServerInstance->Config->ConfTags("uline"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + std::string name = i->second->getString("server"); + if (!name.empty()) + stats.AddRow(248, 'U', name); } return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - diff --git a/src/modules/m_spanningtree/override_whois.cpp b/src/modules/m_spanningtree/override_whois.cpp index ad8c6a6ef..6a64a9403 100644 --- a/src/modules/m_spanningtree/override_whois.cpp +++ b/src/modules/m_spanningtree/override_whois.cpp @@ -16,39 +16,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commandbuilder.h" -ModResult ModuleSpanningTree::HandleRemoteWhois(const std::vector<std::string>& parameters, User* user) +ModResult ModuleSpanningTree::HandleRemoteWhois(const CommandBase::Params& parameters, User* user) { - if ((IS_LOCAL(user)) && (parameters.size() > 1)) + User* remote = ServerInstance->FindNickOnly(parameters[1]); + if (remote && !IS_LOCAL(remote)) { - User* remote = ServerInstance->FindNickOnly(parameters[1]); - if (remote && !IS_LOCAL(remote)) - { - parameterlist params; - params.push_back(remote->uuid); - Utils->DoOneToOne(user->uuid,"IDLE",params,remote->server); - return MOD_RES_DENY; - } - else if (!remote) - { - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[1].c_str()); - user->WriteNumeric(318, "%s %s :End of /WHOIS list.",user->nick.c_str(), parameters[1].c_str()); - return MOD_RES_DENY; - } + CmdBuilder(user, "IDLE").push(remote->uuid).Unicast(remote); + return MOD_RES_DENY; + } + else if (!remote) + { + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + user->WriteNumeric(RPL_ENDOFWHOIS, parameters[0], "End of /WHOIS list."); + return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - diff --git a/src/modules/m_spanningtree/ping.cpp b/src/modules/m_spanningtree/ping.cpp index aec680b23..844feb35b 100644 --- a/src/modules/m_spanningtree/ping.cpp +++ b/src/modules/m_spanningtree/ping.cpp @@ -18,44 +18,24 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" +#include "utils.h" -bool TreeSocket::LocalPing(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandPing::Handle(User* user, Params& params) { - if (params.size() < 1) - return true; - if (params.size() == 1) - { - std::string stufftobounce = params[0]; - this->WriteLine(":"+ServerInstance->Config->GetSID()+" PONG "+stufftobounce); - return true; - } - else + if (params[0] == ServerInstance->Config->GetSID()) { - std::string forwardto = params[1]; - if (forwardto == ServerInstance->Config->ServerName || forwardto == ServerInstance->Config->GetSID()) - { - // this is a ping for us, send back PONG to the requesting server - params[1] = params[0]; - params[0] = forwardto; - Utils->DoOneToOne(ServerInstance->Config->GetSID(),"PONG",params,params[1]); - } - else - { - // not for us, pass it on :) - Utils->DoOneToOne(prefix,"PING",params,forwardto); - } - return true; + // PING for us, reply with a PONG + CmdBuilder reply("PONG"); + reply.push_back(user->uuid); + if (params.size() >= 2) + // If there is a second parameter, append it + reply.push_back(params[1]); + + reply.Unicast(user); } + return CMD_SUCCESS; } - - diff --git a/src/modules/m_spanningtree/pingtimer.cpp b/src/modules/m_spanningtree/pingtimer.cpp new file mode 100644 index 000000000..1c96259bf --- /dev/null +++ b/src/modules/m_spanningtree/pingtimer.cpp @@ -0,0 +1,102 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "pingtimer.h" +#include "treeserver.h" +#include "commandbuilder.h" + +PingTimer::PingTimer(TreeServer* ts) + : Timer(Utils->PingFreq) + , server(ts) + , state(PS_SENDPING) +{ +} + +PingTimer::State PingTimer::TickInternal() +{ + // Timer expired, take next action based on what happened last time + if (state == PS_SENDPING) + { + // Last ping was answered, send next ping + server->GetSocket()->WriteLine(CmdBuilder("PING").push(server->GetID())); + LastPingMsec = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + // Warn next unless warnings are disabled. If they are, jump straight to timeout. + if (Utils->PingWarnTime) + return PS_WARN; + else + return PS_TIMEOUT; + } + else if (state == PS_WARN) + { + // No pong arrived in PingWarnTime seconds, send a warning to opers + ServerInstance->SNO->WriteToSnoMask('l', "Server \002%s\002 has not responded to PING for %d seconds, high latency.", server->GetName().c_str(), GetInterval()); + return PS_TIMEOUT; + } + else // PS_TIMEOUT + { + // They didn't answer the last ping, if they are locally connected, get rid of them + if (server->IsLocal()) + { + TreeSocket* sock = server->GetSocket(); + sock->SendError("Ping timeout"); + sock->Close(); + } + + // If the server is non-locally connected, don't do anything until we get a PONG. + // This is to avoid pinging the server and warning opers more than once. + // If they do answer eventually, we will move to the PS_SENDPING state and ping them again. + return PS_IDLE; + } +} + +void PingTimer::SetState(State newstate) +{ + state = newstate; + + // Set when should the next Tick() happen based on the state + if (state == PS_SENDPING) + SetInterval(Utils->PingFreq); + else if (state == PS_WARN) + SetInterval(Utils->PingWarnTime); + else if (state == PS_TIMEOUT) + SetInterval(Utils->PingFreq - Utils->PingWarnTime); + + // If state == PS_IDLE, do not set the timer, see above why +} + +bool PingTimer::Tick(time_t currtime) +{ + if (server->IsDead()) + return false; + + SetState(TickInternal()); + return false; +} + +void PingTimer::OnPong() +{ + // Calculate RTT + long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + server->rtt = ts - LastPingMsec; + + // Change state to send ping next, also reschedules the timer appropriately + SetState(PS_SENDPING); +} diff --git a/src/modules/m_spanningtree/pingtimer.h b/src/modules/m_spanningtree/pingtimer.h new file mode 100644 index 000000000..753558689 --- /dev/null +++ b/src/modules/m_spanningtree/pingtimer.h @@ -0,0 +1,77 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +class TreeServer; + +/** Handles PINGing servers and killing them on timeout + */ +class PingTimer : public Timer +{ + enum State + { + /** Send PING next */ + PS_SENDPING, + /** Warn opers next */ + PS_WARN, + /** Kill the server next due to ping timeout */ + PS_TIMEOUT, + /** Do nothing */ + PS_IDLE + }; + + /** Server the timer is interacting with + */ + TreeServer* const server; + + /** What to do when the timer ticks next + */ + State state; + + /** Last ping time in milliseconds, used to calculate round trip time + */ + unsigned long LastPingMsec; + + /** Update internal state and reschedule timer according to the new state + * @param newstate State to change to + */ + void SetState(State newstate); + + /** Process timer tick event + * @return State to change to + */ + State TickInternal(); + + /** Called by the TimerManager when the timer expires + * @param currtime Time now + * @return Always false, we reschedule ourselves instead + */ + bool Tick(time_t currtime) CXX11_OVERRIDE; + + public: + /** Construct the timer. This doesn't schedule the timer. + * @param server TreeServer to interact with + */ + PingTimer(TreeServer* server); + + /** Register a PONG from the server + */ + void OnPong(); +}; diff --git a/src/modules/m_spanningtree/pong.cpp b/src/modules/m_spanningtree/pong.cpp index 5966d05d9..718d5f0bb 100644 --- a/src/modules/m_spanningtree/pong.cpp +++ b/src/modules/m_spanningtree/pong.cpp @@ -18,65 +18,24 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" +#include "utils.h" -bool TreeSocket::LocalPong(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandPong::HandleServer(TreeServer* server, CommandBase::Params& params) { - if (params.size() < 1) - return true; - - if (params.size() == 1) + if (server->IsBursting()) { - TreeServer* ServerSource = Utils->FindServer(prefix); - if (ServerSource) - { - ServerSource->SetPingFlag(); - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - ServerSource->rtt = ts - ServerSource->LastPingMsec; - } + ServerInstance->SNO->WriteGlobalSno('l', "Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", server->GetName().c_str()); + server->FinishBurst(); } - else - { - std::string forwardto = params[1]; - if (forwardto == ServerInstance->Config->GetSID() || forwardto == ServerInstance->Config->ServerName) - { - /* - * this is a PONG for us - * if the prefix is a user, check theyre local, and if they are, - * dump the PONG reply back to their fd. If its a server, do nowt. - * Services might want to send these s->s, but we dont need to yet. - */ - User* u = ServerInstance->FindNick(prefix); - if (u) - { - u->WriteServ("PONG %s %s",params[0].c_str(),params[1].c_str()); - } - TreeServer *ServerSource = Utils->FindServer(params[0]); - - if (ServerSource) - { - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - ServerSource->rtt = ts - ServerSource->LastPingMsec; - ServerSource->SetPingFlag(); - } - } - else - { - // not for us, pass it on :) - Utils->DoOneToOne(prefix,"PONG",params,forwardto); - } + if (params[0] == ServerInstance->Config->GetSID()) + { + // PONG for us + server->OnPong(); } - - return true; + return CMD_SUCCESS; } - diff --git a/src/modules/m_spanningtree/postcommand.cpp b/src/modules/m_spanningtree/postcommand.cpp index 3f5d427e1..c7b4707b3 100644 --- a/src/modules/m_spanningtree/postcommand.cpp +++ b/src/modules/m_spanningtree/postcommand.cpp @@ -17,69 +17,55 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" +#include "commandbuilder.h" -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -void ModuleSpanningTree::OnPostCommand(const std::string &command, const std::vector<std::string>& parameters, LocalUser *user, CmdResult result, const std::string &original_line) +void ModuleSpanningTree::OnPostCommand(Command* command, const CommandBase::Params& parameters, LocalUser* user, CmdResult result, bool loop) { if (result == CMD_SUCCESS) Utils->RouteCommand(NULL, command, parameters, user); } -void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string &command, const parameterlist& parameters, User *user) +void SpanningTreeUtilities::RouteCommand(TreeServer* origin, CommandBase* thiscmd, const CommandBase::Params& parameters, User* user) { - if (!ServerInstance->Parser->IsValidCommand(command, parameters.size(), user)) - return; - - /* We know it's non-null because IsValidCommand returned true */ - Command* thiscmd = ServerInstance->Parser->GetHandler(command); - + const std::string& command = thiscmd->name; RouteDescriptor routing = thiscmd->GetRouting(user, parameters); - - std::string sent_cmd = command; - parameterlist params; - if (routing.type == ROUTE_TYPE_LOCALONLY) - { - /* Broadcast when it's a core command with the default route descriptor and the source is a - * remote user or a remote server - */ + return; - Version ver = thiscmd->creator->GetVersion(); - if ((!(ver.Flags & VF_CORE)) || (IS_LOCAL(user)) || (IS_SERVER(user) == ServerInstance->FakeClient)) - return; + const bool encap = ((routing.type == ROUTE_TYPE_OPT_BCAST) || (routing.type == ROUTE_TYPE_OPT_UCAST)); + CmdBuilder params(user, encap ? "ENCAP" : command.c_str()); + TreeServer* sdest = NULL; - routing = ROUTE_BROADCAST; - } - else if (routing.type == ROUTE_TYPE_OPT_BCAST) + if (routing.type == ROUTE_TYPE_OPT_BCAST) { - params.push_back("*"); + params.push('*'); params.push_back(command); - sent_cmd = "ENCAP"; } - else if (routing.type == ROUTE_TYPE_OPT_UCAST) + else if (routing.type == ROUTE_TYPE_UNICAST || routing.type == ROUTE_TYPE_OPT_UCAST) { - TreeServer* sdest = FindServer(routing.serverdest); + sdest = static_cast<TreeServer*>(routing.server); if (!sdest) { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Trying to route ENCAP to nonexistent server %s", - routing.serverdest.c_str()); - return; + // Assume the command handler already validated routing.serverdest and have only returned success if the target is something that the + // user executing the command is allowed to look up e.g. target is not an uuid if user is local. + sdest = FindRouteTarget(routing.serverdest); + if (!sdest) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Trying to route %s%s to nonexistent server %s", (encap ? "ENCAP " : ""), command.c_str(), routing.serverdest.c_str()); + return; + } + } + + if (encap) + { + params.push_back(sdest->GetID()); + params.push_back(command); } - params.push_back(sdest->GetID()); - params.push_back(command); - sent_cmd = "ENCAP"; } else { @@ -88,14 +74,13 @@ void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string & if (!(ver.Flags & (VF_COMMON | VF_CORE)) && srcmodule != Creator) { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Routed command %s from non-VF_COMMON module %s", + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Routed command %s from non-VF_COMMON module %s", command.c_str(), srcmodule->ModuleSourceFile.c_str()); return; } } - std::string output_text; - ServerInstance->Parser->TranslateUIDs(thiscmd->translation, parameters, output_text, true, thiscmd); + std::string output_text = CommandParser::TranslateUIDs(thiscmd->translation, parameters, true, thiscmd); params.push_back(output_text); @@ -106,59 +91,40 @@ void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string & if (ServerInstance->Modes->FindPrefix(dest[0])) { pfx = dest[0]; - dest = dest.substr(1); + dest.erase(dest.begin()); } if (dest[0] == '#') { Channel* c = ServerInstance->FindChan(dest); if (!c) return; - TreeServerList list; // TODO OnBuildExemptList hook was here - GetListOfServersForChannel(c,list,pfx, CUList()); - std::string data = ":" + user->uuid + " " + sent_cmd; - for (unsigned int x = 0; x < params.size(); x++) - data += " " + params[x]; - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (origin && origin->GetSocket() == Sock) - continue; - if (Sock) - Sock->WriteLine(data); - } + CUList exempts; + SendChannelMessage(user->uuid, c, parameters[1], pfx, exempts, command.c_str(), origin ? origin->GetSocket() : NULL); } else if (dest[0] == '$') { - if (origin) - DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName()); - else - DoOneToMany(user->uuid, sent_cmd, params); + params.Forward(origin); } else { // user target? User* d = ServerInstance->FindNick(dest); - if (!d) + if (!d || IS_LOCAL(d)) return; - TreeServer* tsd = BestRouteTo(d->server); + TreeServer* tsd = TreeServer::Get(d)->GetRoute(); if (tsd == origin) // huh? no routing stuff around in a circle, please. return; - DoOneToOne(user->uuid, sent_cmd, params, d->server); + params.Unicast(d); } } else if (routing.type == ROUTE_TYPE_BROADCAST || routing.type == ROUTE_TYPE_OPT_BCAST) { - if (origin) - DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName()); - else - DoOneToMany(user->uuid, sent_cmd, params); + params.Forward(origin); } else if (routing.type == ROUTE_TYPE_UNICAST || routing.type == ROUTE_TYPE_OPT_UCAST) { - if (origin && routing.serverdest == origin->GetName()) - return; - DoOneToOne(user->uuid, sent_cmd, params, routing.serverdest); + params.Unicast(sdest->ServerUser); } } diff --git a/src/modules/m_spanningtree/precommand.cpp b/src/modules/m_spanningtree/precommand.cpp index b331571ca..5db8aafe3 100644 --- a/src/modules/m_spanningtree/precommand.cpp +++ b/src/modules/m_spanningtree/precommand.cpp @@ -18,20 +18,11 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ -ModResult ModuleSpanningTree::OnPreCommand(std::string &command, std::vector<std::string>& parameters, LocalUser *user, bool validated, const std::string &original_line) +ModResult ModuleSpanningTree::OnPreCommand(std::string &command, CommandBase::Params& parameters, LocalUser *user, bool validated) { /* If the command doesnt appear to be valid, we dont want to mess with it. */ if (!validated) @@ -45,10 +36,6 @@ ModResult ModuleSpanningTree::OnPreCommand(std::string &command, std::vector<std { return this->HandleSquit(parameters,user); } - else if (command == "MAP") - { - return this->HandleMap(parameters,user) ? MOD_RES_DENY : MOD_RES_PASSTHRU; - } else if (command == "LINKS") { this->HandleLinks(parameters,user); @@ -64,9 +51,7 @@ ModResult ModuleSpanningTree::OnPreCommand(std::string &command, std::vector<std } else if ((command == "VERSION") && (parameters.size() > 0)) { - this->HandleVersion(parameters,user); - return MOD_RES_DENY; + return this->HandleVersion(parameters,user); } return MOD_RES_PASSTHRU; } - diff --git a/src/modules/m_spanningtree/protocolinterface.cpp b/src/modules/m_spanningtree/protocolinterface.cpp index ca4147fea..e54a528d9 100644 --- a/src/modules/m_spanningtree/protocolinterface.cpp +++ b/src/modules/m_spanningtree/protocolinterface.cpp @@ -19,166 +19,105 @@ #include "inspircd.h" -#include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" #include "protocolinterface.h" +#include "commands.h" /* * For documentation on this class, see include/protocol.h. */ -void SpanningTreeProtocolInterface::GetServerList(ProtoServerList &sl) +void SpanningTreeProtocolInterface::GetServerList(ServerList& sl) { - sl.clear(); for (server_hash::iterator i = Utils->serverlist.begin(); i != Utils->serverlist.end(); i++) { - ProtoServer ps; + ServerInfo ps; ps.servername = i->second->GetName(); TreeServer* s = i->second->GetParent(); ps.parentname = s ? s->GetName() : ""; - ps.usercount = i->second->GetUserCount(); - ps.opercount = i->second->GetOperCount(); - ps.gecos = i->second->GetDesc(); + ps.usercount = i->second->UserCount; + ps.opercount = i->second->OperCount; + ps.description = i->second->GetDesc(); ps.latencyms = i->second->rtt; sl.push_back(ps); } } -bool SpanningTreeProtocolInterface::SendEncapsulatedData(const parameterlist &encap) +bool SpanningTreeProtocolInterface::SendEncapsulatedData(const std::string& targetmask, const std::string& cmd, const CommandBase::Params& params, User* source) { - if (encap[0].find_first_of("*?") != std::string::npos) + if (!source) + source = ServerInstance->FakeClient; + + CmdBuilder encap(source, "ENCAP"); + + // Are there any wildcards in the target string? + if (targetmask.find_first_of("*?") != std::string::npos) { - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "ENCAP", encap); - return true; + // Yes, send the target string as-is; servers will decide whether or not it matches them + encap.push(targetmask).push(cmd).insert(params).Broadcast(); } - return Utils->DoOneToOne(ServerInstance->Config->GetSID(), "ENCAP", encap, encap[0]); -} - -void SpanningTreeProtocolInterface::SendMetaData(Extensible* target, const std::string &key, const std::string &data) -{ - parameterlist params; - - User* u = dynamic_cast<User*>(target); - Channel* c = dynamic_cast<Channel*>(target); - if (u) - params.push_back(u->uuid); - else if (c) - params.push_back(c->name); else - params.push_back("*"); + { + // No wildcards which means the target string has to be the name of a known server + TreeServer* server = Utils->FindServer(targetmask); + if (!server) + return false; - params.push_back(key); - params.push_back(":" + data); + // Use the SID of the target in the message instead of the server name + encap.push(server->GetID()).push(cmd).insert(params).Unicast(server->ServerUser); + } - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"METADATA",params); + return true; } -void SpanningTreeProtocolInterface::SendTopic(Channel* channel, std::string &topic) +void SpanningTreeProtocolInterface::BroadcastEncap(const std::string& cmd, const CommandBase::Params& params, User* source, User* omit) { - parameterlist params; - - params.push_back(channel->name); - params.push_back(ConvToStr(ServerInstance->Time())); - params.push_back(ServerInstance->Config->ServerName); - params.push_back(":" + topic); + if (!source) + source = ServerInstance->FakeClient; - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FTOPIC", params); + // If omit is non-NULL we pass the route belonging to the user to Forward(), + // otherwise we pass NULL, which is equivalent to Broadcast() + TreeServer* server = (omit ? TreeServer::Get(omit)->GetRoute() : NULL); + CmdBuilder(source, "ENCAP * ").push_raw(cmd).insert(params).Forward(server); } -void SpanningTreeProtocolInterface::SendMode(const std::string &target, const parameterlist &modedata, const std::vector<TranslateType> &translate) +void SpanningTreeProtocolInterface::SendMetaData(User* u, const std::string& key, const std::string& data) { - if (modedata.empty()) - return; - - std::string outdata; - ServerInstance->Parser->TranslateUIDs(translate, modedata, outdata); - - std::string uidtarget; - ServerInstance->Parser->TranslateUIDs(TR_NICK, target, uidtarget); - - parameterlist outlist; - outlist.push_back(uidtarget); - outlist.push_back(outdata); - - User* a = ServerInstance->FindNick(uidtarget); - if (a) - { - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"MODE",outlist); - return; - } - else - { - Channel* c = ServerInstance->FindChan(target); - if (c) - { - outlist.insert(outlist.begin() + 1, ConvToStr(c->age)); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FMODE",outlist); - } - } + CommandMetadata::Builder(u, key, data).Broadcast(); } -void SpanningTreeProtocolInterface::SendSNONotice(const std::string &snomask, const std::string &text) +void SpanningTreeProtocolInterface::SendMetaData(Channel* c, const std::string& key, const std::string& data) { - parameterlist p; - p.push_back(snomask); - p.push_back(":" + text); - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "SNONOTICE", p); + CommandMetadata::Builder(c, key, data).Broadcast(); } -void SpanningTreeProtocolInterface::PushToClient(User* target, const std::string &rawline) +void SpanningTreeProtocolInterface::SendMetaData(const std::string& key, const std::string& data) { - parameterlist p; - p.push_back(target->uuid); - p.push_back(":" + rawline); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "PUSH", p, target->server); -} - -void SpanningTreeProtocolInterface::SendChannel(Channel* target, char status, const std::string &text) -{ - TreeServerList list; - CUList exempt_list; - Utils->GetListOfServersForChannel(target,list,status,exempt_list); - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (Sock) - Sock->WriteLine(text); - } + CommandMetadata::Builder(key, data).Broadcast(); } - -void SpanningTreeProtocolInterface::SendChannelPrivmsg(Channel* target, char status, const std::string &text) +void SpanningTreeProtocolInterface::Server::SendMetaData(const std::string& key, const std::string& data) { - std::string cname = target->name; - if (status) - cname.insert(0, 1, status); - - SendChannel(target, status, ":" + ServerInstance->Config->GetSID()+" PRIVMSG "+cname+" :"+text); + sock->WriteLine(CommandMetadata::Builder(key, data)); } -void SpanningTreeProtocolInterface::SendChannelNotice(Channel* target, char status, const std::string &text) +void SpanningTreeProtocolInterface::SendSNONotice(char snomask, const std::string &text) { - std::string cname = target->name; - if (status) - cname.insert(0, 1, status); - - SendChannel(target, status, ":" + ServerInstance->Config->GetSID()+" NOTICE "+cname+" :"+text); + CmdBuilder("SNONOTICE").push(snomask).push_last(text).Broadcast(); } -void SpanningTreeProtocolInterface::SendUserPrivmsg(User* target, const std::string &text) +void SpanningTreeProtocolInterface::SendMessage(Channel* target, char status, const std::string& text, MessageType msgtype) { - parameterlist p; - p.push_back(target->uuid); - p.push_back(":" + text); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "PRIVMSG", p, target->server); + const char* cmd = (msgtype == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); + CUList exempt_list; + Utils->SendChannelMessage(ServerInstance->Config->GetSID(), target, text, status, exempt_list, cmd); } -void SpanningTreeProtocolInterface::SendUserNotice(User* target, const std::string &text) +void SpanningTreeProtocolInterface::SendMessage(User* target, const std::string& text, MessageType msgtype) { - parameterlist p; + CmdBuilder p(msgtype == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); p.push_back(target->uuid); - p.push_back(":" + text); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "NOTICE", p, target->server); + p.push_last(text); + p.Unicast(target); } diff --git a/src/modules/m_spanningtree/protocolinterface.h b/src/modules/m_spanningtree/protocolinterface.h index 297366893..969ed68bf 100644 --- a/src/modules/m_spanningtree/protocolinterface.h +++ b/src/modules/m_spanningtree/protocolinterface.h @@ -17,32 +17,27 @@ */ -#ifndef M_SPANNINGTREE_PROTOCOLINTERFACE_H -#define M_SPANNINGTREE_PROTOCOLINTERFACE_H - -class SpanningTreeUtilities; -class ModuleSpanningTree; +#pragma once class SpanningTreeProtocolInterface : public ProtocolInterface { - SpanningTreeUtilities* Utils; - void SendChannel(Channel* target, char status, const std::string &text); public: - SpanningTreeProtocolInterface(SpanningTreeUtilities* util) : Utils(util) { } - virtual ~SpanningTreeProtocolInterface() { } - - virtual bool SendEncapsulatedData(const parameterlist &encap); - virtual void SendMetaData(Extensible* target, const std::string &key, const std::string &data); - virtual void SendTopic(Channel* channel, std::string &topic); - virtual void SendMode(const std::string &target, const parameterlist &modedata, const std::vector<TranslateType> &types); - virtual void SendSNONotice(const std::string &snomask, const std::string &text); - virtual void PushToClient(User* target, const std::string &rawline); - virtual void SendChannelPrivmsg(Channel* target, char status, const std::string &text); - virtual void SendChannelNotice(Channel* target, char status, const std::string &text); - virtual void SendUserPrivmsg(User* target, const std::string &text); - virtual void SendUserNotice(User* target, const std::string &text); - virtual void GetServerList(ProtoServerList &sl); -}; + class Server : public ProtocolInterface::Server + { + TreeSocket* const sock; -#endif + public: + Server(TreeSocket* s) : sock(s) { } + void SendMetaData(const std::string& key, const std::string& data) CXX11_OVERRIDE; + }; + bool SendEncapsulatedData(const std::string& targetmask, const std::string& cmd, const CommandBase::Params& params, User* source) CXX11_OVERRIDE; + void BroadcastEncap(const std::string& cmd, const CommandBase::Params& params, User* source, User* omit) CXX11_OVERRIDE; + void SendMetaData(User* user, const std::string& key, const std::string& data) CXX11_OVERRIDE; + void SendMetaData(Channel* chan, const std::string& key, const std::string& data) CXX11_OVERRIDE; + void SendMetaData(const std::string& key, const std::string& data) CXX11_OVERRIDE; + void SendSNONotice(char snomask, const std::string& text) CXX11_OVERRIDE; + void SendMessage(Channel* target, char status, const std::string& text, MessageType msgtype) CXX11_OVERRIDE; + void SendMessage(User* target, const std::string& text, MessageType msgtype) CXX11_OVERRIDE; + void GetServerList(ServerList& sl) CXX11_OVERRIDE; +}; diff --git a/src/modules/m_spanningtree/push.cpp b/src/modules/m_spanningtree/push.cpp deleted file mode 100644 index b791376ea..000000000 --- a/src/modules/m_spanningtree/push.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" - -#include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -bool TreeSocket::Push(const std::string &prefix, parameterlist ¶ms) -{ - if (params.size() < 2) - return true; - User* u = ServerInstance->FindNick(params[0]); - if (!u) - return true; - if (IS_LOCAL(u)) - { - u->Write(params[1]); - } - else - { - // continue the raw onwards - params[1] = ":" + params[1]; - Utils->DoOneToOne(prefix,"PUSH",params,u->server); - } - return true; -} - diff --git a/src/modules/m_spanningtree/rconnect.cpp b/src/modules/m_spanningtree/rconnect.cpp index d4254cac6..7779355e2 100644 --- a/src/modules/m_spanningtree/rconnect.cpp +++ b/src/modules/m_spanningtree/rconnect.cpp @@ -19,34 +19,25 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "resolvers.h" #include "main.h" #include "utils.h" -#include "treeserver.h" -#include "link.h" -#include "treesocket.h" #include "commands.h" -CommandRConnect::CommandRConnect (Module* Creator, SpanningTreeUtilities* Util) - : Command(Creator, "RCONNECT", 2), Utils(Util) +CommandRConnect::CommandRConnect (Module* Creator) + : Command(Creator, "RCONNECT", 2) { flags_needed = 'o'; syntax = "<remote-server-mask> <target-server-mask>"; } -CmdResult CommandRConnect::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandRConnect::Handle(User* user, const Params& parameters) { - if (IS_LOCAL(user)) + /* First see if the server which is being asked to connect to another server in fact exists */ + if (!Utils->FindServerMask(parameters[0])) { - if (!Utils->FindServerMask(parameters[0])) - { - user->WriteServ("NOTICE %s :*** RCONNECT: Server \002%s\002 isn't connected to the network!", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - user->WriteServ("NOTICE %s :*** RCONNECT: Sending remote connect to \002%s\002 to connect server \002%s\002.",user->nick.c_str(),parameters[0].c_str(),parameters[1].c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** RCONNECT: Server \002%s\002 isn't connected to the network!", parameters[0].c_str())); + return CMD_FAILURE; } /* Is this aimed at our server? */ @@ -54,14 +45,29 @@ CmdResult CommandRConnect::Handle (const std::vector<std::string>& parameters, U { /* Yes, initiate the given connect */ ServerInstance->SNO->WriteToSnoMask('l',"Remote CONNECT from %s matching \002%s\002, connecting server \002%s\002",user->nick.c_str(),parameters[0].c_str(),parameters[1].c_str()); - std::vector<std::string> para; + CommandBase::Params para; para.push_back(parameters[1]); ((ModuleSpanningTree*)(Module*)creator)->HandleConnect(para, user); } + else + { + /* It's not aimed at our server, but if the request originates from our user + * acknowledge that we sent the request. + * + * It's possible that we're asking a server for something that makes no sense + * (e.g. connect to itself or to an already connected server), but we don't check + * for those conditions here, as ModuleSpanningTree::HandleConnect() (which will run + * on the target) does all the checking and error reporting. + */ + if (IS_LOCAL(user)) + { + user->WriteNotice("*** RCONNECT: Sending remote connect to \002 " + parameters[0] + "\002 to connect server \002" + parameters[1] + "\002."); + } + } return CMD_SUCCESS; } -RouteDescriptor CommandRConnect::GetRouting(User* user, const std::vector<std::string>& parameters) +RouteDescriptor CommandRConnect::GetRouting(User* user, const Params& parameters) { return ROUTE_UNICAST(parameters[0]); } diff --git a/src/modules/account.h b/src/modules/m_spanningtree/remoteuser.cpp index ba671ba0b..717a6fd9f 100644 --- a/src/modules/account.h +++ b/src/modules/m_spanningtree/remoteuser.cpp @@ -1,7 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -17,28 +17,17 @@ */ -#ifndef ACCOUNT_H -#define ACCOUNT_H +#include "inspircd.h" -#include <map> -#include <string> +#include "main.h" +#include "remoteuser.h" -class AccountEvent : public Event +SpanningTree::RemoteUser::RemoteUser(const std::string& uid, Server* srv) + : ::RemoteUser(uid, srv) { - public: - User* const user; - const std::string account; - AccountEvent(Module* me, User* u, const std::string& name) - : Event(me, "account_login"), user(u), account(name) - { - } -}; - -typedef StringExtItem AccountExtItem; +} -inline AccountExtItem* GetAccountExtItem() +void SpanningTree::RemoteUser::WriteRemoteNumeric(const Numeric::Numeric& numeric) { - return static_cast<AccountExtItem*>(ServerInstance->Extensions.GetItem("accountname")); + CommandNum::Builder(this, numeric).Unicast(this); } - -#endif diff --git a/src/modules/sasl.h b/src/modules/m_spanningtree/remoteuser.h index f67351104..416f2f760 100644 --- a/src/modules/sasl.h +++ b/src/modules/m_spanningtree/remoteuser.h @@ -1,7 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -17,18 +17,16 @@ */ -#ifndef SASL_H -#define SASL_H +#pragma once -class SASLFallback : public Event +namespace SpanningTree +{ + class RemoteUser; +} + +class SpanningTree::RemoteUser : public ::RemoteUser { public: - const parameterlist& params; - SASLFallback(Module* me, const parameterlist& p) - : Event(me, "sasl_fallback"), params(p) - { - Send(); - } + RemoteUser(const std::string& uid, Server* srv); + void WriteRemoteNumeric(const Numeric::Numeric& numeric) CXX11_OVERRIDE; }; - -#endif diff --git a/src/modules/m_spanningtree/resolvers.cpp b/src/modules/m_spanningtree/resolvers.cpp index d7c4c5227..ded0573af 100644 --- a/src/modules/m_spanningtree/resolvers.cpp +++ b/src/modules/m_spanningtree/resolvers.cpp @@ -19,9 +19,8 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" +#include "cachetimer.h" #include "resolvers.h" #include "main.h" #include "utils.h" @@ -29,29 +28,35 @@ #include "link.h" #include "treesocket.h" -/* $ModDep: m_spanningtree/resolvers.h m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/link.h m_spanningtree/treesocket.h */ - /** This class is used to resolve server hostnames during /connect and autoconnect. * As of 1.1, the resolver system is seperated out from BufferedSocket, so we must do this * resolver step first ourselves if we need it. This is totally nonblocking, and will * callback to OnLookupComplete or OnError when completed. Once it has completed we * will have an IP address which we can then use to continue our connection. */ -ServernameResolver::ServernameResolver(SpanningTreeUtilities* Util, const std::string &hostname, Link* x, bool &cached, QueryType qt, Autoconnect* myac) - : Resolver(hostname, qt, cached, Util->Creator), Utils(Util), query(qt), host(hostname), MyLink(x), myautoconnect(myac) +ServernameResolver::ServernameResolver(DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt, Autoconnect* myac) + : DNS::Request(mgr, Utils->Creator, hostname, qt) + , query(qt), host(hostname), MyLink(x), myautoconnect(myac) { } -void ServernameResolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) +void ServernameResolver::OnLookupComplete(const DNS::Query *r) { + const DNS::ResourceRecord* const ans_record = r->FindAnswerOfType(this->question.type); + if (!ans_record) + { + OnError(r); + return; + } + /* Initiate the connection, now that we have an IP to use. * Passing a hostname directly to BufferedSocket causes it to * just bail and set its FD to -1. */ - TreeServer* CheckDupe = Utils->FindServer(MyLink->Name.c_str()); + TreeServer* CheckDupe = Utils->FindServer(MyLink->Name); if (!CheckDupe) /* Check that nobody tried to connect it successfully while we were resolving */ { - TreeSocket* newsocket = new TreeSocket(Utils, MyLink, myautoconnect, result); + TreeSocket* newsocket = new TreeSocket(MyLink, myautoconnect, ans_record->rdata); if (newsocket->GetFd() > -1) { /* We're all OK */ @@ -66,47 +71,83 @@ void ServernameResolver::OnLookupComplete(const std::string &result, unsigned in } } -void ServernameResolver::OnError(ResolverError e, const std::string &errormessage) +void ServernameResolver::OnError(const DNS::Query *r) { - /* Ooops! */ - if (query == DNS_QUERY_AAAA) + if (r->error == DNS::ERROR_UNLOADED) { - bool cached = false; - ServernameResolver* snr = new ServernameResolver(Utils, host, MyLink, cached, DNS_QUERY_A, myautoconnect); - ServerInstance->AddResolver(snr, cached); + // We're being unloaded, skip the snotice and ConnectServer() below to prevent autoconnect creating new sockets return; } - ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: Unable to resolve hostname - %s", MyLink->Name.c_str(), errormessage.c_str() ); + + if (query == DNS::QUERY_AAAA) + { + ServernameResolver* snr = new ServernameResolver(this->manager, host, MyLink, DNS::QUERY_A, myautoconnect); + try + { + this->manager->Process(snr); + return; + } + catch (DNS::Exception &) + { + delete snr; + } + } + + ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: Unable to resolve hostname - %s", MyLink->Name.c_str(), this->manager->GetErrorStr(r->error).c_str()); Utils->Creator->ConnectServer(myautoconnect, false); } -SecurityIPResolver::SecurityIPResolver(Module* me, SpanningTreeUtilities* U, const std::string &hostname, Link* x, bool &cached, QueryType qt) - : Resolver(hostname, qt, cached, me), MyLink(x), Utils(U), mine(me), host(hostname), query(qt) +SecurityIPResolver::SecurityIPResolver(Module* me, DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt) + : DNS::Request(mgr, me, hostname, qt) + , MyLink(x), mine(me), host(hostname), query(qt) { } -void SecurityIPResolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) +void SecurityIPResolver::OnLookupComplete(const DNS::Query *r) { for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i != Utils->LinkBlocks.end(); ++i) { Link* L = *i; if (L->IPAddr == host) { - Utils->ValidIPs.push_back(result); + for (std::vector<DNS::ResourceRecord>::const_iterator j = r->answers.begin(); j != r->answers.end(); ++j) + { + const DNS::ResourceRecord& ans_record = *j; + if (ans_record.type == this->question.type) + Utils->ValidIPs.push_back(ans_record.rdata); + } break; } } } -void SecurityIPResolver::OnError(ResolverError e, const std::string &errormessage) +void SecurityIPResolver::OnError(const DNS::Query *r) { - if (query == DNS_QUERY_AAAA) + // This can be called because of us being unloaded but we don't have to do anything differently + if (query == DNS::QUERY_AAAA) { - bool cached = false; - SecurityIPResolver* res = new SecurityIPResolver(mine, Utils, host, MyLink, cached, DNS_QUERY_A); - ServerInstance->AddResolver(res, cached); - return; + SecurityIPResolver* res = new SecurityIPResolver(mine, this->manager, host, MyLink, DNS::QUERY_A); + try + { + this->manager->Process(res); + return; + } + catch (DNS::Exception &) + { + delete res; + } } - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Could not resolve IP associated with Link '%s': %s", - MyLink->Name.c_str(),errormessage.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Could not resolve IP associated with Link '%s': %s", + MyLink->Name.c_str(), this->manager->GetErrorStr(r->error).c_str()); +} + +CacheRefreshTimer::CacheRefreshTimer() + : Timer(3600, true) +{ +} + +bool CacheRefreshTimer::Tick(time_t TIME) +{ + Utils->RefreshIPCache(); + return true; } diff --git a/src/modules/m_spanningtree/resolvers.h b/src/modules/m_spanningtree/resolvers.h index 65b9e7249..782ac86ef 100644 --- a/src/modules/m_spanningtree/resolvers.h +++ b/src/modules/m_spanningtree/resolvers.h @@ -18,30 +18,27 @@ */ -#ifndef M_SPANNINGTREE_RESOLVERS_H -#define M_SPANNINGTREE_RESOLVERS_H +#pragma once -#include "socket.h" #include "inspircd.h" -#include "xline.h" +#include "modules/dns.h" #include "utils.h" #include "link.h" /** Handle resolving of server IPs for the cache */ -class SecurityIPResolver : public Resolver +class SecurityIPResolver : public DNS::Request { private: reference<Link> MyLink; - SpanningTreeUtilities* Utils; Module* mine; std::string host; - QueryType query; + DNS::QueryType query; public: - SecurityIPResolver(Module* me, SpanningTreeUtilities* U, const std::string &hostname, Link* x, bool &cached, QueryType qt); - void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached); - void OnError(ResolverError e, const std::string &errormessage); + SecurityIPResolver(Module* me, DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt); + void OnLookupComplete(const DNS::Query *r) CXX11_OVERRIDE; + void OnError(const DNS::Query *q) CXX11_OVERRIDE; }; /** This class is used to resolve server hostnames during /connect and autoconnect. @@ -50,18 +47,15 @@ class SecurityIPResolver : public Resolver * callback to OnLookupComplete or OnError when completed. Once it has completed we * will have an IP address which we can then use to continue our connection. */ -class ServernameResolver : public Resolver +class ServernameResolver : public DNS::Request { private: - SpanningTreeUtilities* Utils; - QueryType query; + DNS::QueryType query; std::string host; reference<Link> MyLink; reference<Autoconnect> myautoconnect; public: - ServernameResolver(SpanningTreeUtilities* Util, const std::string &hostname, Link* x, bool &cached, QueryType qt, Autoconnect* myac); - void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached); - void OnError(ResolverError e, const std::string &errormessage); + ServernameResolver(DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt, Autoconnect* myac); + void OnLookupComplete(const DNS::Query *r) CXX11_OVERRIDE; + void OnError(const DNS::Query *q) CXX11_OVERRIDE; }; - -#endif diff --git a/src/modules/m_spanningtree/rsquit.cpp b/src/modules/m_spanningtree/rsquit.cpp index 027ae02ab..7ede80b4c 100644 --- a/src/modules/m_spanningtree/rsquit.cpp +++ b/src/modules/m_spanningtree/rsquit.cpp @@ -19,76 +19,48 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" #include "commands.h" -CommandRSQuit::CommandRSQuit (Module* Creator, SpanningTreeUtilities* Util) - : Command(Creator, "RSQUIT", 1), Utils(Util) +CommandRSQuit::CommandRSQuit(Module* Creator) + : Command(Creator, "RSQUIT", 1) { flags_needed = 'o'; syntax = "<target-server-mask> [reason]"; } -CmdResult CommandRSQuit::Handle (const std::vector<std::string>& parameters, User *user) +CmdResult CommandRSQuit::Handle(User* user, const Params& parameters) { TreeServer *server_target; // Server to squit - TreeServer *server_linked; // Server target is linked to server_target = Utils->FindServerMask(parameters[0]); if (!server_target) { - user->WriteServ("NOTICE %s :*** RSQUIT: Server \002%s\002 isn't connected to the network!", user->nick.c_str(), parameters[0].c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** RSQUIT: Server \002%s\002 isn't connected to the network!", parameters[0].c_str())); return CMD_FAILURE; } - if (server_target == Utils->TreeRoot) + if (server_target->IsRoot()) { - NoticeUser(user, "*** RSQUIT: Foolish mortal, you cannot make a server SQUIT itself! ("+parameters[0]+" matches local server name)"); + user->WriteRemoteNotice(InspIRCd::Format("*** RSQUIT: Foolish mortal, you cannot make a server SQUIT itself! (%s matches local server name)", parameters[0].c_str())); return CMD_FAILURE; } - server_linked = server_target->GetParent(); - - if (server_linked == Utils->TreeRoot) + if (server_target->IsLocal()) { // We have been asked to remove server_target. - TreeSocket* sock = server_target->GetSocket(); - if (sock) - { - const char *reason = parameters.size() == 2 ? parameters[1].c_str() : "No reason"; - ServerInstance->SNO->WriteToSnoMask('l',"RSQUIT: Server \002%s\002 removed from network by %s (%s)", parameters[0].c_str(), user->nick.c_str(), reason); - sock->Squit(server_target, "Server quit by " + user->GetFullRealHost() + " (" + reason + ")"); - sock->Close(); - } + const char* reason = parameters.size() == 2 ? parameters[1].c_str() : "No reason"; + ServerInstance->SNO->WriteToSnoMask('l',"RSQUIT: Server \002%s\002 removed from network by %s (%s)", parameters[0].c_str(), user->nick.c_str(), reason); + server_target->SQuit("Server quit by " + user->GetFullRealHost() + " (" + reason + ")"); } return CMD_SUCCESS; } -RouteDescriptor CommandRSQuit::GetRouting(User* user, const std::vector<std::string>& parameters) +RouteDescriptor CommandRSQuit::GetRouting(User* user, const Params& parameters) { return ROUTE_UNICAST(parameters[0]); } - -// XXX use protocol interface instead of rolling our own :) -void CommandRSQuit::NoticeUser(User* user, const std::string &msg) -{ - if (IS_LOCAL(user)) - { - user->WriteServ("NOTICE %s :%s",user->nick.c_str(),msg.c_str()); - } - else - { - parameterlist params; - params.push_back(user->nick); - params.push_back("NOTICE "+ConvToStr(user->nick)+" :"+msg); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "PUSH", params, user->server); - } -} - diff --git a/src/modules/m_spanningtree/save.cpp b/src/modules/m_spanningtree/save.cpp index 92999b422..be3a0a687 100644 --- a/src/modules/m_spanningtree/save.cpp +++ b/src/modules/m_spanningtree/save.cpp @@ -18,38 +18,24 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" #include "utils.h" -#include "treeserver.h" #include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" /** * SAVE command - force nick change to UID on timestamp match */ -bool TreeSocket::ForceNick(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandSave::Handle(User* user, Params& params) { - if (params.size() < 2) - return true; - - User* u = ServerInstance->FindNick(params[0]); - time_t ts = atol(params[1].c_str()); + User* u = ServerInstance->FindUUID(params[0]); + if (!u) + return CMD_FAILURE; - if ((u) && (!IS_SERVER(u)) && (u->age == ts)) - { - Utils->DoOneToAllButSender(prefix,"SAVE",params,prefix); + time_t ts = ConvToNum<time_t>(params[1]); - if (!u->ForceNickChange(u->uuid.c_str())) - { - ServerInstance->Users->QuitUser(u, "Nickname collision"); - } - } + if (u->age == ts) + u->ChangeNick(u->uuid, SavedTimestamp); - return true; + return CMD_SUCCESS; } - diff --git a/src/modules/m_spanningtree/server.cpp b/src/modules/m_spanningtree/server.cpp index d3033799e..07004a1e8 100644 --- a/src/modules/m_spanningtree/server.cpp +++ b/src/modules/m_spanningtree/server.cpp @@ -19,113 +19,102 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" +#include "modules/ssl.h" #include "main.h" #include "utils.h" #include "link.h" #include "treeserver.h" #include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h m_spanningtree/link.h */ +#include "commands.h" /* * Some server somewhere in the network introducing another server. * -- w */ -bool TreeSocket::RemoteServer(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandServer::HandleServer(TreeServer* ParentOfThis, Params& params) { - if (params.size() < 5) - { - SendError("Protocol error - Not enough parameters for SERVER command"); - return false; - } + const std::string& servername = params[0]; + const std::string& sid = params[1]; + const std::string& description = params.back(); + TreeSocket* socket = ParentOfThis->GetSocket(); - std::string servername = params[0]; - // password is not used for a remote server - // hopcount is not used (ever) - std::string sid = params[3]; - std::string description = params[4]; - TreeServer* ParentOfThis = Utils->FindServer(prefix); - - if (!ParentOfThis) - { - this->SendError("Protocol error - Introduced remote server from unknown server "+prefix); - return false; - } - if (!ServerInstance->IsSID(sid)) + if (!InspIRCd::IsSID(sid)) { - this->SendError("Invalid format server ID: "+sid+"!"); - return false; + socket->SendError("Invalid format server ID: "+sid+"!"); + return CMD_FAILURE; } TreeServer* CheckDupe = Utils->FindServer(servername); if (CheckDupe) { - this->SendError("Server "+servername+" already exists!"); + socket->SendError("Server "+servername+" already exists!"); ServerInstance->SNO->WriteToSnoMask('L', "Server \2"+CheckDupe->GetName()+"\2 being introduced from \2" + ParentOfThis->GetName() + "\2 denied, already exists. Closing link with " + ParentOfThis->GetName()); - return false; + return CMD_FAILURE; } CheckDupe = Utils->FindServer(sid); if (CheckDupe) { - this->SendError("Server ID "+sid+" already exists! You may want to specify the server ID for the server manually with <server:id> so they do not conflict."); + socket->SendError("Server ID "+sid+" already exists! You may want to specify the server ID for the server manually with <server:id> so they do not conflict."); ServerInstance->SNO->WriteToSnoMask('L', "Server \2"+servername+"\2 being introduced from \2" + ParentOfThis->GetName() + "\2 denied, server ID already exists on the network. Closing link with " + ParentOfThis->GetName()); - return false; + return CMD_FAILURE; } Link* lnk = Utils->FindLink(servername); - TreeServer *Node = new TreeServer(Utils, servername, description, sid, ParentOfThis,NULL, lnk ? lnk->Hidden : false); + TreeServer* Node = new TreeServer(servername, description, sid, ParentOfThis, ParentOfThis->GetSocket(), lnk ? lnk->Hidden : false); + + HandleExtra(Node, params); - ParentOfThis->AddChild(Node); - params[4] = ":" + params[4]; - Utils->DoOneToAllButSender(prefix,"SERVER",params,prefix); ServerInstance->SNO->WriteToSnoMask('L', "Server \002"+ParentOfThis->GetName()+"\002 introduced server \002"+servername+"\002 ("+description+")"); - return true; + return CMD_SUCCESS; } +void CommandServer::HandleExtra(TreeServer* newserver, Params& params) +{ + for (CommandBase::Params::const_iterator i = params.begin() + 2; i != params.end() - 1; ++i) + { + const std::string& prop = *i; + std::string::size_type p = prop.find('='); -/* - * This is used after the other side of a connection has accepted our credentials. - * They are then introducing themselves to us, BEFORE either of us burst. -- w - */ -bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) + std::string key = prop; + std::string val; + if (p != std::string::npos) + { + key.erase(p); + val.assign(prop, p+1, std::string::npos); + } + + if (key == "burst") + newserver->BeginBurst(ConvToNum<uint64_t>(val)); + } +} + +Link* TreeSocket::AuthRemote(const CommandBase::Params& params) { if (params.size() < 5) { SendError("Protocol error - Not enough parameters for SERVER command"); - return false; + return NULL; } - irc::string servername = params[0].c_str(); - std::string sname = params[0]; - std::string password = params[1]; - std::string sid = params[3]; - std::string description = params[4]; - int hops = atoi(params[2].c_str()); + const std::string& sname = params[0]; + const std::string& password = params[1]; + const std::string& sid = params[3]; + const std::string& description = params.back(); this->SendCapabilities(2); - if (hops) - { - this->SendError("Server too far away for authentication"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, server is too far away for authentication"); - return false; - } - if (!ServerInstance->IsSID(sid)) { this->SendError("Invalid format server ID: "+sid+"!"); - return false; + return NULL; } for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) { Link* x = *i; - if (x->Name != servername && x->Name != "*") // open link allowance + if ((!stdalgo::string::equalsci(x->Name, sname)) && (x->Name != "*")) // open link allowance continue; if (!ComparePass(*x, password)) @@ -134,22 +123,36 @@ bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) continue; } - TreeServer* CheckDupe = Utils->FindServer(sname); - if (CheckDupe) - { - std::string pname = CheckDupe->GetParent() ? CheckDupe->GetParent()->GetName() : "<ourself>"; - SendError("Server "+sname+" already exists on server "+pname+"!"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, already exists on server "+pname); - return false; - } - CheckDupe = Utils->FindServer(sid); - if (CheckDupe) + if (!CheckDuplicate(sname, sid)) + return NULL; + + ServerInstance->SNO->WriteToSnoMask('l',"Verified server connection " + linkID + " ("+description+")"); + + const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(this); + if (ssliohook) { - this->SendError("Server ID "+sid+" already exists on the network! You may want to specify the server ID for the server manually with <server:id> so they do not conflict."); - ServerInstance->SNO->WriteToSnoMask('l',"Server \2"+assign(servername)+"\2 being introduced denied, server ID already exists on the network. Closing link."); - return false; + std::string ciphersuite; + ssliohook->GetCiphersuite(ciphersuite); + ServerInstance->SNO->WriteToSnoMask('l', "Negotiated ciphersuite %s on link %s", ciphersuite.c_str(), x->Name.c_str()); } + return x; + } + + this->SendError("Mismatched server name or password (check the other server's snomask output for details - e.g. umode +s +Ll)"); + ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); + return NULL; +} + +/* + * This is used after the other side of a connection has accepted our credentials. + * They are then introducing themselves to us, BEFORE either of us burst. -- w + */ +bool TreeSocket::Outbound_Reply_Server(CommandBase::Params& params) +{ + const Link* x = AuthRemote(params); + if (x) + { /* * They're in WAIT_AUTH_2 (having accepted our credentials). * Set our state to CONNECTED (since everything's peachy so far) and send our @@ -158,32 +161,17 @@ bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) * While we're at it, create a treeserver object so we know about them. * -- w */ - this->LinkState = CONNECTED; - - Utils->timeoutlist.erase(this); - linkID = sname; - - MyRoot = new TreeServer(Utils, sname, description, sid, Utils->TreeRoot, this, x->Hidden); - Utils->TreeRoot->AddChild(MyRoot); - this->DoBurst(MyRoot); - - params[4] = ":" + params[4]; - - /* IMPORTANT: Take password/hmac hash OUT of here before we broadcast the introduction! */ - params[1] = "*"; - Utils->DoOneToAllButSender(ServerInstance->Config->GetSID(),"SERVER",params,sname); + FinishAuth(params[0], params[3], params.back(), x->Hidden); return true; } - this->SendError("Mismatched server name or password (check the other server's snomask output for details - e.g. umode +s +Ll)"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); return false; } bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid) { - /* Check for fully initialized instances of the server by name */ + // Check if the server name is not in use by a server that's already fully connected TreeServer* CheckDupe = Utils->FindServer(sname); if (CheckDupe) { @@ -193,8 +181,8 @@ bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid return false; } - /* Check for fully initialized instances of the server by id */ - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Looking for dupe SID %s", sid.c_str()); + // Check if the id is not in use by a server that's already fully connected + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Looking for dupe SID %s", sid.c_str()); CheckDupe = Utils->FindServerID(sid); if (CheckDupe) @@ -212,60 +200,16 @@ bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid * Someone else is attempting to connect to us if this is called. Validate their credentials etc. * -- w */ -bool TreeSocket::Inbound_Server(parameterlist ¶ms) +bool TreeSocket::Inbound_Server(CommandBase::Params& params) { - if (params.size() < 5) - { - SendError("Protocol error - Missing SID"); - return false; - } - - irc::string servername = params[0].c_str(); - std::string sname = params[0]; - std::string password = params[1]; - std::string sid = params[3]; - std::string description = params[4]; - int hops = atoi(params[2].c_str()); - - this->SendCapabilities(2); - - if (hops) - { - this->SendError("Server too far away for authentication"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, server is too far away for authentication"); - return false; - } - - if (!ServerInstance->IsSID(sid)) + const Link* x = AuthRemote(params); + if (x) { - this->SendError("Invalid format server ID: "+sid+"!"); - return false; - } - - for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) - { - Link* x = *i; - if (x->Name != servername && x->Name != "*") // open link allowance - continue; - - if (!ComparePass(*x, password)) - { - ServerInstance->SNO->WriteToSnoMask('l',"Invalid password on link: %s", x->Name.c_str()); - continue; - } - - if (!CheckDuplicate(sname, sid)) - return false; - - ServerInstance->SNO->WriteToSnoMask('l',"Verified incoming server connection " + linkID + " ("+description+")"); - - this->SendCapabilities(2); - // Save these for later, so when they accept our credentials (indicated by BURST) we remember them this->capab->hidden = x->Hidden; - this->capab->sid = sid; - this->capab->description = description; - this->capab->name = sname; + this->capab->sid = params[3]; + this->capab->description = params.back(); + this->capab->name = params[0]; // Send our details: Our server name and description and hopcount of 0, // along with the sendpass from this block. @@ -276,8 +220,15 @@ bool TreeSocket::Inbound_Server(parameterlist ¶ms) return true; } - this->SendError("Mismatched server name or password (check the other server's snomask output for details - e.g. umode +s +Ll)"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); return false; } +CommandServer::Builder::Builder(TreeServer* server) + : CmdBuilder(server->GetParent()->GetID(), "SERVER") +{ + push(server->GetName()); + push(server->GetID()); + if (server->IsBursting()) + push_property("burst", ConvToStr(server->StartBurst)); + push_last(server->GetDesc()); +} diff --git a/src/modules/m_spanningtree/servercommand.cpp b/src/modules/m_spanningtree/servercommand.cpp new file mode 100644 index 000000000..5b8152846 --- /dev/null +++ b/src/modules/m_spanningtree/servercommand.cpp @@ -0,0 +1,60 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "main.h" +#include "servercommand.h" + +ServerCommand::ServerCommand(Module* Creator, const std::string& Name, unsigned int MinParams, unsigned int MaxParams) + : CommandBase(Creator, Name, MinParams, MaxParams) +{ +} + +void ServerCommand::RegisterService() +{ + ModuleSpanningTree* st = static_cast<ModuleSpanningTree*>(static_cast<Module*>(creator)); + st->CmdManager.AddCommand(this); +} + +RouteDescriptor ServerCommand::GetRouting(User* user, const Params& parameters) +{ + // Broadcast server-to-server commands unless overridden + return ROUTE_BROADCAST; +} + +time_t ServerCommand::ExtractTS(const std::string& tsstr) +{ + time_t TS = ConvToInt(tsstr); + if (!TS) + throw ProtocolException("Invalid TS"); + return TS; +} + +ServerCommand* ServerCommandManager::GetHandler(const std::string& command) const +{ + ServerCommandMap::const_iterator it = commands.find(command); + if (it != commands.end()) + return it->second; + return NULL; +} + +bool ServerCommandManager::AddCommand(ServerCommand* cmd) +{ + return commands.insert(std::make_pair(cmd->name, cmd)).second; +} diff --git a/src/modules/m_spanningtree/servercommand.h b/src/modules/m_spanningtree/servercommand.h new file mode 100644 index 000000000..6ea5a9251 --- /dev/null +++ b/src/modules/m_spanningtree/servercommand.h @@ -0,0 +1,104 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "utils.h" +#include "treeserver.h" + +class ProtocolException : public ModuleException +{ + public: + ProtocolException(const std::string& msg) + : ModuleException("Protocol violation: " + msg) + { + } +}; + +/** Base class for server-to-server commands that may have a (remote) user source or server source. + */ +class ServerCommand : public CommandBase +{ + public: + ServerCommand(Module* Creator, const std::string& Name, unsigned int MinPara = 0, unsigned int MaxPara = 0); + + /** Register this object in the ServerCommandManager + */ + void RegisterService() CXX11_OVERRIDE; + + virtual CmdResult Handle(User* user, Params& parameters) = 0; + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE; + + /** + * Extract the TS from a string. + * @param tsstr The string containing the TS. + * @return The raw timestamp value. + * This function throws a ProtocolException if it considers the TS invalid. Note that the detection of + * invalid timestamps is not designed to be bulletproof, only some cases - like "0" - trigger an exception. + */ + static time_t ExtractTS(const std::string& tsstr); +}; + +/** Base class for server-to-server command handlers which are only valid if their source is a user. + * When a server sends a command of this type and the source is a server (sid), the link is aborted. + */ +template <class T> +class UserOnlyServerCommand : public ServerCommand +{ + public: + UserOnlyServerCommand(Module* Creator, const std::string& Name, unsigned int MinPara = 0, unsigned int MaxPara = 0) + : ServerCommand(Creator, Name, MinPara, MaxPara) { } + + CmdResult Handle(User* user, Params& parameters) CXX11_OVERRIDE + { + RemoteUser* remoteuser = IS_REMOTE(user); + if (!remoteuser) + throw ProtocolException("Invalid source"); + return static_cast<T*>(this)->HandleRemote(remoteuser, parameters); + } +}; + +/** Base class for server-to-server command handlers which are only valid if their source is a server. + * When a server sends a command of this type and the source is a user (uuid), the link is aborted. + */ +template <class T> +class ServerOnlyServerCommand : public ServerCommand +{ + public: + ServerOnlyServerCommand(Module* Creator, const std::string& Name, unsigned int MinPara = 0, unsigned int MaxPara = 0) + : ServerCommand(Creator, Name, MinPara, MaxPara) { } + + CmdResult Handle(User* user, CommandBase::Params& parameters) CXX11_OVERRIDE + { + if (!IS_SERVER(user)) + throw ProtocolException("Invalid source"); + TreeServer* server = TreeServer::Get(user); + return static_cast<T*>(this)->HandleServer(server, parameters); + } +}; + +class ServerCommandManager +{ + typedef TR1NS::unordered_map<std::string, ServerCommand*> ServerCommandMap; + ServerCommandMap commands; + + public: + ServerCommand* GetHandler(const std::string& command) const; + bool AddCommand(ServerCommand* cmd); +}; diff --git a/src/modules/m_spanningtree/sinfo.cpp b/src/modules/m_spanningtree/sinfo.cpp new file mode 100644 index 000000000..a5dae783c --- /dev/null +++ b/src/modules/m_spanningtree/sinfo.cpp @@ -0,0 +1,55 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "inspircd.h" + +#include "treeserver.h" +#include "commands.h" + +CmdResult CommandSInfo::HandleServer(TreeServer* server, CommandBase::Params& params) +{ + const std::string& key = params.front(); + const std::string& value = params.back(); + + if (key == "fullversion") + { + server->SetFullVersion(value); + } + else if (key == "version") + { + server->SetVersion(value); + } + else if (key == "rawversion") + { + server->SetRawVersion(value); + } + else if (key == "desc") + { + // Only sent when the description of a server changes because of a rehash; not sent on burst + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Server description of " + server->GetName() + " changed: " + value); + server->SetDesc(value); + } + + return CMD_SUCCESS; +} + +CommandSInfo::Builder::Builder(TreeServer* server, const char* key, const std::string& val) + : CmdBuilder(server->GetID(), "SINFO") +{ + push(key).push_last(val); +} diff --git a/src/modules/m_spanningtree/svsjoin.cpp b/src/modules/m_spanningtree/svsjoin.cpp index 416502369..92187ddf7 100644 --- a/src/modules/m_spanningtree/svsjoin.cpp +++ b/src/modules/m_spanningtree/svsjoin.cpp @@ -19,19 +19,13 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" -#include "utils.h" -#include "treeserver.h" #include "commands.h" -CmdResult CommandSVSJoin::Handle(const std::vector<std::string>& parameters, User *user) +CmdResult CommandSVSJoin::Handle(User* user, Params& parameters) { // Check for valid channel name - if (!ServerInstance->IsChannel(parameters[1].c_str(), ServerInstance->Config->Limits.ChanMax)) + if (!ServerInstance->IsChannel(parameters[1])) return CMD_FAILURE; // Check target exists @@ -40,15 +34,25 @@ CmdResult CommandSVSJoin::Handle(const std::vector<std::string>& parameters, Use return CMD_FAILURE; /* only join if it's local, otherwise just pass it on! */ - if (IS_LOCAL(u)) - Channel::JoinUser(u, parameters[1].c_str(), false, "", false, ServerInstance->Time()); + LocalUser* localuser = IS_LOCAL(u); + if (localuser) + { + bool override = false; + std::string key; + if (parameters.size() >= 3) + { + key = parameters[2]; + if (key.empty()) + override = true; + } + + Channel::JoinUser(localuser, parameters[1], override, key); + } + return CMD_SUCCESS; } -RouteDescriptor CommandSVSJoin::GetRouting(User* user, const std::vector<std::string>& parameters) +RouteDescriptor CommandSVSJoin::GetRouting(User* user, const Params& parameters) { - User* u = ServerInstance->FindUUID(parameters[0]); - if (u) - return ROUTE_OPT_UCAST(u->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } diff --git a/src/modules/m_spanningtree/svsnick.cpp b/src/modules/m_spanningtree/svsnick.cpp index 59973202d..2514dfd6f 100644 --- a/src/modules/m_spanningtree/svsnick.cpp +++ b/src/modules/m_spanningtree/svsnick.cpp @@ -21,50 +21,56 @@ #include "inspircd.h" #include "main.h" -#include "utils.h" #include "commands.h" -CmdResult CommandSVSNick::Handle(const std::vector<std::string>& parameters, User *user) +CmdResult CommandSVSNick::Handle(User* user, Params& parameters) { User* u = ServerInstance->FindNick(parameters[0]); if (u && IS_LOCAL(u)) { + // The 4th parameter is optional and it is the expected nick TS of the target user. If this parameter is + // present and it doesn't match the user's nick TS, the SVSNICK is not acted upon. + // This makes it possible to detect the case when services wants to change the nick of a user, but the + // user changes their nick before the SVSNICK arrives, making the SVSNICK nick change (usually to a guest nick) + // unnecessary. Consider the following for example: + // + // 1. test changes nick to Attila which is protected by services + // 2. Services SVSNICKs the user to Guest12345 + // 3. Attila changes nick to Attila_ which isn't protected by services + // 4. SVSNICK arrives + // 5. Attila_ gets his nick changed to Guest12345 unnecessarily + // + // In this case when the SVSNICK is processed the target has already changed his nick to something + // which isn't protected, so changing the nick again to a Guest nick is not desired. + // However, if the expected nick TS parameter is present in the SVSNICK then the nick change in step 5 + // won't happen because the timestamps won't match. + if (parameters.size() > 3) + { + time_t ExpectedTS = ConvToInt(parameters[3]); + if (u->age != ExpectedTS) + return CMD_FAILURE; // Ignore SVSNICK + } + std::string nick = parameters[1]; if (isdigit(nick[0])) nick = u->uuid; - // Don't update the TS if the nick is exactly the same - if (u->nick == nick) - return CMD_FAILURE; - time_t NickTS = ConvToInt(parameters[2]); if (NickTS <= 0) return CMD_FAILURE; - ModuleSpanningTree* st = (ModuleSpanningTree*)(Module*)creator; - st->KeepNickTS = true; - u->age = NickTS; - - if (!u->ForceNickChange(nick.c_str())) + if (!u->ChangeNick(nick, NickTS)) { - /* buh. UID them */ - if (!u->ForceNickChange(u->uuid.c_str())) - { - ServerInstance->Users->QuitUser(u, "Nickname collision"); - } + // Changing to 'nick' failed (it may already be in use), change to the uuid + u->ChangeNick(u->uuid); } - - st->KeepNickTS = false; } return CMD_SUCCESS; } -RouteDescriptor CommandSVSNick::GetRouting(User* user, const std::vector<std::string>& parameters) +RouteDescriptor CommandSVSNick::GetRouting(User* user, const Params& parameters) { - User* u = ServerInstance->FindNick(parameters[0]); - if (u) - return ROUTE_OPT_UCAST(u->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } diff --git a/src/modules/m_spanningtree/svspart.cpp b/src/modules/m_spanningtree/svspart.cpp index 3bdf13b25..505a033e3 100644 --- a/src/modules/m_spanningtree/svspart.cpp +++ b/src/modules/m_spanningtree/svspart.cpp @@ -19,16 +19,10 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" -#include "utils.h" -#include "treeserver.h" #include "commands.h" -CmdResult CommandSVSPart::Handle(const std::vector<std::string>& parameters, User *user) +CmdResult CommandSVSPart::Handle(User* user, Params& parameters) { User* u = ServerInstance->FindUUID(parameters[0]); if (!u) @@ -46,10 +40,7 @@ CmdResult CommandSVSPart::Handle(const std::vector<std::string>& parameters, Use return CMD_SUCCESS; } -RouteDescriptor CommandSVSPart::GetRouting(User* user, const std::vector<std::string>& parameters) +RouteDescriptor CommandSVSPart::GetRouting(User* user, const Params& parameters) { - User* u = ServerInstance->FindUUID(parameters[0]); - if (u) - return ROUTE_OPT_UCAST(u->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } diff --git a/src/modules/m_spanningtree/cachetimer.cpp b/src/modules/m_spanningtree/translate.cpp index be438651d..66e1bb35b 100644 --- a/src/modules/m_spanningtree/cachetimer.cpp +++ b/src/modules/m_spanningtree/translate.cpp @@ -1,7 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -18,24 +18,31 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" +#include "translate.h" -#include "cachetimer.h" -#include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "link.h" -#include "treesocket.h" +std::string Translate::ModeChangeListToParams(const Modes::ChangeList::List& modes) +{ + std::string ret; + for (Modes::ChangeList::List::const_iterator i = modes.begin(); i != modes.end(); ++i) + { + const Modes::Change& item = *i; + ModeHandler* mh = item.mh; + if (!mh->NeedsParam(item.adding)) + continue; -/* $ModDep: m_spanningtree/cachetimer.h m_spanningtree/resolvers.h m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/link.h m_spanningtree/treesocket.h */ + ret.push_back(' '); -CacheRefreshTimer::CacheRefreshTimer(SpanningTreeUtilities *Util) : Timer(3600, ServerInstance->Time(), true), Utils(Util) -{ -} + if (mh->IsPrefixMode()) + { + User* target = ServerInstance->FindNick(item.param); + if (target) + { + ret.append(target->uuid); + continue; + } + } -void CacheRefreshTimer::Tick(time_t TIME) -{ - Utils->RefreshIPCache(); + ret.append(item.param); + } + return ret; } - diff --git a/src/modules/m_spanningtree/translate.h b/src/modules/m_spanningtree/translate.h new file mode 100644 index 000000000..a2bc6df78 --- /dev/null +++ b/src/modules/m_spanningtree/translate.h @@ -0,0 +1,30 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +namespace Translate +{ + /** Generate a list of mode parameters suitable for FMODE/MODE from a Modes::ChangeList::List + * @param modes List of mode changes + * @return List of mode parameters built from the input. Does not include the modes themselves, + * only the parameters. + */ + std::string ModeChangeListToParams(const Modes::ChangeList::List& modes); +} diff --git a/src/modules/m_spanningtree/treeserver.cpp b/src/modules/m_spanningtree/treeserver.cpp index 493b05ebf..ca7959ea7 100644 --- a/src/modules/m_spanningtree/treeserver.cpp +++ b/src/modules/m_spanningtree/treeserver.cpp @@ -21,56 +21,47 @@ #include "inspircd.h" -#include "socket.h" #include "xline.h" #include "main.h" -#include "../spanningtree.h" +#include "modules/server.h" #include "utils.h" #include "treeserver.h" -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h */ - /** We use this constructor only to create the 'root' item, Utils->TreeRoot, which * represents our own server. Therefore, it has no route, no parent, and * no socket associated with it. Its version string is our own local version. */ -TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id) - : ServerName(Name.c_str()), ServerDesc(Desc), Utils(Util), ServerUser(ServerInstance->FakeClient) +TreeServer::TreeServer() + : Server(ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc) + , Parent(NULL), Route(NULL) + , VersionString(ServerInstance->GetVersionString()) + , fullversion(ServerInstance->GetVersionString(true)) + , rawversion(INSPIRCD_VERSION) + , Socket(NULL), sid(ServerInstance->Config->GetSID()), behind_bursting(0), isdead(false) + , pingtimer(this) + , ServerUser(ServerInstance->FakeClient) + , age(ServerInstance->Time()), UserCount(ServerInstance->Users.LocalUserCount()) + , OperCount(0), rtt(0), StartBurst(0), Hidden(false) { - age = ServerInstance->Time(); - bursting = false; - Parent = NULL; - VersionString.clear(); - ServerUserCount = ServerOperCount = 0; - VersionString = ServerInstance->GetVersionString(); - Route = NULL; - Socket = NULL; /* Fix by brain */ - StartBurst = rtt = 0; - Warned = Hidden = false; AddHashEntry(); - SetID(id); } /** When we create a new server, we call this constructor to initialize it. * This constructor initializes the server's Route and Parent, and sets up - * its ping counters so that it will be pinged one minute from now. + * the ping timer for the server. */ -TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id, TreeServer* Above, TreeSocket* Sock, bool Hide) - : Parent(Above), ServerName(Name.c_str()), ServerDesc(Desc), Socket(Sock), Utils(Util), ServerUser(new FakeUser(id, Name)), Hidden(Hide) +TreeServer::TreeServer(const std::string& Name, const std::string& Desc, const std::string& id, TreeServer* Above, TreeSocket* Sock, bool Hide) + : Server(Name, Desc) + , Parent(Above), Socket(Sock), sid(id), behind_bursting(Parent->behind_bursting), isdead(false) + , pingtimer(this) + , ServerUser(new FakeUser(id, this)) + , age(ServerInstance->Time()), UserCount(0), OperCount(0), rtt(0), StartBurst(0), Hidden(Hide) { - age = ServerInstance->Time(); - bursting = true; - VersionString.clear(); - ServerUserCount = ServerOperCount = 0; - SetNextPingTime(ServerInstance->Time() + Utils->PingFreq); - SetPingFlag(); - Warned = false; - rtt = 0; - - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - this->StartBurst = ts; - ServerInstance->Logs->Log("m_spanningtree",DEBUG, "Started bursting at time %lu", ts); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "New server %s behind_bursting %u", GetName().c_str(), behind_bursting); + CheckULine(); + + ServerInstance->Timers.AddTimer(&pingtimer); /* find the 'route' for this server (e.g. the one directly connected * to the local server, which we can use to reach it) @@ -124,246 +115,180 @@ TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::strin */ this->AddHashEntry(); + Parent->Children.push_back(this); - SetID(id); + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), ServerEventListener, OnServerLink, (this)); } -const std::string& TreeServer::GetID() +void TreeServer::BeginBurst(uint64_t startms) { - return sid; + behind_bursting++; + + uint64_t now = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + // If the start time is in the future (clocks are not synced) then use current time + if ((!startms) || (startms > now)) + startms = now; + this->StartBurst = startms; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Server %s started bursting at time %s behind_bursting %u", sid.c_str(), ConvToStr(startms).c_str(), behind_bursting); } void TreeServer::FinishBurstInternal() { - this->bursting = false; - SetNextPingTime(ServerInstance->Time() + Utils->PingFreq); - SetPingFlag(); - for(unsigned int q=0; q < ChildCount(); q++) + // Check is needed because 1202 protocol servers don't send the bursting state of a server, so servers + // introduced during a netburst may later send ENDBURST which would normally decrease this counter + if (behind_bursting > 0) + behind_bursting--; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "FinishBurstInternal() %s behind_bursting %u", GetName().c_str(), behind_bursting); + + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) { - TreeServer* child = GetChild(q); + TreeServer* child = *i; child->FinishBurstInternal(); } } void TreeServer::FinishBurst() { - FinishBurstInternal(); ServerInstance->XLines->ApplyLines(); - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + uint64_t ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); unsigned long bursttime = ts - this->StartBurst; ServerInstance->SNO->WriteToSnoMask(Parent == Utils->TreeRoot ? 'l' : 'L', "Received end of netburst from \2%s\2 (burst time: %lu %s)", - ServerName.c_str(), (bursttime > 10000 ? bursttime / 1000 : bursttime), (bursttime > 10000 ? "secs" : "msecs")); - AddServerEvent(Utils->Creator, ServerName.c_str()); -} + GetName().c_str(), (bursttime > 10000 ? bursttime / 1000 : bursttime), (bursttime > 10000 ? "secs" : "msecs")); -void TreeServer::SetID(const std::string &id) -{ - ServerInstance->Logs->Log("m_spanningtree",DEBUG, "Setting SID to " + id); - sid = id; - Utils->sidlist[sid] = this; + StartBurst = 0; + FinishBurstInternal(); } -int TreeServer::QuitUsers(const std::string &reason) +void TreeServer::SQuitChild(TreeServer* server, const std::string& reason) { - const char* reason_s = reason.c_str(); - std::vector<User*> time_to_die; - for (user_hash::iterator n = ServerInstance->Users->clientlist->begin(); n != ServerInstance->Users->clientlist->end(); n++) + stdalgo::erase(Children, server); + + if (IsRoot()) { - if (n->second->server == ServerName) - { - time_to_die.push_back(n->second); - } + // Server split from us, generate a SQUIT message and broadcast it + ServerInstance->SNO->WriteGlobalSno('l', "Server \002" + server->GetName() + "\002 split: " + reason); + CmdBuilder("SQUIT").push(server->GetID()).push_last(reason).Broadcast(); } - for (std::vector<User*>::iterator n = time_to_die.begin(); n != time_to_die.end(); n++) + else { - User* a = (User*)*n; - if (!IS_LOCAL(a)) - { - if (this->Utils->quiet_bursts) - a->quietquit = true; - - if (ServerInstance->Config->HideSplits) - ServerInstance->Users->QuitUser(a, "*.net *.split", reason_s); - else - ServerInstance->Users->QuitUser(a, reason_s); - } + ServerInstance->SNO->WriteToSnoMask('L', "Server \002" + server->GetName() + "\002 split from server \002" + GetName() + "\002 with reason: " + reason); } - return time_to_die.size(); -} - -/** This method is used to add the structure to the - * hash_map for linear searches. It is only called - * by the constructors. - */ -void TreeServer::AddHashEntry() -{ - server_hash::iterator iter = Utils->serverlist.find(this->ServerName.c_str()); - if (iter == Utils->serverlist.end()) - Utils->serverlist[this->ServerName.c_str()] = this; -} - -/** This method removes the reference to this object - * from the hash_map which is used for linear searches. - * It is only called by the default destructor. - */ -void TreeServer::DelHashEntry() -{ - server_hash::iterator iter = Utils->serverlist.find(this->ServerName.c_str()); - if (iter != Utils->serverlist.end()) - Utils->serverlist.erase(iter); -} - -/** These accessors etc should be pretty self- - * explanitory. - */ -TreeServer* TreeServer::GetRoute() -{ - return Route; -} - -std::string TreeServer::GetName() -{ - return ServerName.c_str(); -} - -const std::string& TreeServer::GetDesc() -{ - return ServerDesc; -} - -const std::string& TreeServer::GetVersion() -{ - return VersionString; -} - -void TreeServer::SetNextPingTime(time_t t) -{ - this->NextPing = t; - LastPingWasGood = false; -} -time_t TreeServer::NextPingTime() -{ - return NextPing; -} + unsigned int num_lost_servers = 0; + server->SQuitInternal(num_lost_servers); -bool TreeServer::AnsweredLastPing() -{ - return LastPingWasGood; -} + const std::string quitreason = GetName() + " " + server->GetName(); + unsigned int num_lost_users = QuitUsers(quitreason); -void TreeServer::SetPingFlag() -{ - LastPingWasGood = true; -} + ServerInstance->SNO->WriteToSnoMask(IsRoot() ? 'l' : 'L', "Netsplit complete, lost \002%u\002 user%s on \002%u\002 server%s.", + num_lost_users, num_lost_users != 1 ? "s" : "", num_lost_servers, num_lost_servers != 1 ? "s" : ""); -unsigned int TreeServer::GetUserCount() -{ - return ServerUserCount; -} + // No-op if the socket is already closed (i.e. it called us) + if (server->IsLocal()) + server->GetSocket()->Close(); -void TreeServer::SetUserCount(int diff) -{ - ServerUserCount += diff; + // Add the server to the cull list, the servers behind it are handled by cull() and the destructor + ServerInstance->GlobalCulls.AddItem(server); } -void TreeServer::SetOperCount(int diff) +void TreeServer::SQuitInternal(unsigned int& num_lost_servers) { - ServerOperCount += diff; -} + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Server %s lost in split", GetName().c_str()); -unsigned int TreeServer::GetOperCount() -{ - return ServerOperCount; -} + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) + { + TreeServer* server = *i; + server->SQuitInternal(num_lost_servers); + } -TreeSocket* TreeServer::GetSocket() -{ - return Socket; -} + // Mark server as dead + isdead = true; + num_lost_servers++; + RemoveHash(); -TreeServer* TreeServer::GetParent() -{ - return Parent; + if (!Utils->Creator->dying) + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), ServerEventListener, OnServerSplit, (this)); } -void TreeServer::SetVersion(const std::string &Version) +unsigned int TreeServer::QuitUsers(const std::string& reason) { - VersionString = Version; -} + std::string publicreason = ServerInstance->Config->HideSplits ? "*.net *.split" : reason; -unsigned int TreeServer::ChildCount() -{ - return Children.size(); -} - -TreeServer* TreeServer::GetChild(unsigned int n) -{ - if (n < Children.size()) - { - /* Make sure they cant request - * an out-of-range object. After - * all we know what these programmer - * types are like *grin*. - */ - return Children[n]; - } - else + const user_hash& users = ServerInstance->Users->GetUsers(); + unsigned int original_size = users.size(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ) { - return NULL; + User* user = i->second; + // Increment the iterator now because QuitUser() removes the user from the container + ++i; + TreeServer* server = TreeServer::Get(user); + if (server->IsDead()) + ServerInstance->Users->QuitUser(user, publicreason, &reason); } + return original_size - users.size(); } -void TreeServer::AddChild(TreeServer* Child) +void TreeServer::CheckULine() { - Children.push_back(Child); -} + uline = silentuline = false; -bool TreeServer::DelChild(TreeServer* Child) -{ - std::vector<TreeServer*>::iterator it = std::find(Children.begin(), Children.end(), Child); - if (it != Children.end()) + ConfigTagList tags = ServerInstance->Config->ConfTags("uline"); + for (ConfigIter i = tags.first; i != tags.second; ++i) { - Children.erase(it); - return true; + ConfigTag* tag = i->second; + std::string server = tag->getString("server"); + if (!strcasecmp(server.c_str(), GetName().c_str())) + { + if (this->IsRoot()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Servers should not uline themselves (at " + tag->getTagLocation() + ")"); + return; + } + + uline = true; + silentuline = tag->getBool("silent"); + break; + } } - return false; } -/** Removes child nodes of this node, and of that node, etc etc. - * This is used during netsplits to automatically tidy up the - * server tree. It is slow, we don't use it for much else. +/** This method is used to add the server to the + * maps for linear searches. It is only called + * by the constructors. */ -bool TreeServer::Tidy() +void TreeServer::AddHashEntry() { - while (1) - { - std::vector<TreeServer*>::iterator a = Children.begin(); - if (a == Children.end()) - return true; - TreeServer* s = *a; - s->Tidy(); - s->cull(); - Children.erase(a); - delete s; - } + Utils->serverlist[GetName()] = this; + Utils->sidlist[sid] = this; } CullResult TreeServer::cull() { - if (ServerUser != ServerInstance->FakeClient) + // Recursively cull all servers that are under us in the tree + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) + { + TreeServer* server = *i; + server->cull(); + } + + if (!IsRoot()) ServerUser->cull(); return classbase::cull(); } TreeServer::~TreeServer() { - /* We'd better tidy up after ourselves, eh? */ - this->DelHashEntry(); - if (ServerUser != ServerInstance->FakeClient) + // Recursively delete all servers that are under us in the tree first + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) + delete *i; + + // Delete server user unless it's us + if (!IsRoot()) delete ServerUser; +} - server_hash::iterator iter = Utils->sidlist.find(GetID()); - if (iter != Utils->sidlist.end()) - Utils->sidlist.erase(iter); +void TreeServer::RemoveHash() +{ + Utils->sidlist.erase(sid); + Utils->serverlist.erase(GetName()); } diff --git a/src/modules/m_spanningtree/treeserver.h b/src/modules/m_spanningtree/treeserver.h index 60b6d1def..037edd194 100644 --- a/src/modules/m_spanningtree/treeserver.h +++ b/src/modules/m_spanningtree/treeserver.h @@ -19,10 +19,10 @@ */ -#ifndef M_SPANNINGTREE_TREESERVER_H -#define M_SPANNINGTREE_TREESERVER_H +#pragma once #include "treesocket.h" +#include "pingtimer.h" /** Each server in the tree is represented by one class of * type TreeServer. A locally connected TreeServer can @@ -38,90 +38,116 @@ * TreeServer items, deleting and inserting them as they * are created and destroyed. */ -class TreeServer : public classbase +class TreeServer : public Server { TreeServer* Parent; /* Parent entry */ TreeServer* Route; /* Route entry */ std::vector<TreeServer*> Children; /* List of child objects */ - irc::string ServerName; /* Server's name */ - std::string ServerDesc; /* Server's description */ std::string VersionString; /* Version string or empty string */ - unsigned int ServerUserCount; /* How many users are on this server? [note: doesn't care about +i] */ - unsigned int ServerOperCount; /* How many opers are on this server? */ - TreeSocket* Socket; /* For directly connected servers this points at the socket object */ - time_t NextPing; /* After this time, the server should be PINGed*/ - bool LastPingWasGood; /* True if the server responded to the last PING with a PONG */ - SpanningTreeUtilities* Utils; /* Utility class */ + + /** Full version string including patch version and other info + */ + std::string fullversion; + std::string rawversion; + + TreeSocket* Socket; /* Socket used to communicate with this server */ std::string sid; /* Server ID */ - /** Set server ID - * @param id Server ID - * @throws CoreException on duplicate ID + /** Counter counting how many servers are bursting in front of this server, including + * this server. Set to parents' value on construction then it is increased if the + * server itself starts bursting. Decreased when a server on the path to this server + * finishes burst. + */ + unsigned int behind_bursting; + + /** True if this server has been lost in a split and is awaiting destruction + */ + bool isdead; + + /** Timer handling PINGing the server and killing it on timeout + */ + PingTimer pingtimer; + + /** This method is used to add this TreeServer to the + * hash maps. It is only called by the constructors. + */ + void AddHashEntry(); + + /** Used by SQuit logic to recursively remove servers + */ + void SQuitInternal(unsigned int& num_lost_servers); + + /** Remove the reference to this server from the hash maps */ - void SetID(const std::string &id); + void RemoveHash(); public: + typedef std::vector<TreeServer*> ChildServers; FakeUser* const ServerUser; /* User representing this server */ - time_t age; + const time_t age; - bool Warned; /* True if we've warned opers about high latency on this server */ - bool bursting; /* whether or not this server is bursting */ + unsigned int UserCount; /* How many users are on this server? [note: doesn't care about +i] */ + unsigned int OperCount; /* How many opers are on this server? */ /** We use this constructor only to create the 'root' item, Utils->TreeRoot, which * represents our own server. Therefore, it has no route, no parent, and * no socket associated with it. Its version string is our own local version. */ - TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id); + TreeServer(); /** When we create a new server, we call this constructor to initialize it. * This constructor initializes the server's Route and Parent, and sets up * its ping counters so that it will be pinged one minute from now. */ - TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id, TreeServer* Above, TreeSocket* Sock, bool Hide); + TreeServer(const std::string& Name, const std::string& Desc, const std::string& id, TreeServer* Above, TreeSocket* Sock, bool Hide); - int QuitUsers(const std::string &reason); - - /** This method is used to add the structure to the - * hash_map for linear searches. It is only called - * by the constructors. + /** SQuit a server connected to this server, removing the given server and all servers behind it + * @param server Server to squit, must be directly below this server + * @param reason Reason for quitting the server, sent to opers and other servers */ - void AddHashEntry(); + void SQuitChild(TreeServer* server, const std::string& reason); - /** This method removes the reference to this object - * from the hash_map which is used for linear searches. - * It is only called by the default destructor. + /** SQuit this server, removing this server and all servers behind it + * @param reason Reason for quitting the server, sent to opers and other servers */ - void DelHashEntry(); + void SQuit(const std::string& reason) + { + GetParent()->SQuitChild(this, reason); + } + + static unsigned int QuitUsers(const std::string& reason); /** Get route. * The 'route' is defined as the locally- * connected server which can be used to reach this server. */ - TreeServer* GetRoute(); + TreeServer* GetRoute() const { return Route; } - /** Get server name + /** Returns true if this server is the tree root (i.e.: us) */ - std::string GetName(); + bool IsRoot() const { return (this->Parent == NULL); } - /** Get server description (GECOS) + /** Returns true if this server is locally connected */ - const std::string& GetDesc(); + bool IsLocal() const { return (this->Route == this); } - /** Get server version string + /** Returns true if the server is awaiting destruction + * @return True if the server is waiting to be culled and deleted, false otherwise */ - const std::string& GetVersion(); + bool IsDead() const { return isdead; } - /** Set time we are next due to ping this server + /** Get server version string */ - void SetNextPingTime(time_t t); + const std::string& GetVersion() const { return VersionString; } - /** Get the time we are next due to ping this server + /** Get the full version string of this server + * @return The full version string of this server, including patch version and other info */ - time_t NextPingTime(); + const std::string& GetFullVersion() const { return fullversion; } - /** Last ping time in milliseconds, used to calculate round trip time + /** Get the raw version string of this server */ - unsigned long LastPingMsec; + const std::string& GetRawVersion() const { return rawversion; } /** Round trip time of last ping */ @@ -129,86 +155,91 @@ class TreeServer : public classbase /** When we recieved BURST from this server, used to calculate total burst time at ENDBURST. */ - unsigned long StartBurst; + uint64_t StartBurst; /** True if this server is hidden */ bool Hidden; - /** True if the server answered their last ping + /** Get the TreeSocket pointer for local servers. + * For remote servers, this returns NULL. */ - bool AnsweredLastPing(); + TreeSocket* GetSocket() const { return Socket; } - /** Set the server as responding to its last ping + /** Get the parent server. + * For the root node, this returns NULL. */ - void SetPingFlag(); + TreeServer* GetParent() const { return Parent; } - /** Get the number of users on this server. + /** Set the server version string */ - unsigned int GetUserCount(); + void SetVersion(const std::string& verstr) { VersionString = verstr; } - /** Increment or decrement the user count by diff. + /** Set the full version string + * @param verstr The version string to set */ - void SetUserCount(int diff); + void SetFullVersion(const std::string& verstr) { fullversion = verstr; } - /** Gets the numbers of opers on this server. + /** Set the raw version string */ - unsigned int GetOperCount(); + void SetRawVersion(const std::string& verstr) { rawversion = verstr; } - /** Increment or decrement the oper count by diff. + /** Sets the description of this server. Called when the description of a remote server changes + * and we are notified about it. + * @param descstr The description to set */ - void SetOperCount(int diff); + void SetDesc(const std::string& descstr) { description = descstr; } - /** Get the TreeSocket pointer for local servers. - * For remote servers, this returns NULL. + /** Return all child servers */ - TreeSocket* GetSocket(); + const ChildServers& GetChildren() const { return Children; } - /** Get the parent server. - * For the root node, this returns NULL. + /** Get server ID */ - TreeServer* GetParent(); + const std::string& GetID() const { return sid; } - /** Set the server version string + /** Marks a server as having finished bursting and performs appropriate actions. */ - void SetVersion(const std::string &Version); + void FinishBurst(); + /** Recursive call for child servers */ + void FinishBurstInternal(); - /** Return number of child servers + /** (Re)check the uline state of this server */ - unsigned int ChildCount(); + void CheckULine(); - /** Return a child server indexed 0..n + /** Get the bursting state of this server + * @return True if this server is bursting, false if it isn't */ - TreeServer* GetChild(unsigned int n); + bool IsBursting() const { return (StartBurst != 0); } - /** Add a child server + /** Check whether this server is behind a bursting server or is itself bursting. + * This can tell whether a user is on a part of the network that is still bursting. + * @return True if this server is bursting or is behind a server that is bursting, false if it isn't */ - void AddChild(TreeServer* Child); + bool IsBehindBursting() const { return (behind_bursting != 0); } - /** Delete a child server, return false if it didn't exist. + /** Set the bursting state of the server + * @param startms Time the server started bursting, if 0 or omitted, use current time */ - bool DelChild(TreeServer* Child); + void BeginBurst(uint64_t startms = 0); - /** Removes child nodes of this node, and of that node, etc etc. - * This is used during netsplits to automatically tidy up the - * server tree. It is slow, we don't use it for much else. + /** Register a PONG from the server */ - bool Tidy(); + void OnPong() { pingtimer.OnPong(); } - /** Get server ID - */ - const std::string& GetID(); + CullResult cull() CXX11_OVERRIDE; - /** Marks a server as having finished bursting and performs appropriate actions. + /** Destructor, deletes ServerUser unless IsRoot() */ - void FinishBurst(); - /** Recursive call for child servers */ - void FinishBurstInternal(); + ~TreeServer(); - CullResult cull(); - /** Destructor + /** Returns the TreeServer the given user is connected to + * @param user The user whose server to return + * @return The TreeServer this user is connected to. */ - ~TreeServer(); + static TreeServer* Get(User* user) + { + return static_cast<TreeServer*>(user->server); + } }; - -#endif diff --git a/src/modules/m_spanningtree/treesocket.h b/src/modules/m_spanningtree/treesocket.h index efcce5f7a..d99dc9104 100644 --- a/src/modules/m_spanningtree/treesocket.h +++ b/src/modules/m_spanningtree/treesocket.h @@ -20,12 +20,9 @@ */ -#ifndef M_SPANNINGTREE_TREESOCKET_H -#define M_SPANNINGTREE_TREESOCKET_H +#pragma once -#include "socket.h" #include "inspircd.h" -#include "xline.h" #include "utils.h" @@ -76,7 +73,7 @@ struct CapabData std::string ourchallenge; /* Challenge sent for challenge/response */ std::string theirchallenge; /* Challenge recv for challenge/response */ int capab_phase; /* Have sent CAPAB already */ - bool auth_fingerprint; /* Did we auth using SSL fingerprint */ + bool auth_fingerprint; /* Did we auth using SSL certificate fingerprint */ bool auth_challenge; /* Did we auth using challenge/response */ // Data saved from incoming SERVER command, for later use when our credentials have been accepted by the other party @@ -92,39 +89,92 @@ struct CapabData */ class TreeSocket : public BufferedSocket { - SpanningTreeUtilities* Utils; /* Utility class */ + struct BurstState; + std::string linkID; /* Description for this link */ ServerState LinkState; /* Link state */ CapabData* capab; /* Link setup data (held until burst is sent) */ TreeServer* MyRoot; /* The server we are talking to */ int proto_version; /* Remote protocol version */ - bool ConnectionFailureShown; /* Set to true if a connection failure message was shown */ - static const unsigned int FMODE_MAX_LENGTH = 350; + /** True if we've sent our burst. + * This only changes the behavior of message translation for 1202 protocol servers and it can be + * removed once 1202 support is dropped. + */ + bool burstsent; /** Checks if the given servername and sid are both free */ bool CheckDuplicate(const std::string& servername, const std::string& sid); + /** Send all ListModeBase modes set on the channel + */ + void SendListModes(Channel* chan); + + /** Send all known information about a channel */ + void SyncChannel(Channel* chan, BurstState& bs); + + /** Send all users and their oper state, away state and metadata */ + void SendUsers(BurstState& bs); + + /** Send all additional info about the given server to this server */ + void SendServerInfo(TreeServer* from); + + /** Find the User source of a command given a prefix and a command string. + * This connection must be fully up when calling this function. + * @param prefix Prefix string to find the source User object for. Can be a sid, a uuid or a server name. + * @param command The command whose source to find. This is required because certain commands (like mode + * changes and kills) must be processed even if their claimed source doesn't exist. If the given command is + * such a command and the source does not exist, the function returns a valid FakeUser that can be used to + * to process the command with. + * @return The command source to use when processing the command or NULL if the source wasn't found. + * Note that the direction of the returned source is not verified. + */ + User* FindSource(const std::string& prefix, const std::string& command); + + /** Finish the authentication phase of this connection. + * Change the state of the connection to CONNECTED, create a TreeServer object for the server on the + * other end of the connection using the details provided in the parameters, and finally send a burst. + * @param remotename Name of the remote server + * @param remotesid SID of the remote server + * @param remotedesc Description of the remote server + * @param hidden True if the remote server is hidden according to the configuration + */ + void FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden); + + /** Authenticate the remote server. + * Validate the parameters and find the link block that matches the remote server. In case of an error, + * an appropriate snotice is generated, an ERROR message is sent and the connection is closed. + * Failing to find a matching link block counts as an error. + * @param params Parameters they sent in the SERVER command + * @return Link block for the remote server, or NULL if an error occurred + */ + Link* AuthRemote(const CommandBase::Params& params); + + /** Write a line on this socket with a new line character appended, skipping all translation for old protocols + * @param line Line to write without a new line character at the end + */ + void WriteLineNoCompat(const std::string& line); + public: - time_t age; + const time_t age; /** Because most of the I/O gubbins are encapsulated within * BufferedSocket, we just call the superclass constructor for * most of the action, and append a few of our own values * to it. */ - TreeSocket(SpanningTreeUtilities* Util, Link* link, Autoconnect* myac, const std::string& ipaddr); + TreeSocket(Link* link, Autoconnect* myac, const std::string& ipaddr); /** When a listening socket gives us a new file descriptor, * we must associate it with a socket without creating a new * connection. This constructor is used for this purpose. */ - TreeSocket(SpanningTreeUtilities* Util, int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); + TreeSocket(int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); /** Get link state */ - ServerState GetLinkState(); + ServerState GetLinkState() const { return LinkState; } /** Get challenge set in our CAPAB for challenge/response */ @@ -150,7 +200,7 @@ class TreeSocket : public BufferedSocket */ void CleanNegotiationInfo(); - CullResult cull(); + CullResult cull() CXX11_OVERRIDE; /** Destructor */ ~TreeSocket(); @@ -166,11 +216,11 @@ class TreeSocket : public BufferedSocket * to server docs on the inspircd.org site, the other side * will then send back its own server string. */ - virtual void OnConnected(); + void OnConnected() CXX11_OVERRIDE; /** Handle socket error event */ - virtual void OnError(BufferedSocketError e); + void OnError(BufferedSocketError e) CXX11_OVERRIDE; /** Sends an error to the remote server, and displays it locally to show * that it was sent. @@ -180,13 +230,8 @@ class TreeSocket : public BufferedSocket /** Recursively send the server tree with distances as hops. * This is used during network burst to inform the other server * (and any of ITS servers too) of what servers we know about. - * If at any point any of these servers already exist on the other - * end, our connection may be terminated. The hopcounts given - * by this function are relative, this doesn't matter so long as - * they are all >1, as all the remote servers re-calculate them - * to be relative too, with themselves as hop 0. */ - void SendServers(TreeServer* Current, TreeServer* s, int hops); + void SendServers(TreeServer* Current, TreeServer* s); /** Returns module list as a string, filtered by filter * @param filter a module version bitmask, such as VF_COMMON or VF_OPTCOMMON @@ -197,31 +242,11 @@ class TreeSocket : public BufferedSocket */ void SendCapabilities(int phase); - /** Add modules to VF_COMMON list for backwards compatability */ - void CompatAddModules(std::vector<std::string>& modlist); - /* Isolate and return the elements that are different between two lists */ void ListDifference(const std::string &one, const std::string &two, char sep, std::string& mleft, std::string& mright); - bool Capab(const parameterlist ¶ms); - - /** This function forces this server to quit, removing this server - * and any users on it (and servers and users below that, etc etc). - * It's very slow and pretty clunky, but luckily unless your network - * is having a REAL bad hair day, this function shouldnt be called - * too many times a month ;-) - */ - void SquitServer(std::string &from, TreeServer* Current, int& num_lost_servers, int& num_lost_users); - - /** This is a wrapper function for SquitServer above, which - * does some validation first and passes on the SQUIT to all - * other remaining servers. - */ - void Squit(TreeServer* Current, const std::string &reason); - - /* Used on nick collision ... XXX ugly function HACK */ - int DoCollision(User *u, time_t remotets, const std::string &remoteident, const std::string &remoteip, const std::string &remoteuid); + bool Capab(const CommandBase::Params& params); /** Send one or more FJOINs for a channel of users. * If the length of a single line is more than 480-NICKMAX @@ -232,11 +257,8 @@ class TreeSocket : public BufferedSocket /** Send G, Q, Z and E lines */ void SendXLines(); - /** Send channel modes and topics */ - void SendChannelModes(); - - /** send all users and their oper state/modes */ - void SendUsers(); + /** Send all known information about a channel */ + void SyncChannel(Channel* chan); /** This function is called when we want to send a netburst to a local * server. There is a set order we must do this, because for example @@ -248,90 +270,41 @@ class TreeSocket : public BufferedSocket /** This function is called when we receive data from a remote * server. */ - void OnDataReady(); + void OnDataReady() CXX11_OVERRIDE; /** Send one or more complete lines down the socket */ - void WriteLine(std::string line); + void WriteLine(const std::string& line); /** Handle ERROR command */ - void Error(parameterlist ¶ms); - - /** Remote AWAY */ - bool Away(const std::string &prefix, parameterlist ¶ms); - - /** SAVE to resolve nick collisions without killing */ - bool ForceNick(const std::string &prefix, parameterlist ¶ms); - - /** ENCAP command - */ - void Encap(User* who, parameterlist ¶ms); - - /** OPERQUIT command - */ - bool OperQuit(const std::string &prefix, parameterlist ¶ms); - - /** PONG - */ - bool LocalPong(const std::string &prefix, parameterlist ¶ms); - - /** VERSION - */ - bool ServerVersion(const std::string &prefix, parameterlist ¶ms); - - /** ADDLINE - */ - bool AddLine(const std::string &prefix, parameterlist ¶ms); - - /** DELLINE - */ - bool DelLine(const std::string &prefix, parameterlist ¶ms); - - /** WHOIS - */ - bool Whois(const std::string &prefix, parameterlist ¶ms); - - /** PUSH - */ - bool Push(const std::string &prefix, parameterlist ¶ms); - - /** PING - */ - bool LocalPing(const std::string &prefix, parameterlist ¶ms); - - /** <- (remote) <- SERVER - */ - bool RemoteServer(const std::string &prefix, parameterlist ¶ms); + void Error(CommandBase::Params& params); /** (local) -> SERVER */ - bool Outbound_Reply_Server(parameterlist ¶ms); + bool Outbound_Reply_Server(CommandBase::Params& params); /** (local) <- SERVER */ - bool Inbound_Server(parameterlist ¶ms); + bool Inbound_Server(CommandBase::Params& params); /** Handle IRC line split */ - void Split(const std::string &line, std::string& prefix, std::string& command, parameterlist ¶ms); + void Split(const std::string &line, std::string& prefix, std::string& command, CommandBase::Params& params); /** Process complete line from buffer */ void ProcessLine(std::string &line); - void ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params); + void ProcessConnectedLine(std::string& prefix, std::string& command, CommandBase::Params& params); /** Handle socket timeout from connect() */ - virtual void OnTimeout(); + void OnTimeout() CXX11_OVERRIDE; /** Handle server quit on close */ - virtual void Close(); + void Close() CXX11_OVERRIDE; - /** Returns true if this server was introduced to the rest of the network + /** Fixes messages coming from old servers so the new command handlers understand them */ - bool Introduced(); + bool PreProcessOldProtocolMessage(User*& who, std::string& cmd, CommandBase::Params& params); }; - -#endif - diff --git a/src/modules/m_spanningtree/treesocket1.cpp b/src/modules/m_spanningtree/treesocket1.cpp index c9729cc0f..062d04222 100644 --- a/src/modules/m_spanningtree/treesocket1.cpp +++ b/src/modules/m_spanningtree/treesocket1.cpp @@ -21,78 +21,63 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" +#include "iohook.h" #include "main.h" -#include "../spanningtree.h" +#include "modules/server.h" #include "utils.h" #include "treeserver.h" #include "link.h" #include "treesocket.h" -#include "resolvers.h" +#include "commands.h" -/** Because most of the I/O gubbins are encapsulated within - * BufferedSocket, we just call the superclass constructor for - * most of the action, and append a few of our own values - * to it. +/** Constructor for outgoing connections. + * Because most of the I/O gubbins are encapsulated within + * BufferedSocket, we just call DoConnect() for most of the action, + * and only do minor initialization tasks ourselves. */ -TreeSocket::TreeSocket(SpanningTreeUtilities* Util, Link* link, Autoconnect* myac, const std::string& ipaddr) - : Utils(Util) +TreeSocket::TreeSocket(Link* link, Autoconnect* myac, const std::string& ipaddr) + : linkID(link->Name), LinkState(CONNECTING), MyRoot(NULL), proto_version(0) + , burstsent(false), age(ServerInstance->Time()) { - age = ServerInstance->Time(); - linkID = assign(link->Name); capab = new CapabData; capab->link = link; capab->ac = myac; capab->capab_phase = 0; - MyRoot = NULL; - proto_version = 0; - ConnectionFailureShown = false; - LinkState = CONNECTING; - if (!link->Hook.empty()) - { - ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_IOHOOK, link->Hook); - if (!prov) - { - SetError("Could not find hook '" + link->Hook + "' for connection to " + linkID); - return; - } - AddIOHook(prov->creator); - } + DoConnect(ipaddr, link->Port, link->Timeout, link->Bind); - Utils->timeoutlist[this] = std::pair<std::string, int>(linkID, link->Timeout); + Utils->timeoutlist[this] = std::pair<std::string, unsigned int>(linkID, link->Timeout); SendCapabilities(1); } -/** When a listening socket gives us a new file descriptor, - * we must associate it with a socket without creating a new - * connection. This constructor is used for this purpose. +/** Constructor for incoming connections */ -TreeSocket::TreeSocket(SpanningTreeUtilities* Util, int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) - : BufferedSocket(newfd), Utils(Util) +TreeSocket::TreeSocket(int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) + : BufferedSocket(newfd) + , linkID("inbound from " + client->addr()), LinkState(WAIT_AUTH_1), MyRoot(NULL), proto_version(0) + , burstsent(false), age(ServerInstance->Time()) { capab = new CapabData; capab->capab_phase = 0; - MyRoot = NULL; - age = ServerInstance->Time(); - LinkState = WAIT_AUTH_1; - proto_version = 0; - ConnectionFailureShown = false; - linkID = "inbound from " + client->addr(); - FOREACH_MOD(I_OnHookIO, OnHookIO(this, via)); - if (GetIOHook()) - GetIOHook()->OnStreamSocketAccept(this, client, server); - SendCapabilities(1); + for (ListenSocket::IOHookProvList::iterator i = via->iohookprovs.begin(); i != via->iohookprovs.end(); ++i) + { + ListenSocket::IOHookProvRef& iohookprovref = *i; + if (!iohookprovref) + continue; - Utils->timeoutlist[this] = std::pair<std::string, int>(linkID, 30); -} + iohookprovref->OnAccept(this, client, server); + // IOHook could have encountered a fatal error, e.g. if the TLS ClientHello was already in the queue and there was no common TLS version + if (!getError().empty()) + { + TreeSocket::OnError(I_ERR_OTHER); + return; + } + } -ServerState TreeSocket::GetLinkState() -{ - return this->LinkState; + SendCapabilities(1); + + Utils->timeoutlist[this] = std::pair<std::string, unsigned int>(linkID, 30); } void TreeSocket::CleanNegotiationInfo() @@ -114,20 +99,30 @@ CullResult TreeSocket::cull() TreeSocket::~TreeSocket() { - if (capab) - delete capab; + delete capab; } /** When an outbound connection finishes connecting, we receive - * this event, and must send our SERVER string to the other + * this event, and must do CAPAB negotiation with the other * side. If the other side is happy, as outlined in the server * to server docs on the inspircd.org site, the other side - * will then send back its own server string. + * will then send back its own SERVER string eventually. */ void TreeSocket::OnConnected() { if (this->LinkState == CONNECTING) { + if (!capab->link->Hook.empty()) + { + ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_IOHOOK, capab->link->Hook); + if (!prov) + { + SetError("Could not find hook '" + capab->link->Hook + "' for connection to " + linkID); + return; + } + static_cast<IOHookProvider*>(prov)->OnConnect(this); + } + ServerInstance->SNO->WriteGlobalSno('l', "Connection to \2%s\2[%s] started.", linkID.c_str(), (capab->link->HiddenFromStats ? "<hidden>" : capab->link->IPAddr.c_str())); this->SendCapabilities(1); @@ -139,6 +134,7 @@ void TreeSocket::OnError(BufferedSocketError e) ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\002%s\002' failed with error: %s", linkID.c_str(), getError().c_str()); LinkState = DYING; + Close(); } void TreeSocket::SendError(const std::string &errormessage) @@ -149,79 +145,31 @@ void TreeSocket::SendError(const std::string &errormessage) SetError(errormessage); } -/** This function forces this server to quit, removing this server - * and any users on it (and servers and users below that, etc etc). - * It's very slow and pretty clunky, but luckily unless your network - * is having a REAL bad hair day, this function shouldnt be called - * too many times a month ;-) - */ -void TreeSocket::SquitServer(std::string &from, TreeServer* Current, int& num_lost_servers, int& num_lost_users) +CmdResult CommandSQuit::HandleServer(TreeServer* server, CommandBase::Params& params) { - std::string servername = Current->GetName(); - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"SquitServer for %s from %s", - servername.c_str(), from.c_str()); - /* recursively squit the servers attached to 'Current'. - * We're going backwards so we don't remove users - * while we still need them ;) - */ - for (unsigned int q = 0; q < Current->ChildCount(); q++) + TreeServer* quitting = Utils->FindServer(params[0]); + if (!quitting) { - TreeServer* recursive_server = Current->GetChild(q); - this->SquitServer(from,recursive_server, num_lost_servers, num_lost_users); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Squit from unknown server"); + return CMD_FAILURE; } - /* Now we've whacked the kids, whack self */ - num_lost_servers++; - num_lost_users += Current->QuitUsers(from); -} -/** This is a wrapper function for SquitServer above, which - * does some validation first and passes on the SQUIT to all - * other remaining servers. - */ -void TreeSocket::Squit(TreeServer* Current, const std::string &reason) -{ - bool LocalSquit = false; - - if ((Current) && (Current != Utils->TreeRoot)) + CmdResult ret = CMD_SUCCESS; + if (quitting == server) { - DelServerEvent(Utils->Creator, Current->GetName()); + ret = CMD_FAILURE; + server = server->GetParent(); + } + else if (quitting->GetParent() != server) + throw ProtocolException("Attempted to SQUIT a non-directly connected server or the parent"); - if (!Current->GetSocket() || Current->GetSocket()->Introduced()) - { - parameterlist params; - params.push_back(Current->GetID()); - params.push_back(":"+reason); - Utils->DoOneToAllButSender(Current->GetParent()->GetID(),"SQUIT",params,Current->GetID()); - } + server->SQuitChild(quitting, params[1]); - if (Current->GetParent() == Utils->TreeRoot) - { - ServerInstance->SNO->WriteGlobalSno('l', "Server \002"+Current->GetName()+"\002 split: "+reason); - LocalSquit = true; - } - else - { - ServerInstance->SNO->WriteToSnoMask('L', "Server \002"+Current->GetName()+"\002 split from server \002"+Current->GetParent()->GetName()+"\002 with reason: "+reason); - } - int num_lost_servers = 0; - int num_lost_users = 0; - std::string from = Current->GetParent()->GetName()+" "+Current->GetName(); - SquitServer(from, Current, num_lost_servers, num_lost_users); - ServerInstance->SNO->WriteToSnoMask(LocalSquit ? 'l' : 'L', "Netsplit complete, lost \002%d\002 user%s on \002%d\002 server%s.", - num_lost_users, num_lost_users != 1 ? "s" : "", num_lost_servers, num_lost_servers != 1 ? "s" : ""); - Current->Tidy(); - Current->GetParent()->DelChild(Current); - Current->cull(); - const bool ismyroot = (Current == MyRoot); - delete Current; - if (ismyroot) - { - MyRoot = NULL; - Close(); - } - } - else - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Squit from unknown server"); + // XXX: Return CMD_FAILURE when servers SQUIT themselves (i.e. :00S SQUIT 00S :Shutting down) + // to stop this message from being forwarded. + // The squit logic generates a SQUIT message with our sid as the source and sends it to the + // remaining servers. + return ret; } /** This function is called when we receive data from a remote @@ -235,13 +183,24 @@ void TreeSocket::OnDataReady() { std::string::size_type rline = line.find('\r'); if (rline != std::string::npos) - line = line.substr(0,rline); + line.erase(rline); if (line.find('\0') != std::string::npos) { SendError("Read null character from socket"); break; } - ProcessLine(line); + + try + { + ProcessLine(line); + } + catch (CoreException& ex) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error while processing: " + line); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason()); + SendError(ex.GetReason() + " - check the log file for details"); + } + if (!getError().empty()) break; } @@ -249,8 +208,3 @@ void TreeSocket::OnDataReady() SendError("RecvQ overrun (line too long)"); Utils->Creator->loopCall = false; } - -bool TreeSocket::Introduced() -{ - return (capab == NULL); -} diff --git a/src/modules/m_spanningtree/treesocket2.cpp b/src/modules/m_spanningtree/treesocket2.cpp index acb822fbf..513fa6dbf 100644 --- a/src/modules/m_spanningtree/treesocket2.cpp +++ b/src/modules/m_spanningtree/treesocket2.cpp @@ -23,41 +23,38 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "link.h" #include "treesocket.h" #include "resolvers.h" +#include "commands.h" /* Handle ERROR command */ -void TreeSocket::Error(parameterlist ¶ms) +void TreeSocket::Error(CommandBase::Params& params) { std::string msg = params.size() ? params[0] : ""; SetError("received ERROR " + msg); } -void TreeSocket::Split(const std::string& line, std::string& prefix, std::string& command, parameterlist& params) +void TreeSocket::Split(const std::string& line, std::string& prefix, std::string& command, CommandBase::Params& params) { irc::tokenstream tokens(line); - if (!tokens.GetToken(prefix)) + if (!tokens.GetMiddle(prefix)) return; - + if (prefix[0] == ':') { - prefix = prefix.substr(1); + prefix.erase(prefix.begin()); if (prefix.empty()) { this->SendError("BUG (?) Empty prefix received: " + line); return; } - if (!tokens.GetToken(command)) + if (!tokens.GetMiddle(command)) { this->SendError("BUG (?) Empty command received: " + line); return; @@ -72,7 +69,7 @@ void TreeSocket::Split(const std::string& line, std::string& prefix, std::string this->SendError("BUG (?) Empty command received: " + line); std::string param; - while (tokens.GetToken(param)) + while (tokens.GetTrailing(param)) { params.push_back(param); } @@ -82,9 +79,9 @@ void TreeSocket::ProcessLine(std::string &line) { std::string prefix; std::string command; - parameterlist params; + CommandBase::Params params; - ServerInstance->Logs->Log("m_spanningtree", RAWIO, "S[%d] I %s", this->GetFd(), line.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] I %s", this->GetFd(), line.c_str()); Split(line, prefix, command, params); @@ -151,7 +148,7 @@ void TreeSocket::ProcessLine(std::string &line) { if (params.size()) { - time_t them = atoi(params[0].c_str()); + time_t them = ConvToInt(params[0]); time_t delta = them - ServerInstance->Time(); if ((delta < -600) || (delta > 600)) { @@ -171,25 +168,7 @@ void TreeSocket::ProcessLine(std::string &line) if (!CheckDuplicate(capab->name, capab->sid)) return; - this->LinkState = CONNECTED; - Utils->timeoutlist.erase(this); - - linkID = capab->name; - - MyRoot = new TreeServer(Utils, capab->name, capab->description, capab->sid, Utils->TreeRoot, this, capab->hidden); - Utils->TreeRoot->AddChild(MyRoot); - - MyRoot->bursting = true; - this->DoBurst(MyRoot); - - parameterlist sparams; - sparams.push_back(MyRoot->GetName()); - sparams.push_back("*"); - sparams.push_back("0"); - sparams.push_back(MyRoot->GetID()); - sparams.push_back(":" + MyRoot->GetDesc()); - Utils->DoOneToAllButSender(ServerInstance->Config->GetSID(), "SERVER", sparams, MyRoot->GetName()); - Utils->DoOneToAllButSender(MyRoot->GetID(), "BURST", params, MyRoot->GetName()); + FinishAuth(capab->name, capab->sid, capab->description, capab->hidden); } else if (command == "ERROR") { @@ -235,52 +214,63 @@ void TreeSocket::ProcessLine(std::string &line) } } -void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params) +User* TreeSocket::FindSource(const std::string& prefix, const std::string& command) { - User* who = ServerInstance->FindUUID(prefix); - std::string direction; + // Empty prefix means the source is the directly connected server that sent this command + if (prefix.empty()) + return MyRoot->ServerUser; - if (!who) + if (prefix.size() == 3) { - TreeServer* ServerSource = Utils->FindServer(prefix); - if (prefix.empty()) - ServerSource = MyRoot; + // Prefix looks like a sid + TreeServer* server = Utils->FindServerID(prefix); + if (server) + return server->ServerUser; + } + else + { + // If the prefix string is a uuid FindUUID() returns the appropriate User object + User* user = ServerInstance->FindUUID(prefix); + if (user) + return user; + } - if (ServerSource) - { - who = ServerSource->ServerUser; - } - else - { - /* It is important that we don't close the link here, unknown prefix can occur - * due to various race conditions such as the KILL message for a user somehow - * crossing the users QUIT further upstream from the server. Thanks jilles! - */ + // Some implementations wrongly send a server name as prefix occasionally, handle that too for now + TreeServer* const server = Utils->FindServer(prefix); + if (server) + return server->ServerUser; - if ((prefix.length() == UUID_LENGTH-1) && (isdigit(prefix[0])) && - ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE"))) - { - /* Special case, we cannot drop these commands as they've been committed already on a - * part of the network by the time we receive them, so in this scenario pretend the - * command came from a server to avoid desync. - */ + /* It is important that we don't close the link here, unknown prefix can occur + * due to various race conditions such as the KILL message for a user somehow + * crossing the users QUIT further upstream from the server. Thanks jilles! + */ - who = ServerInstance->FindUUID(prefix.substr(0, 3)); - if (!who) - who = this->MyRoot->ServerUser; - } - else - { - ServerInstance->Logs->Log("m_spanningtree", DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", - command.c_str(), prefix.c_str()); - return; - } - } + if ((prefix.length() == UIDGenerator::UUID_LENGTH) && (isdigit(prefix[0])) && + ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE"))) + { + /* Special case, we cannot drop these commands as they've been committed already on a + * part of the network by the time we receive them, so in this scenario pretend the + * command came from a server to avoid desync. + */ + + TreeServer* const usersserver = Utils->FindServerID(prefix.substr(0, 3)); + if (usersserver) + return usersserver->ServerUser; + return this->MyRoot->ServerUser; } - // Make sure prefix is still good - direction = who->server; - prefix = who->uuid; + // Unknown prefix + return NULL; +} + +void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, CommandBase::Params& params) +{ + User* who = FindSource(prefix, command); + if (!who) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", command.c_str(), prefix.c_str()); + return; + } /* * Check for fake direction here, and drop any instances that are found. @@ -298,214 +288,69 @@ void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, * a valid SID or a valid UUID, so that invalid UUID or SID never makes it * to the higher level functions. -- B */ - TreeServer* route_back_again = Utils->BestRouteTo(direction); - if ((!route_back_again) || (route_back_again->GetSocket() != this)) + TreeServer* const server = TreeServer::Get(who); + if (server->GetSocket() != this) { - if (route_back_again) - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Protocol violation: Fake direction '%s' from connection '%s'", - prefix.c_str(),linkID.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(), linkID.c_str()); return; } - /* - * First up, check for any malformed commands (e.g. MODE without a timestamp) - * and rewrite commands where necessary (SVSMODE -> MODE for services). -- w - */ - if (command == "SVSMODE") // This isn't in an "else if" so we still force FMODE for changes on channels. - command = "MODE"; - - // TODO move all this into Commands - if (command == "MAP") - { - Utils->Creator->HandleMap(params, who); - } - else if (command == "SERVER") - { - this->RemoteServer(prefix,params); - } - else if (command == "ERROR") - { - this->Error(params); - } - else if (command == "AWAY") - { - this->Away(prefix,params); - } - else if (command == "PING") - { - this->LocalPing(prefix,params); - } - else if (command == "PONG") - { - TreeServer *s = Utils->FindServer(prefix); - if (s && s->bursting) - { - ServerInstance->SNO->WriteGlobalSno('l',"Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", prefix.c_str()); - s->FinishBurst(); - } - this->LocalPong(prefix,params); - } - else if (command == "VERSION") - { - this->ServerVersion(prefix,params); - } - else if (command == "ADDLINE") + // Translate commands coming from servers using an older protocol + if (proto_version < ProtocolVersion) { - this->AddLine(prefix,params); - } - else if (command == "DELLINE") - { - this->DelLine(prefix,params); - } - else if (command == "SAVE") - { - this->ForceNick(prefix,params); - } - else if (command == "OPERQUIT") - { - this->OperQuit(prefix,params); - } - else if (command == "IDLE") - { - this->Whois(prefix,params); - } - else if (command == "PUSH") - { - this->Push(prefix,params); - } - else if (command == "SQUIT") - { - if (params.size() == 2) - { - this->Squit(Utils->FindServer(params[0]),params[1]); - } - } - else if (command == "SNONOTICE") - { - if (params.size() >= 2) - { - ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + who->nick + ": "+ params[1]); - params[1] = ":" + params[1]; - Utils->DoOneToAllButSender(prefix, command, params, prefix); - } - } - else if (command == "BURST") - { - // Set prefix server as bursting - TreeServer* ServerSource = Utils->FindServer(prefix); - if (!ServerSource) - { - ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got BURST from a non-server(?): %s", prefix.c_str()); + if (!PreProcessOldProtocolMessage(who, command, params)) return; - } - - ServerSource->bursting = true; - Utils->DoOneToAllButSender(prefix, command, params, prefix); } - else if (command == "ENDBURST") - { - TreeServer* ServerSource = Utils->FindServer(prefix); - if (!ServerSource) - { - ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got ENDBURST from a non-server(?): %s", prefix.c_str()); - return; - } - ServerSource->FinishBurst(); - Utils->DoOneToAllButSender(prefix, command, params, prefix); - } - else if (command == "ENCAP") + ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command); + CommandBase* cmdbase = scmd; + Command* cmd = NULL; + if (!scmd) { - this->Encap(who, params); - } - else if (command == "NICK") - { - if (params.size() != 2) - { - SendError("Protocol violation: Wrong number of parameters for NICK message"); - return; - } - - if (IS_SERVER(who)) - { - SendError("Protocol violation: Server changing nick"); - return; - } - - if ((isdigit(params[0][0])) && (params[0] != who->uuid)) - { - SendError("Protocol violation: User changing nick to an invalid UID - " + params[0]); - return; - } - - /* Update timestamp on user when they change nicks */ - who->age = atoi(params[1].c_str()); - - /* - * On nick messages, check that the nick doesnt already exist here. - * If it does, perform collision logic. - */ - bool callfnc = true; - User* x = ServerInstance->FindNickOnly(params[0]); - if ((x) && (x != who) && (x->registered == REG_ALL)) + // Not a special server-to-server command + cmd = ServerInstance->Parser.GetHandler(command); + if (!cmd) { - int collideret = 0; - /* x is local, who is remote */ - collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid); - if (collideret != 1) + if (command == "ERROR") { - // Remote client lost, or both lost, rewrite this nick change as a change to uuid before - // forwarding and don't call ForceNickChange() because DoCollision() has done it already - params[0] = who->uuid; - callfnc = false; + this->Error(params); + return; + } + else if (command == "BURST") + { + // This is sent even when there is no need for it, drop it here for now + return; } - } - if (callfnc) - who->ForceNickChange(params[0].c_str()); - Utils->RouteCommand(route_back_again, command, params, who); - } - else - { - Command* cmd = ServerInstance->Parser->GetHandler(command); - - if (!cmd) - { - irc::stringjoiner pmlist(" ", params, 0, params.size() - 1); - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised S2S command :%s %s %s", - who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str()); - SendError("Unrecognised command '" + command + "' -- possibly loaded mismatched modules"); - return; - } - if (params.size() < cmd->min_params) - { - irc::stringjoiner pmlist(" ", params, 0, params.size() - 1); - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Insufficient parameters for S2S command :%s %s %s", - who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str()); - SendError("Insufficient parameters for command '" + command + "'"); - return; + throw ProtocolException("Unknown command"); } + cmdbase = cmd; + } - if ((!params.empty()) && (params.back().empty()) && (!cmd->allow_empty_last_param)) - { - // the last param is empty and the command handler doesn't allow that, check if there will be enough params if we drop the last - if (params.size()-1 < cmd->min_params) - return; - params.pop_back(); - } + if (params.size() < cmdbase->min_params) + throw ProtocolException("Insufficient parameters"); - CmdResult res = cmd->Handle(params, who); + if ((!params.empty()) && (params.back().empty()) && (!cmdbase->allow_empty_last_param)) + { + // the last param is empty and the command handler doesn't allow that, check if there will be enough params if we drop the last + if (params.size()-1 < cmdbase->min_params) + return; + params.pop_back(); + } + CmdResult res; + if (scmd) + res = scmd->Handle(who, params); + else + { + ClientProtocol::TagMap tags; + res = cmd->Handle(who, CommandBase::Params(params, tags)); if (res == CMD_INVALID) - { - irc::stringjoiner pmlist(" ", params, 0, params.size() - 1); - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Error handling S2S command :%s %s %s", - who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str()); - SendError("Error handling '" + command + "' -- possibly loaded mismatched modules"); - } - else if (res == CMD_SUCCESS) - Utils->RouteCommand(route_back_again, command, params, who); + throw ProtocolException("Error in command handler"); } + + if (res == CMD_SUCCESS) + Utils->RouteCommand(server->GetRoute(), cmdbase, params, who); } void TreeSocket::OnTimeout() @@ -515,8 +360,10 @@ void TreeSocket::OnTimeout() void TreeSocket::Close() { - if (fd != -1) - ServerInstance->GlobalCulls.AddItem(this); + if (fd < 0) + return; + + ServerInstance->GlobalCulls.AddItem(this); this->BufferedSocket::Close(); SetError("Remote host closed connection"); @@ -524,18 +371,30 @@ void TreeSocket::Close() // If the connection is fully up (state CONNECTED) // then propogate a netsplit to all peers. if (MyRoot) - Squit(MyRoot,getError()); + MyRoot->SQuit(getError()); - if (!ConnectionFailureShown) - { - ConnectionFailureShown = true; - ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str()); + ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str()); - time_t server_uptime = ServerInstance->Time() - this->age; - if (server_uptime) - { - std::string timestr = Utils->Creator->TimeToStr(server_uptime); - ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), timestr.c_str()); - } + time_t server_uptime = ServerInstance->Time() - this->age; + if (server_uptime) + { + std::string timestr = ModuleSpanningTree::TimeToStr(server_uptime); + ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), timestr.c_str()); } } + +void TreeSocket::FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden) +{ + this->LinkState = CONNECTED; + Utils->timeoutlist.erase(this); + + linkID = remotename; + + MyRoot = new TreeServer(remotename, remotedesc, remotesid, Utils->TreeRoot, this, hidden); + + // Mark the server as bursting + MyRoot->BeginBurst(); + this->DoBurst(MyRoot); + + CommandServer::Builder(MyRoot).Forward(MyRoot); +} diff --git a/src/modules/m_spanningtree/uid.cpp b/src/modules/m_spanningtree/uid.cpp index 6620dd13a..01af56fa6 100644 --- a/src/modules/m_spanningtree/uid.cpp +++ b/src/modules/m_spanningtree/uid.cpp @@ -23,173 +23,145 @@ #include "commands.h" #include "utils.h" -#include "link.h" -#include "treesocket.h" #include "treeserver.h" -#include "resolvers.h" +#include "remoteuser.h" -CmdResult CommandUID::Handle(const parameterlist ¶ms, User* serversrc) +CmdResult CommandUID::HandleServer(TreeServer* remoteserver, CommandBase::Params& params) { - SpanningTreeUtilities* Utils = ((ModuleSpanningTree*)(Module*)creator)->Utils; - /** Do we have enough parameters: + /** * 0 1 2 3 4 5 6 7 8 9 (n-1) - * UID uuid age nick host dhost ident ip.string signon +modes (modepara) :gecos + * UID uuid age nick host dhost ident ip.string signon +modes (modepara) :real */ - time_t age_t = ConvToInt(params[1]); - time_t signon = ConvToInt(params[7]); + time_t age_t = ServerCommand::ExtractTS(params[1]); + time_t signon = ServerCommand::ExtractTS(params[7]); std::string empty; - std::string modestr(params[8]); - - TreeServer* remoteserver = Utils->FindServer(serversrc->server); - - if (!remoteserver) - return CMD_INVALID; - /* Is this a valid UID, and not misrouted? */ - if (params[0].length() != 9 || params[0].substr(0,3) != serversrc->uuid) - return CMD_INVALID; - /* Check parameters for validity before introducing the client, discovered by dmb */ - if (!age_t) - return CMD_INVALID; - if (!signon) - return CMD_INVALID; - if (modestr[0] != '+') - return CMD_INVALID; - TreeSocket* sock = remoteserver->GetRoute()->GetSocket(); + const std::string& modestr = params[8]; - /* check for collision */ - User* const collideswith = ServerInstance->FindNickOnly(params[2]); + // Check if the length of the uuid is correct and confirm the sid portion of the uuid matches the sid of the server introducing the user + if (params[0].length() != UIDGenerator::UUID_LENGTH || params[0].compare(0, 3, remoteserver->GetID())) + throw ProtocolException("Bogus UUID"); + // Sanity check on mode string: must begin with '+' + if (modestr[0] != '+') + throw ProtocolException("Invalid mode string"); + // See if there is a nick collision + User* collideswith = ServerInstance->FindNickOnly(params[2]); if ((collideswith) && (collideswith->registered != REG_ALL)) { // User that the incoming user is colliding with is not fully registered, we force nick change the // unregistered user to their uuid and tell them what happened - collideswith->WriteFrom(collideswith, "NICK %s", collideswith->uuid.c_str()); - collideswith->WriteNumeric(433, "%s %s :Nickname overruled.", collideswith->nick.c_str(), collideswith->nick.c_str()); - - // Clear the bit before calling User::ChangeNick() to make it NOT run the OnUserPostNick() hook - collideswith->registered &= ~REG_NICK; - collideswith->ChangeNick(collideswith->uuid, true); + LocalUser* const localuser = static_cast<LocalUser*>(collideswith); + localuser->OverruleNick(); } else if (collideswith) { - /* - * Nick collision. - */ - int collide = sock->DoCollision(collideswith, age_t, params[5], params[6], params[0]); - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"*** Collision on %s, collide=%d", params[2].c_str(), collide); - - if (collide != 1) + // The user on this side is registered, handle the collision + bool they_change = Utils->DoCollision(collideswith, remoteserver, age_t, params[5], params[6], params[0], "UID"); + if (they_change) { - /* remote client lost, make sure we change their nick for the hash too - * - * This alters the line that will be sent to other servers, which - * commands normally shouldn't do; hence the required const_cast. - */ - const_cast<parameterlist&>(params)[2] = params[0]; + // The client being introduced needs to change nick to uuid, change the nick in the message before + // processing/forwarding it. Also change the nick TS to CommandSave::SavedTimestamp. + age_t = CommandSave::SavedTimestamp; + params[1] = ConvToStr(CommandSave::SavedTimestamp); + params[2] = params[0]; } } - /* IMPORTANT NOTE: For remote users, we pass the UUID in the constructor. This automatically - * sets it up in the UUID hash for us. + /* For remote users, we pass the UUID they sent to the constructor. + * If the UUID already exists User::User() throws an exception which causes this connection to be closed. */ - User* _new = NULL; - try - { - _new = new RemoteUser(params[0], remoteserver->GetName()); - } - catch (...) - { - ServerInstance->Logs->Log("m_spanningtree", DEFAULT, "Duplicate UUID %s in client introduction", params[0].c_str()); - return CMD_INVALID; - } - (*(ServerInstance->Users->clientlist))[params[2]] = _new; + RemoteUser* _new = new SpanningTree::RemoteUser(params[0], remoteserver); + ServerInstance->Users->clientlist[params[2]] = _new; _new->nick = params[2]; - _new->host = params[3]; - _new->dhost = params[4]; + _new->ChangeRealHost(params[3], false); + _new->ChangeDisplayedHost(params[4]); _new->ident = params[5]; - _new->fullname = params[params.size() - 1]; + _new->ChangeRealName(params.back()); _new->registered = REG_ALL; _new->signon = signon; _new->age = age_t; - /* we need to remove the + from the modestring, so we can do our stuff */ - std::string::size_type pos_after_plus = modestr.find_first_not_of('+'); - if (pos_after_plus != std::string::npos) - modestr = modestr.substr(pos_after_plus); - unsigned int paramptr = 9; - for (std::string::iterator v = modestr.begin(); v != modestr.end(); v++) + + for (std::string::const_iterator v = modestr.begin(); v != modestr.end(); ++v) { - /* For each mode thats set, increase counter */ + // Accept more '+' chars, for now + if (*v == '+') + continue; + + /* For each mode thats set, find the mode handler and set it on the new user */ ModeHandler* mh = ServerInstance->Modes->FindMode(*v, MODETYPE_USER); + if (!mh) + throw ProtocolException("Unrecognised mode '" + std::string(1, *v) + "'"); - if (mh) + if (mh->NeedsParam(true)) { - if (mh->GetNumParams(true)) - { - if (paramptr >= params.size() - 1) - return CMD_INVALID; - std::string mp = params[paramptr++]; - /* IMPORTANT NOTE: - * All modes are assumed to succeed here as they are being set by a remote server. - * Modes CANNOT FAIL here. If they DO fail, then the failure is ignored. This is important - * to note as all but one modules currently cannot ever fail in this situation, except for - * m_servprotect which specifically works this way to prevent the mode being set ANYWHERE - * but here, at client introduction. You may safely assume this behaviour is standard and - * will not change in future versions if you want to make use of this protective behaviour - * yourself. - */ - mh->OnModeChange(_new, _new, NULL, mp, true); - } - else - mh->OnModeChange(_new, _new, NULL, empty, true); - _new->SetMode(*v, true); + if (paramptr >= params.size() - 1) + throw ProtocolException("Out of parameters while processing modes"); + std::string mp = params[paramptr++]; + /* IMPORTANT NOTE: + * All modes are assumed to succeed here as they are being set by a remote server. + * Modes CANNOT FAIL here. If they DO fail, then the failure is ignored. This is important + * to note as all but one modules currently cannot ever fail in this situation, except for + * m_servprotect which specifically works this way to prevent the mode being set ANYWHERE + * but here, at client introduction. You may safely assume this behaviour is standard and + * will not change in future versions if you want to make use of this protective behaviour + * yourself. + */ + mh->OnModeChange(_new, _new, NULL, mp, true); } + else + mh->OnModeChange(_new, _new, NULL, empty, true); + _new->SetMode(mh, true); } - /* now we've done with modes processing, put the + back for remote servers */ - if (modestr[0] != '+') - modestr = "+" + modestr; + _new->SetClientIP(params[6]); - _new->SetClientIP(params[6].c_str()); - - ServerInstance->Users->AddGlobalClone(_new); - remoteserver->SetUserCount(1); // increment by 1 + ServerInstance->Users->AddClone(_new); + remoteserver->UserCount++; bool dosend = true; - if ((Utils->quiet_bursts && remoteserver->bursting) || ServerInstance->SilentULine(_new->server)) + if ((Utils->quiet_bursts && remoteserver->IsBehindBursting()) || _new->server->IsSilentULine()) dosend = false; if (dosend) - ServerInstance->SNO->WriteToSnoMask('C',"Client connecting at %s: %s (%s) [%s]", _new->server.c_str(), _new->GetFullRealHost().c_str(), _new->GetIPString(), _new->fullname.c_str()); + ServerInstance->SNO->WriteToSnoMask('C',"Client connecting at %s: %s (%s) [%s]", remoteserver->GetName().c_str(), _new->GetFullRealHost().c_str(), _new->GetIPString().c_str(), _new->GetRealName().c_str()); - FOREACH_MOD(I_OnPostConnect,OnPostConnect(_new)); + FOREACH_MOD(OnPostConnect, (_new)); return CMD_SUCCESS; } -CmdResult CommandFHost::Handle(const parameterlist ¶ms, User* src) -{ - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeDisplayedHost(params[0].c_str()); +CmdResult CommandFHost::HandleRemote(RemoteUser* src, Params& params) +{ + src->ChangeDisplayedHost(params[0]); return CMD_SUCCESS; } -CmdResult CommandFIdent::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFIdent::HandleRemote(RemoteUser* src, Params& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeIdent(params[0].c_str()); + src->ChangeIdent(params[0]); return CMD_SUCCESS; } -CmdResult CommandFName::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFName::HandleRemote(RemoteUser* src, Params& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeName(params[0].c_str()); + src->ChangeRealName(params[0]); return CMD_SUCCESS; } +CommandUID::Builder::Builder(User* user) + : CmdBuilder(TreeServer::Get(user)->GetID(), "UID") +{ + push(user->uuid); + push_int(user->age); + push(user->nick); + push(user->GetRealHost()); + push(user->GetDisplayedHost()); + push(user->ident); + push(user->GetIPString()); + push_int(user->signon); + push(user->GetModeLetters(true)); + push_last(user->GetRealName()); +} diff --git a/src/modules/m_spanningtree/utils.cpp b/src/modules/m_spanningtree/utils.cpp index 367a3b921..cde627e21 100644 --- a/src/modules/m_spanningtree/utils.cpp +++ b/src/modules/m_spanningtree/utils.cpp @@ -21,21 +21,19 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "link.h" #include "treesocket.h" #include "resolvers.h" +#include "commandbuilder.h" + +SpanningTreeUtilities* Utils = NULL; -/* Create server sockets off a listener. */ ModResult ModuleSpanningTree::OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) { - if (from->bind_tag->getString("type") != "servers") + if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "servers")) return MOD_RES_PASSTHRU; std::string incomingip = client->addr(); @@ -45,7 +43,7 @@ ModResult ModuleSpanningTree::OnAcceptConnection(int newsock, ListenSocket* from if (*i == "*" || *i == incomingip || irc::sockets::cidr_mask(*i).match(*client)) { /* we don't need to do anything with the pointer, creating it stores it in the necessary places */ - new TreeSocket(Utils, newsock, from, client, server); + new TreeSocket(newsock, from, client, server); return MOD_RES_ALLOW; } } @@ -53,18 +51,12 @@ ModResult ModuleSpanningTree::OnAcceptConnection(int newsock, ListenSocket* from return MOD_RES_DENY; } -/** Yay for fast searches! - * This is hundreds of times faster than recursion - * or even scanning a linked list, especially when - * there are more than a few servers to deal with. - * (read as: lots). - */ TreeServer* SpanningTreeUtilities::FindServer(const std::string &ServerName) { - if (ServerInstance->IsSID(ServerName)) + if (InspIRCd::IsSID(ServerName)) return this->FindServerID(ServerName); - server_hash::iterator iter = serverlist.find(ServerName.c_str()); + server_hash::iterator iter = serverlist.find(ServerName); if (iter != serverlist.end()) { return iter->second; @@ -75,41 +67,8 @@ TreeServer* SpanningTreeUtilities::FindServer(const std::string &ServerName) } } -/** Returns the locally connected server we must route a - * message through to reach server 'ServerName'. This - * only applies to one-to-one and not one-to-many routing. - * See the comments for the constructor of TreeServer - * for more details. - */ -TreeServer* SpanningTreeUtilities::BestRouteTo(const std::string &ServerName) -{ - if (ServerName.c_str() == TreeRoot->GetName() || ServerName == ServerInstance->Config->GetSID()) - return NULL; - TreeServer* Found = FindServer(ServerName); - if (Found) - { - return Found->GetRoute(); - } - else - { - // Cheat a bit. This allows for (better) working versions of routing commands with nick based prefixes, without hassle - User *u = ServerInstance->FindNick(ServerName); - if (u) - { - Found = FindServer(u->server); - if (Found) - return Found->GetRoute(); - } - - return NULL; - } -} - /** Find the first server matching a given glob mask. - * Theres no find-using-glob method of hash_map [awwww :-(] - * so instead, we iterate over the list using an iterator - * and match each one until we get a hit. Yes its slow, - * deal with it. + * We iterate over the list and match each one until we get a hit. */ TreeServer* SpanningTreeUtilities::FindServerMask(const std::string &ServerName) { @@ -130,27 +89,36 @@ TreeServer* SpanningTreeUtilities::FindServerID(const std::string &id) return NULL; } -SpanningTreeUtilities::SpanningTreeUtilities(ModuleSpanningTree* C) : Creator(C) +TreeServer* SpanningTreeUtilities::FindRouteTarget(const std::string& target) { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"***** Using SID for hash: %s *****", ServerInstance->Config->GetSID().c_str()); + TreeServer* const server = FindServer(target); + if (server) + return server; + + User* const user = ServerInstance->FindNick(target); + if (user) + return TreeServer::Get(user); - this->TreeRoot = new TreeServer(this, ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc, ServerInstance->Config->GetSID()); - this->ReadConfiguration(); + return NULL; +} + +SpanningTreeUtilities::SpanningTreeUtilities(ModuleSpanningTree* C) + : Creator(C), TreeRoot(NULL) + , PingFreq(60) // XXX: TreeServer constructor reads this and TreeRoot is created before the config is read, so init it to something (value doesn't matter) to avoid a valgrind warning in TimerManager on unload +{ + ServerInstance->Timers.AddTimer(&RefreshTimer); } CullResult SpanningTreeUtilities::cull() { - while (TreeRoot->ChildCount()) + const TreeServer::ChildServers& children = TreeRoot->GetChildren(); + while (!children.empty()) { - TreeServer* child_server = TreeRoot->GetChild(0); - if (child_server) - { - TreeSocket* sock = child_server->GetSocket(); - sock->Close(); - } + TreeSocket* sock = children.front()->GetSocket(); + sock->Close(); } - for(std::map<TreeSocket*, std::pair<std::string, int> >::iterator i = timeoutlist.begin(); i != timeoutlist.end(); ++i) + for(TimeoutList::iterator i = timeoutlist.begin(); i != timeoutlist.end(); ++i) { TreeSocket* s = i->first; s->Close(); @@ -165,26 +133,19 @@ SpanningTreeUtilities::~SpanningTreeUtilities() delete TreeRoot; } -void SpanningTreeUtilities::AddThisServer(TreeServer* server, TreeServerList &list) -{ - if (list.find(server) == list.end()) - list[server] = server; -} - -/* returns a list of DIRECT servernames for a specific channel */ -void SpanningTreeUtilities::GetListOfServersForChannel(Channel* c, TreeServerList &list, char status, const CUList &exempt_list) +// Returns a list of DIRECT servers for a specific channel +void SpanningTreeUtilities::GetListOfServersForChannel(Channel* c, TreeSocketSet& list, char status, const CUList& exempt_list) { unsigned int minrank = 0; if (status) { - ModeHandler* mh = ServerInstance->Modes->FindPrefix(status); + PrefixMode* mh = ServerInstance->Modes->FindPrefix(status); if (mh) minrank = mh->GetPrefixRank(); } - const UserMembList *ulist = c->GetUsers(); - - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + const Channel::MemberMap& ulist = c->GetUsers(); + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { if (IS_LOCAL(i->first)) continue; @@ -194,86 +155,35 @@ void SpanningTreeUtilities::GetListOfServersForChannel(Channel* c, TreeServerLis if (exempt_list.find(i->first) == exempt_list.end()) { - TreeServer* best = this->BestRouteTo(i->first->server); - if (best) - AddThisServer(best,list); + TreeServer* best = TreeServer::Get(i->first); + list.insert(best->GetSocket()); } } return; } -bool SpanningTreeUtilities::DoOneToAllButSender(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& omit) +void SpanningTreeUtilities::DoOneToAllButSender(const CmdBuilder& params, TreeServer* omitroute) { - TreeServer* omitroute = this->BestRouteTo(omit); - std::string FullLine = ":" + prefix + " " + command; - unsigned int words = params.size(); - for (unsigned int x = 0; x < words; x++) - { - FullLine = FullLine + " " + params[x]; - } - unsigned int items = this->TreeRoot->ChildCount(); - for (unsigned int x = 0; x < items; x++) - { - TreeServer* Route = this->TreeRoot->GetChild(x); - // Send the line IF: - // The route has a socket (its a direct connection) - // The route isnt the one to be omitted - // The route isnt the path to the one to be omitted - if ((Route) && (Route->GetSocket()) && (Route->GetName() != omit) && (omitroute != Route)) - { - TreeSocket* Sock = Route->GetSocket(); - if (Sock) - Sock->WriteLine(FullLine); - } - } - return true; -} + const std::string& FullLine = params.str(); -bool SpanningTreeUtilities::DoOneToMany(const std::string &prefix, const std::string &command, const parameterlist ¶ms) -{ - std::string FullLine = ":" + prefix + " " + command; - unsigned int words = params.size(); - for (unsigned int x = 0; x < words; x++) - { - FullLine = FullLine + " " + params[x]; - } - unsigned int items = this->TreeRoot->ChildCount(); - for (unsigned int x = 0; x < items; x++) + const TreeServer::ChildServers& children = TreeRoot->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = children.begin(); i != children.end(); ++i) { - TreeServer* Route = this->TreeRoot->GetChild(x); - if (Route && Route->GetSocket()) + TreeServer* Route = *i; + // Send the line if the route isn't the path to the one to be omitted + if (Route != omitroute) { - TreeSocket* Sock = Route->GetSocket(); - if (Sock) - Sock->WriteLine(FullLine); + Route->GetSocket()->WriteLine(FullLine); } } - return true; } -bool SpanningTreeUtilities::DoOneToOne(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& target) +void SpanningTreeUtilities::DoOneToOne(const CmdBuilder& params, Server* server) { - TreeServer* Route = this->BestRouteTo(target); - if (Route) - { - std::string FullLine = ":" + prefix + " " + command; - unsigned int words = params.size(); - for (unsigned int x = 0; x < words; x++) - { - FullLine = FullLine + " " + params[x]; - } - if (Route && Route->GetSocket()) - { - TreeSocket* Sock = Route->GetSocket(); - if (Sock) - Sock->WriteLine(FullLine); - } - return true; - } - else - { - return false; - } + TreeServer* ts = static_cast<TreeServer*>(server); + TreeSocket* sock = ts->GetSocket(); + if (sock) + sock->WriteLine(params); } void SpanningTreeUtilities::RefreshIPCache() @@ -284,28 +194,27 @@ void SpanningTreeUtilities::RefreshIPCache() Link* L = *i; if (!L->Port) { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"m_spanningtree: Ignoring a link block without a port."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring a link block without a port."); /* Invalid link block */ continue; } - if (L->AllowMask.length()) - ValidIPs.push_back(L->AllowMask); + ValidIPs.insert(ValidIPs.end(), L->AllowMasks.begin(), L->AllowMasks.end()); irc::sockets::sockaddrs dummy; bool ipvalid = irc::sockets::aptosa(L->IPAddr, L->Port, dummy); if ((L->IPAddr == "*") || (ipvalid)) ValidIPs.push_back(L->IPAddr); - else + else if (this->Creator->DNS) { + SecurityIPResolver* sr = new SecurityIPResolver(Creator, *this->Creator->DNS, L->IPAddr, L, DNS::QUERY_AAAA); try { - bool cached = false; - SecurityIPResolver* sr = new SecurityIPResolver(Creator, this, L->IPAddr, L, cached, DNS_QUERY_AAAA); - ServerInstance->AddResolver(sr, cached); + this->Creator->DNS->Process(sr); } - catch (...) + catch (DNS::Exception &) { + delete sr; } } } @@ -319,15 +228,11 @@ void SpanningTreeUtilities::ReadConfiguration() HideULines = security->getBool("hideulines"); AnnounceTSChange = options->getBool("announcets"); AllowOptCommon = options->getBool("allowmismatch"); - ChallengeResponse = !security->getBool("disablehmac"); quiet_bursts = ServerInstance->Config->ConfValue("performance")->getBool("quietbursts"); - PingWarnTime = options->getInt("pingwarning"); - PingFreq = options->getInt("serverpingfreq"); + PingWarnTime = options->getDuration("pingwarning", 15); + PingFreq = options->getDuration("serverpingfreq", 60, 1); - if (PingFreq == 0) - PingFreq = 60; - - if (PingWarnTime < 0 || PingWarnTime > PingFreq - 1) + if (PingWarnTime >= PingFreq) PingWarnTime = 0; AutoconnectBlocks.clear(); @@ -339,14 +244,18 @@ void SpanningTreeUtilities::ReadConfiguration() reference<Link> L = new Link(tag); std::string linkname = tag->getString("name"); L->Name = linkname.c_str(); - L->AllowMask = tag->getString("allowmask"); + + irc::spacesepstream sep = tag->getString("allowmask"); + for (std::string s; sep.GetToken(s);) + L->AllowMasks.push_back(s); + L->IPAddr = tag->getString("ipaddr"); - L->Port = tag->getInt("port"); + L->Port = tag->getUInt("port", 0); L->SendPass = tag->getString("sendpass", tag->getString("password")); L->RecvPass = tag->getString("recvpass", tag->getString("password")); L->Fingerprint = tag->getString("fingerprint"); L->HiddenFromStats = tag->getBool("statshidden"); - L->Timeout = tag->getInt("timeout", 30); + L->Timeout = tag->getDuration("timeout", 30); L->Hook = tag->getString("ssl"); L->Bind = tag->getString("bind"); L->Hidden = tag->getBool("hidden"); @@ -355,31 +264,31 @@ void SpanningTreeUtilities::ReadConfiguration() throw ModuleException("Invalid configuration, found a link tag without a name!" + (!L->IPAddr.empty() ? " IP address: "+L->IPAddr : "")); if (L->Name.find('.') == std::string::npos) - throw ModuleException("The link name '"+assign(L->Name)+"' is invalid as it must contain at least one '.' character"); + throw ModuleException("The link name '"+L->Name+"' is invalid as it must contain at least one '.' character"); - if (L->Name.length() > 64) - throw ModuleException("The link name '"+assign(L->Name)+"' is invalid as it is longer than 64 characters"); + if (L->Name.length() > ServerInstance->Config->Limits.MaxHost) + throw ModuleException("The link name '"+L->Name+"' is invalid as it is longer than " + ConvToStr(ServerInstance->Config->Limits.MaxHost) + " characters"); if (L->RecvPass.empty()) - throw ModuleException("Invalid configuration for server '"+assign(L->Name)+"', recvpass not defined"); + throw ModuleException("Invalid configuration for server '"+L->Name+"', recvpass not defined"); if (L->SendPass.empty()) - throw ModuleException("Invalid configuration for server '"+assign(L->Name)+"', sendpass not defined"); + throw ModuleException("Invalid configuration for server '"+L->Name+"', sendpass not defined"); if ((L->SendPass.find(' ') != std::string::npos) || (L->RecvPass.find(' ') != std::string::npos)) - throw ModuleException("Link block '" + assign(L->Name) + "' has a password set that contains a space character which is invalid"); + throw ModuleException("Link block '" + L->Name + "' has a password set that contains a space character which is invalid"); if ((L->SendPass[0] == ':') || (L->RecvPass[0] == ':')) - throw ModuleException("Link block '" + assign(L->Name) + "' has a password set that begins with a colon (:) which is invalid"); + throw ModuleException("Link block '" + L->Name + "' has a password set that begins with a colon (:) which is invalid"); if (L->IPAddr.empty()) { L->IPAddr = "*"; - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Configuration warning: Link block '" + assign(L->Name) + "' has no IP defined! This will allow any IP to connect as this server, and MAY not be what you want."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Configuration warning: Link block '" + L->Name + "' has no IP defined! This will allow any IP to connect as this server, and MAY not be what you want."); } if (!L->Port) - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Configuration warning: Link block '" + assign(L->Name) + "' has no port defined, you will not be able to /connect it."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Configuration warning: Link block '" + L->Name + "' has no port defined, you will not be able to /connect it."); L->Fingerprint.erase(std::remove(L->Fingerprint.begin(), L->Fingerprint.end(), ':'), L->Fingerprint.end()); LinkBlocks.push_back(L); @@ -390,7 +299,7 @@ void SpanningTreeUtilities::ReadConfiguration() { ConfigTag* tag = i->second; reference<Autoconnect> A = new Autoconnect(tag); - A->Period = tag->getInt("period"); + A->Period = tag->getDuration("period", 60, 1); A->NextConnectTime = ServerInstance->Time() + A->Period; A->position = -1; irc::spacesepstream ss(tag->getString("server")); @@ -400,11 +309,6 @@ void SpanningTreeUtilities::ReadConfiguration() A->servers.push_back(server); } - if (A->Period <= 0) - { - throw ModuleException("Invalid configuration for autoconnect, period not a positive integer!"); - } - if (A->servers.empty()) { throw ModuleException("Invalid configuration for autoconnect, server cannot be empty!"); @@ -413,6 +317,9 @@ void SpanningTreeUtilities::ReadConfiguration() AutoconnectBlocks.push_back(A); } + for (server_hash::const_iterator i = serverlist.begin(); i != serverlist.end(); ++i) + i->second->CheckULine(); + RefreshIPCache(); } @@ -421,7 +328,7 @@ Link* SpanningTreeUtilities::FindLink(const std::string& name) for (std::vector<reference<Link> >::iterator i = LinkBlocks.begin(); i != LinkBlocks.end(); ++i) { Link* x = *i; - if (InspIRCd::Match(x->Name.c_str(), name.c_str(), rfc_case_insensitive_map)) + if (InspIRCd::Match(x->Name, name, ascii_case_insensitive_map)) { return x; } @@ -429,15 +336,20 @@ Link* SpanningTreeUtilities::FindLink(const std::string& name) return NULL; } -void SpanningTreeUtilities::Rehash() +void SpanningTreeUtilities::SendChannelMessage(const std::string& prefix, Channel* target, const std::string& text, char status, const CUList& exempt_list, const char* message_type, TreeSocket* omit) { - server_hash temp; - for (server_hash::const_iterator i = serverlist.begin(); i != serverlist.end(); ++i) - temp.insert(std::make_pair(i->first, i->second)); - serverlist.swap(temp); - temp.clear(); - - for (server_hash::const_iterator i = sidlist.begin(); i != sidlist.end(); ++i) - temp.insert(std::make_pair(i->first, i->second)); - sidlist.swap(temp); + CmdBuilder msg(prefix, message_type); + msg.push_raw(' '); + if (status != 0) + msg.push_raw(status); + msg.push_raw(target->name).push_last(text); + + TreeSocketSet list; + this->GetListOfServersForChannel(target, list, status, exempt_list); + for (TreeSocketSet::iterator i = list.begin(); i != list.end(); ++i) + { + TreeSocket* Sock = *i; + if (Sock != omit) + Sock->WriteLine(msg); + } } diff --git a/src/modules/m_spanningtree/utils.h b/src/modules/m_spanningtree/utils.h index 5559b3459..495b09414 100644 --- a/src/modules/m_spanningtree/utils.h +++ b/src/modules/m_spanningtree/utils.h @@ -20,37 +20,35 @@ */ -#ifndef M_SPANNINGTREE_UTILS_H -#define M_SPANNINGTREE_UTILS_H +#pragma once #include "inspircd.h" +#include "cachetimer.h" -/* Foward declarations */ class TreeServer; class TreeSocket; class Link; class Autoconnect; class ModuleSpanningTree; class SpanningTreeUtilities; +class CmdBuilder; -/* This hash_map holds the hash equivalent of the server - * tree, used for rapid linear lookups. - */ -#ifdef HASHMAP_DEPRECATED - typedef nspace::hash_map<std::string, TreeServer*, nspace::insensitive, irc::StrHashComp> server_hash; -#else - typedef nspace::hash_map<std::string, TreeServer*, nspace::hash<std::string>, irc::StrHashComp> server_hash; -#endif +extern SpanningTreeUtilities* Utils; -typedef std::map<TreeServer*,TreeServer*> TreeServerList; +/** Associative container type, mapping server names/ids to TreeServers + */ +typedef TR1NS::unordered_map<std::string, TreeServer*, irc::insensitive, irc::StrHashComp> server_hash; /** Contains helper functions and variables for this module, * and keeps them out of the global namespace */ class SpanningTreeUtilities : public classbase { + CacheRefreshTimer RefreshTimer; + public: - typedef std::map<TreeSocket*, std::pair<std::string, int> > TimeoutList; + typedef std::set<TreeSocket*> TreeSocketSet; + typedef std::map<TreeSocket*, std::pair<std::string, unsigned int> > TimeoutList; /** Creator module */ @@ -77,7 +75,7 @@ class SpanningTreeUtilities : public classbase /* Number of seconds that a server can go without ping * before opers are warned of high latency. */ - int PingWarnTime; + unsigned int PingWarnTime; /** This variable represents the root of the server tree */ TreeServer *TreeRoot; @@ -100,17 +98,9 @@ class SpanningTreeUtilities : public classbase */ std::vector<reference<Autoconnect> > AutoconnectBlocks; - /** True (default) if we are to use challenge-response HMAC - * to authenticate passwords. - * - * NOTE: This defaults to on, but should be turned off if - * you are linking to an older version of inspircd. - */ - bool ChallengeResponse; - /** Ping frequency of server to server links */ - int PingFreq; + unsigned int PingFreq; /** Initialise utility class */ @@ -118,39 +108,39 @@ class SpanningTreeUtilities : public classbase /** Prepare for class destruction */ - CullResult cull(); + CullResult cull() CXX11_OVERRIDE; /** Destroy class and free listeners etc */ ~SpanningTreeUtilities(); - void RouteCommand(TreeServer*, const std::string&, const parameterlist&, User*); + void RouteCommand(TreeServer* origin, CommandBase* cmd, const CommandBase::Params& parameters, User* user); /** Send a message from this server to one other local or remote */ - bool DoOneToOne(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& target); + void DoOneToOne(const CmdBuilder& params, Server* target); /** Send a message from this server to all but one other, local or remote */ - bool DoOneToAllButSender(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& omit); + void DoOneToAllButSender(const CmdBuilder& params, TreeServer* omit); /** Send a message from this server to all others */ - bool DoOneToMany(const std::string &prefix, const std::string &command, const parameterlist ¶ms); + void DoOneToMany(const CmdBuilder& params); /** Read the spanningtree module's tags from the config file */ void ReadConfiguration(); - /** Add a server to the server list for GetListOfServersForChannel + /** Handle nick collision */ - void AddThisServer(TreeServer* server, TreeServerList &list); + bool DoCollision(User* u, TreeServer* server, time_t remotets, const std::string& remoteident, const std::string& remoteip, const std::string& remoteuid, const char* collidecmd); /** Compile a list of servers which contain members of channel c */ - void GetListOfServersForChannel(Channel* c, TreeServerList &list, char status, const CUList &exempt_list); + void GetListOfServersForChannel(Channel* c, TreeSocketSet& list, char status, const CUList& exempt_list); - /** Find a server by name + /** Find a server by name or SID */ TreeServer* FindServer(const std::string &ServerName); @@ -158,9 +148,10 @@ class SpanningTreeUtilities : public classbase */ TreeServer* FindServerID(const std::string &id); - /** Find a route to a server by name + /** Find a server based on a target string. + * @param target Target string where a command should be routed to. May be a server name, a sid, a nickname or a uuid. */ - TreeServer* BestRouteTo(const std::string &ServerName); + TreeServer* FindRouteTarget(const std::string& target); /** Find a server by glob mask */ @@ -174,10 +165,12 @@ class SpanningTreeUtilities : public classbase */ void RefreshIPCache(); - /** Recreate serverlist and sidlist, this is needed because of m_nationalchars changing - * national_case_insensitive_map which is used by the hash function + /** Sends a PRIVMSG or a NOTICE to a channel obeying an exempt list and an optional prefix */ - void Rehash(); + void SendChannelMessage(const std::string& prefix, Channel* target, const std::string& text, char status, const CUList& exempt_list, const char* message_type, TreeSocket* omit = NULL); }; -#endif +inline void SpanningTreeUtilities::DoOneToMany(const CmdBuilder& params) +{ + DoOneToAllButSender(params, NULL); +} diff --git a/src/modules/m_spanningtree/version.cpp b/src/modules/m_spanningtree/version.cpp deleted file mode 100644 index e08d13e6e..000000000 --- a/src/modules/m_spanningtree/version.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" - -#include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -bool TreeSocket::ServerVersion(const std::string &prefix, parameterlist ¶ms) -{ - if (params.size() < 1) - return true; - - TreeServer* ServerSource = Utils->FindServer(prefix); - - if (ServerSource) - { - ServerSource->SetVersion(params[0]); - } - params[0] = ":" + params[0]; - Utils->DoOneToAllButSender(prefix,"VERSION",params,prefix); - return true; -} - diff --git a/src/modules/m_sqlauth.cpp b/src/modules/m_sqlauth.cpp index df97145be..5c3c5a84e 100644 --- a/src/modules/m_sqlauth.cpp +++ b/src/modules/m_sqlauth.cpp @@ -18,10 +18,9 @@ #include "inspircd.h" -#include "sql.h" -#include "hash.h" - -/* $ModDesc: Allow/Deny connections based upon an arbitrary SQL table */ +#include "modules/sql.h" +#include "modules/hash.h" +#include "modules/ssl.h" enum AuthState { AUTH_STATE_NONE = 0, @@ -29,24 +28,69 @@ enum AuthState { AUTH_STATE_FAIL = 2 }; -class AuthQuery : public SQLQuery +class AuthQuery : public SQL::Query { public: const std::string uid; LocalIntExt& pendingExt; bool verbose; - AuthQuery(Module* me, const std::string& u, LocalIntExt& e, bool v) - : SQLQuery(me), uid(u), pendingExt(e), verbose(v) + const std::string& kdf; + const std::string& pwcolumn; + + AuthQuery(Module* me, const std::string& u, LocalIntExt& e, bool v, const std::string& kd, const std::string& pwcol) + : SQL::Query(me) + , uid(u) + , pendingExt(e) + , verbose(v) + , kdf(kd) + , pwcolumn(pwcol) { } - - void OnResult(SQLResult& res) + + void OnResult(SQL::Result& res) CXX11_OVERRIDE { - User* user = ServerInstance->FindNick(uid); + LocalUser* user = static_cast<LocalUser*>(ServerInstance->FindUUID(uid)); if (!user) return; + if (res.Rows()) { + if (!kdf.empty()) + { + HashProvider* hashprov = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + kdf); + if (!hashprov) + { + if (verbose) + ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (a provider for %s was not loaded)", user->GetFullRealHost().c_str(), kdf.c_str()); + pendingExt.set(user, AUTH_STATE_FAIL); + return; + } + + size_t colindex = 0; + if (!pwcolumn.empty() && !res.HasColumn(pwcolumn, colindex)) + { + if (verbose) + ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (the column specified (%s) was not returned)", user->GetFullRealHost().c_str(), pwcolumn.c_str()); + pendingExt.set(user, AUTH_STATE_FAIL); + return; + } + + SQL::Row row; + while (res.GetRow(row)) + { + if (hashprov->Compare(user->password, row[colindex])) + { + pendingExt.set(user, AUTH_STATE_NONE); + return; + } + } + + if (verbose) + ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (Password from the SQL query did not match the user provided password)", user->GetFullRealHost().c_str()); + pendingExt.set(user, AUTH_STATE_FAIL); + return; + } + pendingExt.set(user, AUTH_STATE_NONE); } else @@ -57,41 +101,38 @@ class AuthQuery : public SQLQuery } } - void OnError(SQLerror& error) + void OnError(SQL::Error& error) CXX11_OVERRIDE { User* user = ServerInstance->FindNick(uid); if (!user) return; pendingExt.set(user, AUTH_STATE_FAIL); if (verbose) - ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (SQL query failed: %s)", user->GetFullRealHost().c_str(), error.Str()); + ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s (SQL query failed: %s)", user->GetFullRealHost().c_str(), error.ToString()); } }; class ModuleSQLAuth : public Module { LocalIntExt pendingExt; - dynamic_reference<SQLProvider> SQL; + dynamic_reference<SQL::Provider> SQL; std::string freeformquery; std::string killreason; std::string allowpattern; bool verbose; + std::vector<std::string> hash_algos; + std::string kdf; + std::string pwcolumn; public: - ModuleSQLAuth() : pendingExt("sqlauth-wait", this), SQL(this, "SQL") + ModuleSQLAuth() + : pendingExt("sqlauth-wait", ExtensionItem::EXT_USER, this) + , SQL(this, "SQL") { } - void init() - { - ServerInstance->Modules->AddService(pendingExt); - OnRehash(NULL); - Implementation eventlist[] = { I_OnCheckReady, I_OnRehash, I_OnUserRegister }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* conf = ServerInstance->Config->ConfValue("sqlauth"); std::string dbid = conf->getString("dbid"); @@ -103,9 +144,17 @@ class ModuleSQLAuth : public Module killreason = conf->getString("killreason"); allowpattern = conf->getString("allowpattern"); verbose = conf->getBool("verbose"); + kdf = conf->getString("kdf"); + pwcolumn = conf->getString("column"); + + hash_algos.clear(); + irc::commasepstream algos(conf->getString("hash", "md5,sha256")); + std::string algo; + while (algos.GetToken(algo)) + hash_algos.push_back(algo); } - ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { // Note this is their initial (unresolved) connect block ConfigTag* tag = user->MyClass->config; @@ -127,24 +176,26 @@ class ModuleSQLAuth : public Module pendingExt.set(user, AUTH_STATE_BUSY); - ParamM userinfo; - SQL->PopulateUserInfo(user, userinfo); + SQL::ParamMap userinfo; + SQL::PopulateUserInfo(user, userinfo); userinfo["pass"] = user->password; - HashProvider* md5 = ServerInstance->Modules->FindDataService<HashProvider>("hash/md5"); - if (md5) - userinfo["md5pass"] = md5->hexsum(user->password); + for (std::vector<std::string>::const_iterator it = hash_algos.begin(); it != hash_algos.end(); ++it) + { + HashProvider* hashprov = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + *it); + if (hashprov && !hashprov->IsKDF()) + userinfo[*it + "pass"] = hashprov->Generate(user->password); + } - HashProvider* sha256 = ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"); - if (sha256) - userinfo["sha256pass"] = sha256->hexsum(user->password); + const std::string certfp = SSLClientCert::GetFingerprint(&user->eh); + userinfo["certfp"] = certfp; - SQL->submit(new AuthQuery(this, user->uuid, pendingExt, verbose), freeformquery, userinfo); + SQL->Submit(new AuthQuery(this, user->uuid, pendingExt, verbose, kdf, pwcolumn), freeformquery, userinfo); return MOD_RES_PASSTHRU; } - ModResult OnCheckReady(LocalUser* user) + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { switch (pendingExt.get(user)) { @@ -159,7 +210,7 @@ class ModuleSQLAuth : public Module return MOD_RES_PASSTHRU; } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allow/Deny connections based upon an arbitrary SQL table", VF_VENDOR); } diff --git a/src/modules/m_sqloper.cpp b/src/modules/m_sqloper.cpp index ae581cc4b..da538caef 100644 --- a/src/modules/m_sqloper.cpp +++ b/src/modules/m_sqloper.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2017 Dylan Frank <b00mx0r@aureus.pw> * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> * * This file is part of InspIRCd. InspIRCd is free software: you can @@ -18,138 +19,161 @@ #include "inspircd.h" -#include "sql.h" -#include "hash.h" +#include "modules/sql.h" -/* $ModDesc: Allows storage of oper credentials in an SQL table */ - -static bool OneOfMatches(const char* host, const char* ip, const std::string& hostlist) -{ - std::stringstream hl(hostlist); - std::string xhost; - while (hl >> xhost) - { - if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) - { - return true; - } - } - return false; -} - -class OpMeQuery : public SQLQuery +class OperQuery : public SQL::Query { public: + // This variable will store all the OPER blocks from the DB + std::vector<std::string>& my_blocks; + /** We want to store the username and password if this is called during an /OPER, as we're responsible for /OPER post-DB fetch + * Note: uid will be empty if this DB update was not called as a result of a user command (i.e. /REHASH) + */ const std::string uid, username, password; - OpMeQuery(Module* me, const std::string& u, const std::string& un, const std::string& pw) - : SQLQuery(me), uid(u), username(un), password(pw) + OperQuery(Module* me, std::vector<std::string>& mb, const std::string& u, const std::string& un, const std::string& pw) + : SQL::Query(me) + , my_blocks(mb) + , uid(u) + , username(un) + , password(pw) + { + } + OperQuery(Module* me, std::vector<std::string>& mb) + : SQL::Query(me) + , my_blocks(mb) { } - void OnResult(SQLResult& res) + void OnResult(SQL::Result& res) CXX11_OVERRIDE { - ServerInstance->Logs->Log("m_sqloper",DEBUG, "SQLOPER: result for %s", uid.c_str()); - User* user = ServerInstance->FindNick(uid); - if (!user) - return; + ServerConfig::OperIndex& oper_blocks = ServerInstance->Config->oper_blocks; - // multiple rows may exist - SQLEntries row; + // Remove our previous blocks from oper_blocks for a clean update + for (std::vector<std::string>::const_iterator i = my_blocks.begin(); i != my_blocks.end(); ++i) + { + oper_blocks.erase(*i); + } + my_blocks.clear(); + + SQL::Row row; + // Iterate through DB results to create oper blocks from sqloper rows while (res.GetRow(row)) { -#if 0 - parameterlist cols; + std::vector<std::string> cols; res.GetCols(cols); - std::vector<KeyVal>* items; - reference<ConfigTag> tag = ConfigTag::create("oper", "<m_sqloper>", 0, items); - for(unsigned int i=0; i < cols.size(); i++) + // Create the oper tag as if we were the conf file. + ConfigItems* items; + reference<ConfigTag> tag = ConfigTag::create("oper", MODNAME, 0, items); + + /** Iterate through each column in the SQLOpers table. An infinite number of fields can be specified. + * Column 'x' with cell value 'y' will be the same as x=y in an OPER block in opers.conf. + */ + for (unsigned int i=0; i < cols.size(); ++i) { - if (!row[i].nul) - items->insert(std::make_pair(cols[i], row[i])); + if (!row[i].IsNull()) + (*items)[cols[i]] = row[i]; } -#else - if (OperUser(user, row[0], row[1])) - return; -#endif + const std::string name = tag->getString("name"); + + // Skip both duplicate sqloper blocks and sqloper blocks that attempt to override conf blocks. + if (oper_blocks.find(name) != oper_blocks.end()) + continue; + + const std::string type = tag->getString("type"); + ServerConfig::OperIndex::iterator tblk = ServerInstance->Config->OperTypes.find(type); + if (tblk == ServerInstance->Config->OperTypes.end()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Sqloper block " + name + " has missing type " + type); + ServerInstance->SNO->WriteGlobalSno('a', "m_sqloper: Oper block %s has missing type %s", name.c_str(), type.c_str()); + continue; + } + + OperInfo* ifo = new OperInfo(type); + + ifo->type_block = tblk->second->type_block; + ifo->oper_block = tag; + ifo->class_blocks.assign(tblk->second->class_blocks.begin(), tblk->second->class_blocks.end()); + oper_blocks[name] = ifo; + my_blocks.push_back(name); + } + + // If this was done as a result of /OPER and not a config read + if (!uid.empty()) + { + // Now that we've updated the DB, call any other /OPER hooks and then call /OPER + OperExec(); } - ServerInstance->Logs->Log("m_sqloper",DEBUG, "SQLOPER: no matches for %s (checked %d rows)", uid.c_str(), res.Rows()); - // nobody succeeded... fall back to OPER - fallback(); } - void OnError(SQLerror& error) + void OnError(SQL::Error& error) CXX11_OVERRIDE { - ServerInstance->Logs->Log("m_sqloper",DEFAULT, "SQLOPER: query failed (%s)", error.Str()); - fallback(); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "query failed (%s)", error.ToString()); + ServerInstance->SNO->WriteGlobalSno('a', "m_sqloper: failed to update blocks from database"); + if (!uid.empty()) + { + // Fallback. We don't want to block a netadmin from /OPER + OperExec(); + } } - void fallback() + // Call /oper after placing all blocks from the SQL table into the config->oper_blocks list. + void OperExec() { User* user = ServerInstance->FindNick(uid); - if (!user) + LocalUser* localuser = IS_LOCAL(user); + // This should never be true + if (!localuser) return; - Command* oper_command = ServerInstance->Parser->GetHandler("OPER"); + Command* oper_command = ServerInstance->Parser.GetHandler("OPER"); if (oper_command) { - std::vector<std::string> params; + CommandBase::Params params; params.push_back(username); params.push_back(password); - oper_command->Handle(params, user); - } - else - { - ServerInstance->Logs->Log("m_sqloper",SPARSE, "BUG: WHAT?! Why do we have no OPER command?!"); - } - } - - bool OperUser(User* user, const std::string &pattern, const std::string &type) - { - OperIndex::iterator iter = ServerInstance->Config->oper_blocks.find(" " + type); - if (iter == ServerInstance->Config->oper_blocks.end()) - { - ServerInstance->Logs->Log("m_sqloper",DEFAULT, "SQLOPER: bad type '%s' in returned row for oper %s", type.c_str(), username.c_str()); - return false; - } - OperInfo* ifo = iter->second; - std::string hostname(user->ident); + // Begin callback to other modules (i.e. sslinfo) now that we completed the DB fetch + ModResult MOD_RESULT; - hostname.append("@").append(user->host); + std::string origin = "OPER"; + FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (origin, params, localuser, true)); + if (MOD_RESULT == MOD_RES_DENY) + return; - if (OneOfMatches(hostname.c_str(), user->GetIPString(), pattern)) + // Now handle /OPER. + ClientProtocol::TagMap tags; + oper_command->Handle(user, CommandBase::Params(params, tags)); + } + else { - /* Opertype and host match, looks like this is it. */ - - user->Oper(ifo); - return true; + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, "BUG: WHAT?! Why do we have no OPER command?!"); } - - return false; } }; class ModuleSQLOper : public Module { + // Whether OperQuery is running + bool active; std::string query; - std::string hashtype; - dynamic_reference<SQLProvider> SQL; + // Stores oper blocks from DB + std::vector<std::string> my_blocks; + dynamic_reference<SQL::Provider> SQL; public: - ModuleSQLOper() : SQL(this, "SQL") {} - - void init() + ModuleSQLOper() + : active(false) + , SQL(this, "SQL") { - OnRehash(NULL); - - Implementation eventlist[] = { I_OnRehash, I_OnPreCommand }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { + // Clear list of our blocks, as ConfigReader just wiped them anyway + my_blocks.clear(); + ConfigTag* tag = ServerInstance->Config->ConfValue("sqloper"); std::string dbid = tag->getString("dbid"); @@ -158,42 +182,67 @@ public: else SQL.SetProvider("SQL/" + dbid); - hashtype = tag->getString("hash"); - query = tag->getString("query", "SELECT hostname as host, type FROM ircd_opers WHERE username='$username' AND password='$password'"); + query = tag->getString("query", "SELECT * FROM ircd_opers WHERE active=1;"); + // Update sqloper list from the database. + GetOperBlocks(); } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ~ModuleSQLOper() { - if (validated && command == "OPER" && parameters.size() >= 2) + // Remove all oper blocks that were from the DB + for (std::vector<std::string>::const_iterator i = my_blocks.begin(); i != my_blocks.end(); ++i) + { + ServerInstance->Config->oper_blocks.erase(*i); + } + } + + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE + { + // If we are not in the middle of an existing /OPER and someone is trying to oper-up + if (validated && command == "OPER" && parameters.size() >= 2 && !active) { if (SQL) { - LookupOper(user, parameters[0], parameters[1]); - /* Query is in progress, it will re-invoke OPER if needed */ + GetOperBlocks(user->uuid, parameters[0], parameters[1]); + /** We need to reload oper blocks from the DB before other + * hooks can run (i.e. sslinfo). We will re-call /OPER later. + */ return MOD_RES_DENY; } - ServerInstance->Logs->Log("m_sqloper",DEFAULT, "SQLOPER: database not present"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "database not present"); + } + else if (active) + { + active = false; } + // There is either no DB or we successfully reloaded oper blocks return MOD_RES_PASSTHRU; } - void LookupOper(User* user, const std::string &username, const std::string &password) + // The one w/o params is for non-/OPER DB updates, such as a rehash. + void GetOperBlocks() { - HashProvider* hash = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + hashtype); - - ParamM userinfo; - SQL->PopulateUserInfo(user, userinfo); - userinfo["username"] = username; - userinfo["password"] = hash ? hash->hexsum(password) : password; + SQL->Submit(new OperQuery(this, my_blocks), query); + } + void GetOperBlocks(const std::string u, const std::string& un, const std::string& pw) + { + active = true; + // Call to SQL query to fetch oper list from SQL table. + SQL->Submit(new OperQuery(this, my_blocks, u, un, pw), query); + } - SQL->submit(new OpMeQuery(this, user->uuid, username, password), query, userinfo); + void Prioritize() CXX11_OVERRIDE + { + /** Run before other /OPER hooks that expect populated blocks, i.e. sslinfo or a TOTP module. + * We issue a DENY first, and will re-run OnPreCommand later to trigger the other hooks post-DB update. + */ + ServerInstance->Modules.SetPriority(this, I_OnPreCommand, PRIORITY_FIRST); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Allows storage of oper credentials in an SQL table", VF_VENDOR); } - }; MODULE_INIT(ModuleSQLOper) diff --git a/src/modules/m_sslinfo.cpp b/src/modules/m_sslinfo.cpp index 083ac0f04..6d0a84249 100644 --- a/src/modules/m_sslinfo.cpp +++ b/src/modules/m_sslinfo.cpp @@ -18,13 +18,25 @@ #include "inspircd.h" -#include "ssl.h" +#include "modules/ssl.h" +#include "modules/whois.h" -/* $ModDesc: Provides SSL metadata, including /WHOIS information and /SSLINFO command */ +enum +{ + // From oftc-hybrid. + RPL_WHOISCERTFP = 276, + + // From UnrealIRCd. + RPL_WHOISSECURE = 671 +}; class SSLCertExt : public ExtensionItem { public: - SSLCertExt(Module* parent) : ExtensionItem("ssl_cert", parent) {} + SSLCertExt(Module* parent) + : ExtensionItem("ssl_cert", ExtensionItem::EXT_USER, parent) + { + } + ssl_cert* get(const Extensible* item) const { return static_cast<ssl_cert*>(get_raw(item)); @@ -37,12 +49,12 @@ class SSLCertExt : public ExtensionItem { delete old; } - std::string serialize(SerializeFormat format, const Extensible* container, void* item) const + std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE { return static_cast<ssl_cert*>(item)->GetMetaLine(); } - void unserialize(SerializeFormat format, Extensible* container, const std::string& value) + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE { ssl_cert* cert = new ssl_cert; set(container, cert); @@ -67,7 +79,7 @@ class SSLCertExt : public ExtensionItem { } } - void free(void* item) + void free(Extensible* container, void* item) CXX11_OVERRIDE { ssl_cert* old = static_cast<ssl_cert*>(item); if (old && old->refcount_dec()) @@ -87,107 +99,99 @@ class CommandSSLInfo : public Command this->syntax = "<nick>"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* target = ServerInstance->FindNickOnly(parameters[0]); if ((!target) || (target->registered != REG_ALL)) { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nickname", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); return CMD_FAILURE; } bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly"); - if (operonlyfp && !IS_OPER(user) && target != user) + if (operonlyfp && !user->IsOper() && target != user) { - user->WriteServ("NOTICE %s :*** You cannot view SSL certificate information for other users", user->nick.c_str()); + user->WriteNotice("*** You cannot view SSL certificate information for other users"); return CMD_FAILURE; } ssl_cert* cert = CertExt.get(target); if (!cert) { - user->WriteServ("NOTICE %s :*** No SSL certificate for this user", user->nick.c_str()); + user->WriteNotice("*** No SSL certificate for this user"); } else if (cert->GetError().length()) { - user->WriteServ("NOTICE %s :*** No SSL certificate information for this user (%s).", user->nick.c_str(), cert->GetError().c_str()); + user->WriteNotice("*** No SSL certificate information for this user (" + cert->GetError() + ")."); } else { - user->WriteServ("NOTICE %s :*** Distinguished Name: %s", user->nick.c_str(), cert->GetDN().c_str()); - user->WriteServ("NOTICE %s :*** Issuer: %s", user->nick.c_str(), cert->GetIssuer().c_str()); - user->WriteServ("NOTICE %s :*** Key Fingerprint: %s", user->nick.c_str(), cert->GetFingerprint().c_str()); + user->WriteNotice("*** Distinguished Name: " + cert->GetDN()); + user->WriteNotice("*** Issuer: " + cert->GetIssuer()); + user->WriteNotice("*** Key Fingerprint: " + cert->GetFingerprint()); } return CMD_SUCCESS; } }; -class ModuleSSLInfo : public Module +class UserCertificateAPIImpl : public UserCertificateAPIBase { - CommandSSLInfo cmd; + SSLCertExt& ext; public: - ModuleSSLInfo() : cmd(this) + UserCertificateAPIImpl(Module* mod, SSLCertExt& certext) + : UserCertificateAPIBase(mod), ext(certext) { } - void init() - { - ServerInstance->Modules->AddService(cmd); + ssl_cert* GetCertificate(User* user) CXX11_OVERRIDE + { + return ext.get(user); + } +}; - ServerInstance->Modules->AddService(cmd.CertExt); +class ModuleSSLInfo : public Module, public Whois::EventListener +{ + CommandSSLInfo cmd; + UserCertificateAPIImpl APIImpl; - Implementation eventlist[] = { I_OnWhois, I_OnPreCommand, I_OnSetConnectClass, I_OnUserConnect, I_OnPostConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + public: + ModuleSSLInfo() + : Whois::EventListener(this) + , cmd(this) + , APIImpl(this, cmd.CertExt) + { } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("SSL Certificate Utilities", VF_VENDOR); } - void OnWhois(User* source, User* dest) + void OnWhois(Whois::Context& whois) CXX11_OVERRIDE { - ssl_cert* cert = cmd.CertExt.get(dest); + ssl_cert* cert = cmd.CertExt.get(whois.GetTarget()); if (cert) { - ServerInstance->SendWhoisLine(source, dest, 671, "%s %s :is using a secure connection", source->nick.c_str(), dest->nick.c_str()); + whois.SendLine(RPL_WHOISSECURE, "is using a secure connection"); bool operonlyfp = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly"); - if ((!operonlyfp || source == dest || IS_OPER(source)) && !cert->fingerprint.empty()) - ServerInstance->SendWhoisLine(source, dest, 276, "%s %s :has client certificate fingerprint %s", - source->nick.c_str(), dest->nick.c_str(), cert->fingerprint.c_str()); - } - } - - bool OneOfMatches(const char* host, const char* ip, const char* hostlist) - { - std::stringstream hl(hostlist); - std::string xhost; - while (hl >> xhost) - { - if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) - { - return true; - } + if ((!operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty()) + whois.SendLine(RPL_WHOISCERTFP, InspIRCd::Format("has client certificate fingerprint %s", cert->fingerprint.c_str())); } - return false; } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { if ((command == "OPER") && (validated)) { - OperIndex::iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]); + ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.find(parameters[0]); if (i != ServerInstance->Config->oper_blocks.end()) { OperInfo* ifo = i->second; - if (!ifo->oper_block) - return MOD_RES_PASSTHRU; - ssl_cert* cert = cmd.CertExt.get(user); if (ifo->oper_block->getBool("sslonly") && !cert) { - user->WriteNumeric(491, "%s :This oper login requires an SSL connection.", user->nick.c_str()); + user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires an SSL connection."); user->CommandFloodPenalty += 10000; return MOD_RES_DENY; } @@ -195,7 +199,7 @@ class ModuleSSLInfo : public Module std::string fingerprint; if (ifo->oper_block->readString("fingerprint", fingerprint) && (!cert || cert->GetFingerprint() != fingerprint)) { - user->WriteNumeric(491, "%s :This oper login requires a matching SSL fingerprint.",user->nick.c_str()); + user->WriteNumeric(ERR_NOOPERHOST, "This oper login requires a matching SSL certificate fingerprint."); user->CommandFloodPenalty += 10000; return MOD_RES_DENY; } @@ -206,59 +210,68 @@ class ModuleSSLInfo : public Module return MOD_RES_PASSTHRU; } - void OnUserConnect(LocalUser* user) + void OnUserConnect(LocalUser* user) CXX11_OVERRIDE { - SocketCertificateRequest req(&user->eh, this); - if (!req.cert) - return; - cmd.CertExt.set(user, req.cert); + ssl_cert* cert = SSLClientCert::GetCertificate(&user->eh); + if (cert) + cmd.CertExt.set(user, cert); } - void OnPostConnect(User* user) + void OnPostConnect(User* user) CXX11_OVERRIDE { - ssl_cert *cert = cmd.CertExt.get(user); - if (!cert || cert->fingerprint.empty()) + LocalUser* const localuser = IS_LOCAL(user); + if (!localuser) + return; + + const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(&localuser->eh); + if (!ssliohook) + return; + + ssl_cert* const cert = ssliohook->GetCertificate(); + + { + std::string text = "*** You are connected to "; + if (!ssliohook->GetServerName(text)) + text.append(ServerInstance->Config->ServerName); + text.append(" using SSL cipher '"); + ssliohook->GetCiphersuite(text); + text.push_back('\''); + if ((cert) && (!cert->GetFingerprint().empty())) + text.append(" and your SSL certificate fingerprint is ").append(cert->GetFingerprint()); + user->WriteNotice(text); + } + + if (!cert) return; // find an auto-oper block for this user - for(OperIndex::iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); i++) + for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i) { OperInfo* ifo = i->second; - if (!ifo->oper_block) - continue; - std::string fp = ifo->oper_block->getString("fingerprint"); if (fp == cert->fingerprint && ifo->oper_block->getBool("autologin")) user->Oper(ifo); } } - ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { - SocketCertificateRequest req(&user->eh, this); + ssl_cert* cert = SSLClientCert::GetCertificate(&user->eh); bool ok = true; if (myclass->config->getString("requiressl") == "trusted") { - ok = (req.cert && req.cert->IsCAVerified()); + ok = (cert && cert->IsCAVerified()); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires a trusted SSL cert. Client %s one.", (ok ? "has" : "does not have")); } else if (myclass->config->getBool("requiressl")) { - ok = (req.cert != NULL); + ok = (cert != NULL); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class requires any SSL cert. Client %s one.", (ok ? "has" : "does not have")); } if (!ok) return MOD_RES_DENY; return MOD_RES_PASSTHRU; } - - void OnRequest(Request& request) - { - if (strcmp("GET_USER_CERT", request.id) == 0) - { - UserCertificateRequest& req = static_cast<UserCertificateRequest&>(request); - req.cert = cmd.CertExt.get(req.user); - } - } }; MODULE_INIT(ModuleSSLInfo) - diff --git a/src/modules/m_sslmodes.cpp b/src/modules/m_sslmodes.cpp index c81c74207..dc0063d29 100644 --- a/src/modules/m_sslmodes.cpp +++ b/src/modules/m_sslmodes.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2013 Shawn Smith <shawn@inspircd.org> * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> @@ -22,38 +23,64 @@ #include "inspircd.h" -#include "ssl.h" +#include "modules/ssl.h" -/* $ModDesc: Provides channel mode +z to allow for Secure/SSL only channels */ +enum +{ + // From UnrealIRCd. + ERR_SECUREONLYCHAN = 489, + ERR_ALLMUSTSSL = 490 +}; + +namespace +{ + bool IsSSLUser(UserCertificateAPI& api, User* user) + { + if (!api) + return false; + + ssl_cert* cert = api->GetCertificate(user); + return (cert != NULL); + } +} /** Handle channel mode +z */ class SSLMode : public ModeHandler { + private: + UserCertificateAPI& API; + public: - SSLMode(Module* Creator) : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_CHANNEL) { } + SSLMode(Module* Creator, UserCertificateAPI& api) + : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_CHANNEL) + , API(api) + { + } - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) + ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE { if (adding) { - if (!channel->IsModeSet('z')) + if (!channel->IsModeSet(this)) { if (IS_LOCAL(source)) { - const UserMembList* userlist = channel->GetUsers(); - for(UserMembCIter i = userlist->begin(); i != userlist->end(); i++) + if (!API) + return MODEACTION_DENY; + + const Channel::MemberMap& userlist = channel->GetUsers(); + for (Channel::MemberMap::const_iterator i = userlist.begin(); i != userlist.end(); ++i) { - UserCertificateRequest req(i->first, creator); - req.Send(); - if(!req.cert && !ServerInstance->ULine(i->first->server)) + ssl_cert* cert = API->GetCertificate(i->first); + if (!cert && !i->first->server->IsULine()) { - source->WriteNumeric(ERR_ALLMUSTSSL, "%s %s :all members of the channel must be connected via SSL", source->nick.c_str(), channel->name.c_str()); + source->WriteNumeric(ERR_ALLMUSTSSL, channel->name, "all members of the channel must be connected via SSL"); return MODEACTION_DENY; } } } - channel->SetMode('z',true); + channel->SetMode(this, true); return MODEACTION_ALLOW; } else @@ -63,9 +90,9 @@ class SSLMode : public ModeHandler } else { - if (channel->IsModeSet('z')) + if (channel->IsModeSet(this)) { - channel->SetMode('z',false); + channel->SetMode(this, false); return MODEACTION_ALLOW; } @@ -74,31 +101,72 @@ class SSLMode : public ModeHandler } }; -class ModuleSSLModes : public Module +/** Handle user mode +z +*/ +class SSLModeUser : public ModeHandler { - - SSLMode sslm; + private: + UserCertificateAPI& API; public: - ModuleSSLModes() - : sslm(this) + SSLModeUser(Module* Creator, UserCertificateAPI& api) + : ModeHandler(Creator, "sslqueries", 'z', PARAM_NONE, MODETYPE_USER) + , API(api) { + if (!ServerInstance->Config->ConfValue("sslmodes")->getBool("enableumode")) + DisableAutoRegister(); + } + + ModeAction OnModeChange(User* user, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE + { + if (adding) + { + if (!dest->IsModeSet(this)) + { + if (!IsSSLUser(API, user)) + return MODEACTION_DENY; + + dest->SetMode(this, true); + return MODEACTION_ALLOW; + } + } + else + { + if (dest->IsModeSet(this)) + { + dest->SetMode(this, false); + return MODEACTION_ALLOW; + } + } + + return MODEACTION_DENY; } +}; + +class ModuleSSLModes : public Module +{ + private: + UserCertificateAPI api; + SSLMode sslm; + SSLModeUser sslquery; - void init() + public: + ModuleSSLModes() + : api(this) + , sslm(this, api) + , sslquery(this, api) { - ServerInstance->Modules->AddService(sslm); - Implementation eventlist[] = { I_OnUserPreJoin, I_OnCheckBan, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - ModResult OnUserPreJoin(User* user, Channel* chan, const char* cname, std::string &privs, const std::string &keygiven) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - if(chan && chan->IsModeSet('z')) + if(chan && chan->IsModeSet(sslm)) { - UserCertificateRequest req(user, this); - req.Send(); - if (req.cert) + if (!api) + return MOD_RES_DENY; + + ssl_cert* cert = api->GetCertificate(user); + if (cert) { // Let them in return MOD_RES_PASSTHRU; @@ -106,7 +174,7 @@ class ModuleSSLModes : public Module else { // Deny - user->WriteServ( "489 %s %s :Cannot join channel; SSL users only (+z)", user->nick.c_str(), cname); + user->WriteNumeric(ERR_SECUREONLYCHAN, cname, "Cannot join channel; SSL users only (+z)"); return MOD_RES_DENY; } } @@ -114,33 +182,63 @@ class ModuleSSLModes : public Module return MOD_RES_PASSTHRU; } - ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) + ModResult OnUserPreMessage(User* user, const MessageTarget& msgtarget, MessageDetails& details) CXX11_OVERRIDE { - if ((mask.length() > 2) && (mask[0] == 'z') && (mask[1] == ':')) + if (msgtarget.type != MessageTarget::TYPE_USER) + return MOD_RES_PASSTHRU; + + User* target = msgtarget.Get<User>(); + + /* If one or more of the parties involved is a ulined service, we wont stop it. */ + if (user->server->IsULine() || target->server->IsULine()) + return MOD_RES_PASSTHRU; + + /* If the target is +z */ + if (target->IsModeSet(sslquery)) { - UserCertificateRequest req(user, this); - req.Send(); - if (req.cert && InspIRCd::Match(req.cert->GetFingerprint(), mask.substr(2))) + if (!IsSSLUser(api, user)) + { + /* The sending user is not on an SSL connection */ + user->WriteNumeric(ERR_CANTSENDTOUSER, target->nick, "You are not permitted to send private messages to this user (+z set)"); return MOD_RES_DENY; + } } + /* If the user is +z */ + else if (user->IsModeSet(sslquery)) + { + if (!IsSSLUser(api, target)) + { + user->WriteNumeric(ERR_CANTSENDTOUSER, target->nick, "You must remove usermode 'z' before you are able to send private messages to a non-ssl user."); + return MOD_RES_DENY; + } + } + return MOD_RES_PASSTHRU; } - ~ModuleSSLModes() + ModResult OnCheckBan(User *user, Channel *c, const std::string& mask) CXX11_OVERRIDE { + if ((mask.length() > 2) && (mask[0] == 'z') && (mask[1] == ':')) + { + if (!api) + return MOD_RES_DENY; + + ssl_cert* cert = api->GetCertificate(user); + if (cert && InspIRCd::Match(cert->GetFingerprint(), mask.substr(2))) + return MOD_RES_DENY; + } + return MOD_RES_PASSTHRU; } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('z'); + tokens["EXTBAN"].push_back('z'); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Provides channel mode +z to allow for Secure/SSL only channels", VF_VENDOR); + return Version("Provides user and channel mode +z to allow for SSL-only channels, queries and notices.", VF_VENDOR); } }; - MODULE_INIT(ModuleSSLModes) - diff --git a/src/modules/m_starttls.cpp b/src/modules/m_starttls.cpp new file mode 100644 index 000000000..ea6536ed1 --- /dev/null +++ b/src/modules/m_starttls.cpp @@ -0,0 +1,111 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Adam <Adam@anope.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/ssl.h" +#include "modules/cap.h" + +// From IRCv3 tls-3.1 +enum +{ + RPL_STARTTLS = 670, + ERR_STARTTLS = 691 +}; + +class CommandStartTLS : public SplitCommand +{ + dynamic_reference_nocheck<IOHookProvider>& ssl; + + public: + CommandStartTLS(Module* mod, dynamic_reference_nocheck<IOHookProvider>& s) + : SplitCommand(mod, "STARTTLS") + , ssl(s) + { + works_before_reg = true; + } + + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE + { + if (!ssl) + { + user->WriteNumeric(ERR_STARTTLS, "STARTTLS is not enabled"); + return CMD_FAILURE; + } + + if (user->registered == REG_ALL) + { + user->WriteNumeric(ERR_STARTTLS, "STARTTLS is not permitted after client registration is complete"); + return CMD_FAILURE; + } + + if (user->eh.GetIOHook()) + { + user->WriteNumeric(ERR_STARTTLS, "STARTTLS failure"); + return CMD_FAILURE; + } + + user->WriteNumeric(RPL_STARTTLS, "STARTTLS successful, go ahead with TLS handshake"); + /* We need to flush the write buffer prior to adding the IOHook, + * otherwise we'll be sending this line inside the SSL session - which + * won't start its handshake until the client gets this line. Currently, + * we assume the write will not block here; this is usually safe, as + * STARTTLS is sent very early on in the registration phase, where the + * user hasn't built up much sendq. Handling a blocked write here would + * be very annoying. + */ + user->eh.DoWrite(); + + ssl->OnAccept(&user->eh, NULL, NULL); + + return CMD_SUCCESS; + } +}; + +class ModuleStartTLS : public Module +{ + CommandStartTLS starttls; + Cap::Capability tls; + dynamic_reference_nocheck<IOHookProvider> ssl; + + public: + ModuleStartTLS() + : starttls(this, ssl) + , tls(this, "tls") + , ssl(this, "ssl") + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ConfigTag* conf = ServerInstance->Config->ConfValue("starttls"); + + std::string newprovider = conf->getString("provider"); + if (newprovider.empty()) + ssl.SetProvider("ssl"); + else + ssl.SetProvider("ssl/" + newprovider); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides support for the STARTTLS command", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleStartTLS) diff --git a/src/modules/m_stripcolor.cpp b/src/modules/m_stripcolor.cpp index f1504edaf..3699fc3aa 100644 --- a/src/modules/m_stripcolor.cpp +++ b/src/modules/m_stripcolor.cpp @@ -20,89 +20,75 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel +S mode (strip ansi color) */ - -/** Handles channel mode +S - */ -class ChannelStripColor : public SimpleChannelModeHandler -{ - public: - ChannelStripColor(Module* Creator) : SimpleChannelModeHandler(Creator, "stripcolor", 'S') { } -}; - -/** Handles user mode +S - */ -class UserStripColor : public SimpleUserModeHandler -{ - public: - UserStripColor(Module* Creator) : SimpleUserModeHandler(Creator, "u_stripcolor", 'S') { } -}; - +#include "modules/exemption.h" class ModuleStripColor : public Module { - ChannelStripColor csc; - UserStripColor usc; + CheckExemption::EventProvider exemptionprov; + SimpleChannelModeHandler csc; + SimpleUserModeHandler usc; public: - ModuleStripColor() : csc(this), usc(this) + ModuleStripColor() + : exemptionprov(this) + , csc(this, "stripcolor", 'S') + , usc(this, "u_stripcolor", 'S') { } - void init() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(usc); - ServerInstance->Modules->AddService(csc); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + tokens["EXTBAN"].push_back('S'); } - virtual ~ModuleStripColor() - { - } - - virtual void On005Numeric(std::string &output) - { - ServerInstance->AddExtBanChar('S'); - } - - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; bool active = false; - if (target_type == TYPE_USER) + if (target.type == MessageTarget::TYPE_USER) { - User* t = (User*)dest; - active = t->IsModeSet('S'); + User* t = target.Get<User>(); + active = t->IsModeSet(usc); } - else if (target_type == TYPE_CHANNEL) + else if (target.type == MessageTarget::TYPE_CHANNEL) { - Channel* t = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,t,"stripcolor"); + Channel* t = target.Get<Channel>(); + ModResult res = CheckExemption::Call(exemptionprov, user, t, "stripcolor"); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; - active = !t->GetExtBanStatus(user, 'S').check(!t->IsModeSet('S')); + active = !t->GetExtBanStatus(user, 'S').check(!t->IsModeSet(csc)); } if (active) { - InspIRCd::StripColor(text); + InspIRCd::StripColor(details.text); } return MOD_RES_PASSTHRU; } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + void OnUserPart(Membership* memb, std::string& partmessage, CUList& except_list) CXX11_OVERRIDE { - return OnUserPreMessage(user,dest,target_type,text,status,exempt_list); + User* user = memb->user; + Channel* channel = memb->chan; + + if (!IS_LOCAL(user)) + return; + + if (channel->GetExtBanStatus(user, 'S').check(!user->IsModeSet(csc))) + { + ModResult res = CheckExemption::Call(exemptionprov, user, channel, "stripcolor"); + + if (res != MOD_RES_ALLOW) + InspIRCd::StripColor(partmessage); + } } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel +S mode (strip ansi color)", VF_VENDOR); } diff --git a/src/modules/m_svshold.cpp b/src/modules/m_svshold.cpp index e666b0fe2..e74f3324b 100644 --- a/src/modules/m_svshold.cpp +++ b/src/modules/m_svshold.cpp @@ -22,8 +22,7 @@ #include "inspircd.h" #include "xline.h" - -/* $ModDesc: Implements SVSHOLD. Like Q:Lines, but can only be added/removed by Services. */ +#include "modules/stats.h" namespace { @@ -35,44 +34,38 @@ namespace class SVSHold : public XLine { public: - irc::string nickname; + std::string nickname; SVSHold(time_t s_time, long d, const std::string& src, const std::string& re, const std::string& nick) : XLine(s_time, d, src, re, "SVSHOLD") { - this->nickname = nick.c_str(); - } - - ~SVSHold() - { + this->nickname = nick; } - bool Matches(User *u) + bool Matches(User* u) CXX11_OVERRIDE { if (u->nick == nickname) return true; return false; } - bool Matches(const std::string &s) + bool Matches(const std::string& s) CXX11_OVERRIDE { - if (nickname == s) - return true; - return false; + return InspIRCd::Match(s, nickname); } - void DisplayExpiry() + void DisplayExpiry() CXX11_OVERRIDE { if (!silent) { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired SVSHOLD %s (set by %s %ld seconds ago)", - this->nickname.c_str(), this->source.c_str(), (long int)(ServerInstance->Time() - this->set_time)); + ServerInstance->SNO->WriteToSnoMask('x', "Removing expired SVSHOLD %s (set by %s %ld seconds ago)", + nickname.c_str(), source.c_str(), (long)(ServerInstance->Time() - set_time)); } } - const char* Displayable() + const std::string& Displayable() CXX11_OVERRIDE { - return nickname.c_str(); + return nickname; } }; @@ -85,12 +78,12 @@ class SVSHoldFactory : public XLineFactory /** Generate a shun */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { return new SVSHold(set_time, duration, source, reason, xline_specific_mask); } - bool AutoApplyToUserList(XLine *x) + bool AutoApplyToUserList(XLine* x) CXX11_OVERRIDE { return false; } @@ -104,15 +97,14 @@ class CommandSvshold : public Command CommandSvshold(Module* Creator) : Command(Creator, "SVSHOLD", 1) { flags_needed = 'o'; this->syntax = "<nickname> [<duration> :<reason>]"; - TRANSLATE4(TR_TEXT, TR_TEXT, TR_TEXT, TR_END); } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { /* syntax: svshold nickname time :reason goes here */ /* 'time' is a human-readable timestring, like 2d3h2s. */ - if (!ServerInstance->ULine(user->server)) + if (!user->server->IsULine()) { /* don't allow SVSHOLD from non-ulined clients */ return CMD_FAILURE; @@ -127,7 +119,7 @@ class CommandSvshold : public Command } else { - user->WriteServ("NOTICE %s :*** SVSHOLD %s not found in list, try /stats S.",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** SVSHOLD " + parameters[0] + " not found in list, try /stats S."); } } else @@ -135,8 +127,7 @@ class CommandSvshold : public Command if (parameters.size() < 3) return CMD_FAILURE; - // Adding - XXX todo make this respect <insane> tag perhaps.. - long duration = ServerInstance->Duration(parameters[1]); + unsigned long duration = InspIRCd::Duration(parameters[1]); SVSHold* r = new SVSHold(ServerInstance->Time(), duration, user->nick.c_str(), parameters[2].c_str(), parameters[0].c_str()); if (ServerInstance->XLines->AddLine(r, user)) @@ -151,7 +142,7 @@ class CommandSvshold : public Command else { time_t c_requires_crap = duration + ServerInstance->Time(); - std::string timestr = ServerInstance->TimeString(c_requires_crap); + std::string timestr = InspIRCd::TimeString(c_requires_crap); ServerInstance->SNO->WriteGlobalSno('x', "%s added timed SVSHOLD for %s, expires on %s: %s", user->nick.c_str(), parameters[0].c_str(), timestr.c_str(), parameters[2].c_str()); } } @@ -165,67 +156,65 @@ class CommandSvshold : public Command return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } }; -class ModuleSVSHold : public Module +class ModuleSVSHold : public Module, public Stats::EventListener { CommandSvshold cmd; SVSHoldFactory s; public: - ModuleSVSHold() : cmd(this) + ModuleSVSHold() + : Stats::EventListener(this) + , cmd(this) { } - void init() + void init() CXX11_OVERRIDE { ServerInstance->XLines->RegisterFactory(&s); - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnUserPreNick, I_OnStats, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("svshold"); - silent = tag->getBool("silent"); + silent = tag->getBool("silent", true); } - virtual ModResult OnStats(char symbol, User* user, string_list &out) + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE { - if(symbol != 'S') + if (stats.GetSymbol() != 'S') return MOD_RES_PASSTHRU; - ServerInstance->XLines->InvokeStats("SVSHOLD", 210, user, out); + ServerInstance->XLines->InvokeStats("SVSHOLD", 210, stats); return MOD_RES_DENY; } - virtual ModResult OnUserPreNick(User *user, const std::string &newnick) + ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) CXX11_OVERRIDE { XLine *rl = ServerInstance->XLines->MatchesLine("SVSHOLD", newnick); if (rl) { - user->WriteServ( "432 %s %s :Services reserved nickname: %s", user->nick.c_str(), newnick.c_str(), rl->reason.c_str()); + user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, InspIRCd::Format("Services reserved nickname: %s", rl->reason.c_str())); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - virtual ~ModuleSVSHold() + ~ModuleSVSHold() { ServerInstance->XLines->DelAll("SVSHOLD"); ServerInstance->XLines->UnregisterFactory(&s); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements SVSHOLD. Like Q:Lines, but can only be added/removed by Services.", VF_COMMON | VF_VENDOR); } diff --git a/src/modules/m_swhois.cpp b/src/modules/m_swhois.cpp index b29d268d1..6c420ca3a 100644 --- a/src/modules/m_swhois.cpp +++ b/src/modules/m_swhois.cpp @@ -24,8 +24,13 @@ #include "inspircd.h" +#include "modules/whois.h" -/* $ModDesc: Provides the SWHOIS command which allows setting of arbitrary WHOIS lines */ +enum +{ + // From UnrealIRCd. + RPL_WHOISSPECIAL = 320 +}; /** Handle /SWHOIS */ @@ -35,21 +40,21 @@ class CommandSwhois : public Command LocalIntExt operblock; StringExtItem swhois; CommandSwhois(Module* Creator) - : Command(Creator,"SWHOIS", 2,2) - , operblock("swhois_operblock", Creator) - , swhois("swhois", Creator) + : Command(Creator, "SWHOIS", 2, 2) + , operblock("swhois_operblock", ExtensionItem::EXT_USER, Creator) + , swhois("swhois", ExtensionItem::EXT_USER, Creator) { flags_needed = 'o'; syntax = "<nick> :<swhois>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle(const std::vector<std::string> ¶meters, User* user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* dest = ServerInstance->FindNick(parameters[0]); - if ((!dest) || (IS_SERVER(dest))) // allow setting swhois using SWHOIS before reg + if (!dest) // allow setting swhois using SWHOIS before reg { - user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); return CMD_FAILURE; } @@ -57,11 +62,11 @@ class CommandSwhois : public Command if (text) { // We already had it set... - if (!ServerInstance->ULine(user->server)) + if (!user->server->IsULine()) // Ulines set SWHOISes silently ServerInstance->SNO->WriteGlobalSno('a', "%s used SWHOIS to set %s's extra whois from '%s' to '%s'", user->nick.c_str(), dest->nick.c_str(), text->c_str(), parameters[1].c_str()); } - else if (!ServerInstance->ULine(user->server)) + else if (!user->server->IsULine()) { // Ulines set SWHOISes silently ServerInstance->SNO->WriteGlobalSno('a', "%s used SWHOIS to set %s's extra whois to '%s'", user->nick.c_str(), dest->nick.c_str(), parameters[1].c_str()); @@ -86,34 +91,28 @@ class CommandSwhois : public Command }; -class ModuleSWhois : public Module +class ModuleSWhois : public Module, public Whois::LineEventListener { CommandSwhois cmd; public: - ModuleSWhois() : cmd(this) - { - } - - void init() + ModuleSWhois() + : Whois::LineEventListener(this) + , cmd(this) { - ServiceProvider* providerlist[] = { &cmd, &cmd.operblock, &cmd.swhois }; - ServerInstance->Modules->AddServices(providerlist, sizeof(providerlist)/sizeof(ServiceProvider*)); - Implementation eventlist[] = { I_OnWhoisLine, I_OnPostOper, I_OnPostDeoper, I_OnDecodeMetaData }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } // :kenny.chatspike.net 320 Brain Azhrarn :is getting paid to play games. - ModResult OnWhoisLine(User* user, User* dest, int &numeric, std::string &text) + ModResult OnWhoisLine(Whois::Context& whois, Numeric::Numeric& numeric) CXX11_OVERRIDE { /* We use this and not OnWhois because this triggers for remote, too */ - if (numeric == 312) + if (numeric.GetNumeric() == 312) { /* Insert our numeric before 312 */ - std::string* swhois = cmd.swhois.get(dest); + std::string* swhois = cmd.swhois.get(whois.GetTarget()); if (swhois) { - ServerInstance->SendWhoisLine(user, dest, 320, "%s %s :%s",user->nick.c_str(), dest->nick.c_str(), swhois->c_str()); + whois.SendLine(RPL_WHOISSPECIAL, *swhois); } } @@ -121,7 +120,7 @@ class ModuleSWhois : public Module return MOD_RES_PASSTHRU; } - void OnPostOper(User* user, const std::string &opertype, const std::string &opername) + void OnPostOper(User* user, const std::string &opertype, const std::string &opername) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return; @@ -136,7 +135,7 @@ class ModuleSWhois : public Module ServerInstance->PI->SendMetaData(user, "swhois", swhois); } - void OnPostDeoper(User* user) + void OnPostDeoper(User* user) CXX11_OVERRIDE { std::string* swhois = cmd.swhois.get(user); if (!swhois) @@ -150,20 +149,14 @@ class ModuleSWhois : public Module ServerInstance->PI->SendMetaData(user, "swhois", ""); } - void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string&) + void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string&) CXX11_OVERRIDE { - // XXX: We use a dynamic_cast in m_services_account so I used one - // here but do we actually need it or is static_cast okay? - User* dest = dynamic_cast<User*>(target); + User* dest = static_cast<User*>(target); if (dest && (extname == "swhois")) cmd.operblock.set(dest, 0); } - ~ModuleSWhois() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the SWHOIS command which allows setting of arbitrary WHOIS lines", VF_OPTCOMMON | VF_VENDOR); } diff --git a/src/modules/m_testnet.cpp b/src/modules/m_testnet.cpp deleted file mode 100644 index 401766d8a..000000000 --- a/src/modules/m_testnet.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -/* $ModDesc: Provides a module for testing the server while linked in a network */ - -#include "inspircd.h" - -struct vtbase -{ - virtual void isok(const char* name, int impl, Module* basemod, std::vector<std::string>& allmods) = 0; - virtual ~vtbase() {} -}; - -template<typename T> struct vtable : public vtbase -{ - union u { - T function; - struct v { - size_t delta; - size_t vtoff; - } v; - } u; - vtable(T t) { - u.function = t; - } - /** member function pointer dereference from vtable; depends on the GCC 4.4 ABI (x86_64) */ - template<typename E> void* read(E* obj) - { - if (u.v.delta & 1) - { - uint8_t* optr = reinterpret_cast<uint8_t*>(obj); - optr += u.v.vtoff; - uint8_t* vptr = *reinterpret_cast<uint8_t**>(optr); - vptr += u.v.delta - 1; - return *reinterpret_cast<void**>(vptr); - } - else - return reinterpret_cast<void*>(u.v.delta); - } - void isok(const char* name, int impl, Module* basemod, std::vector<std::string>& allmods) - { - void* base = read(basemod); - for(unsigned int i=0; i < allmods.size(); ++i) - { - Module* mod = ServerInstance->Modules->Find(allmods[i]); - void* fptr = read(mod); - for(EventHandlerIter j = ServerInstance->Modules->EventHandlers[impl].begin(); - j != ServerInstance->Modules->EventHandlers[impl].end(); j++) - { - if (mod == *j) - { - if (fptr == base) - { - ServerInstance->SNO->WriteToSnoMask('a', "Module %s implements %s but uses default function", - mod->ModuleSourceFile.c_str(), name); - } - goto done; - } - } - if (fptr != base) - { - ServerInstance->SNO->WriteToSnoMask('a', "Module %s does not implement %s but overrides function", - mod->ModuleSourceFile.c_str(), name); - } - done:; - } - } -}; - -template<typename T> vtbase* vtinit(T t) -{ - return new vtable<T>(t); -} - -static void checkall(Module* noimpl) -{ - std::vector<std::string> allmods = ServerInstance->Modules->GetAllModuleNames(0); -#define CHK(name) do { \ - vtbase* vt = vtinit(&Module::name); \ - vt->isok(#name, I_ ## name, noimpl, allmods); \ - delete vt; \ -} while (0) - CHK(OnUserConnect); - CHK(OnUserQuit); - CHK(OnUserDisconnect); - CHK(OnUserJoin); - CHK(OnUserPart); - CHK(OnRehash); - CHK(OnSendSnotice); - CHK(OnUserPreJoin); - CHK(OnUserPreKick); - CHK(OnUserKick); - CHK(OnOper); - CHK(OnInfo); - CHK(OnWhois); - CHK(OnUserPreInvite); - CHK(OnUserInvite); - CHK(OnUserPreMessage); - CHK(OnUserPreNotice); - CHK(OnUserPreNick); - CHK(OnUserMessage); - CHK(OnUserNotice); - CHK(OnMode); - CHK(OnGetServerDescription); - CHK(OnSyncUser); - CHK(OnSyncChannel); - CHK(OnDecodeMetaData); - CHK(OnWallops); - CHK(OnAcceptConnection); - CHK(OnChangeHost); - CHK(OnChangeName); - CHK(OnAddLine); - CHK(OnDelLine); - CHK(OnExpireLine); - CHK(OnUserPostNick); - CHK(OnPreMode); - CHK(On005Numeric); - CHK(OnKill); - CHK(OnRemoteKill); - CHK(OnLoadModule); - CHK(OnUnloadModule); - CHK(OnBackgroundTimer); - CHK(OnPreCommand); - CHK(OnCheckReady); - CHK(OnCheckInvite); - CHK(OnRawMode); - CHK(OnCheckKey); - CHK(OnCheckLimit); - CHK(OnCheckBan); - CHK(OnCheckChannelBan); - CHK(OnExtBanCheck); - CHK(OnStats); - CHK(OnChangeLocalUserHost); - CHK(OnPreTopicChange); - CHK(OnPostTopicChange); - CHK(OnEvent); - CHK(OnGlobalOper); - CHK(OnPostConnect); - CHK(OnAddBan); - CHK(OnDelBan); - CHK(OnChangeLocalUserGECOS); - CHK(OnUserRegister); - CHK(OnChannelPreDelete); - CHK(OnChannelDelete); - CHK(OnPostOper); - CHK(OnSyncNetwork); - CHK(OnSetAway); - CHK(OnPostCommand); - CHK(OnPostJoin); - CHK(OnWhoisLine); - CHK(OnBuildNeighborList); - CHK(OnGarbageCollect); - CHK(OnText); - CHK(OnPassCompare); - CHK(OnRunTestSuite); - CHK(OnNamesListItem); - CHK(OnNumeric); - CHK(OnHookIO); - CHK(OnPreRehash); - CHK(OnModuleRehash); - CHK(OnSendWhoLine); - CHK(OnChangeIdent); -} - -class CommandTest : public Command -{ - public: - CommandTest(Module* parent) : Command(parent, "TEST", 1) - { - syntax = "<action> <parameters>"; - } - - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) - { - if (parameters[0] == "flood") - { - unsigned int count = parameters.size() > 1 ? atoi(parameters[1].c_str()) : 100; - std::string line = parameters.size() > 2 ? parameters[2] : ":z.z NOTICE !flood :Flood text"; - for(unsigned int i=0; i < count; i++) - user->Write(line); - } - else if (parameters[0] == "freeze" && IS_LOCAL(user) && parameters.size() > 1) - { - IS_LOCAL(user)->CommandFloodPenalty += atoi(parameters[1].c_str()); - } - else if (parameters[0] == "check") - { - checkall(creator); - ServerInstance->SNO->WriteToSnoMask('a', "Module check complete"); - } - return CMD_SUCCESS; - } -}; - -class ModuleTest : public Module -{ - CommandTest cmd; - public: - ModuleTest() : cmd(this) - { - } - - void init() - { - if (!strstr(ServerInstance->Config->ServerName.c_str(), ".test")) - throw ModuleException("Don't load modules without reading their descriptions!"); - ServerInstance->Modules->AddService(cmd); - } - - Version GetVersion() - { - return Version("Provides a module for testing the server while linked in a network", VF_VENDOR|VF_OPTCOMMON); - } -}; - -MODULE_INIT(ModuleTest) - diff --git a/src/modules/m_timedbans.cpp b/src/modules/m_timedbans.cpp index bbbc518bd..058028f61 100644 --- a/src/modules/m_timedbans.cpp +++ b/src/modules/m_timedbans.cpp @@ -20,16 +20,14 @@ */ -/* $ModDesc: Adds timed bans */ - #include "inspircd.h" +#include "listmode.h" /** Holds a timed ban */ class TimedBan { public: - std::string channel; std::string mask; time_t expire; Channel* chan; @@ -42,76 +40,79 @@ timedbans TimedBanList; */ class CommandTban : public Command { - static bool IsBanSet(Channel* chan, const std::string& mask) + ChanModeReference banmode; + + bool IsBanSet(Channel* chan, const std::string& mask) { - for (BanList::const_iterator i = chan->bans.begin(); i != chan->bans.end(); ++i) + ListModeBase* banlm = static_cast<ListModeBase*>(*banmode); + if (!banlm) + return false; + const ListModeBase::ModeList* bans = banlm->GetList(chan); + if (bans) { - if (!strcasecmp(i->data.c_str(), mask.c_str())) - return true; + for (ListModeBase::ModeList::const_iterator i = bans->begin(); i != bans->end(); ++i) + { + const ListModeBase::ListItem& ban = *i; + if (!strcasecmp(ban.mask.c_str(), mask.c_str())) + return true; + } } + return false; } public: CommandTban(Module* Creator) : Command(Creator,"TBAN", 3) + , banmode(Creator, "ban") { syntax = "<channel> <duration> <banmask>"; - TRANSLATE4(TR_TEXT, TR_TEXT, TR_TEXT, TR_END); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { Channel* channel = ServerInstance->FindChan(parameters[0]); if (!channel) { - user->WriteNumeric(401, "%s %s :No such channel",user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchChannel(parameters[0])); return CMD_FAILURE; } - int cm = channel->GetPrefixValue(user); + unsigned int cm = channel->GetPrefixValue(user); if (cm < HALFOP_VALUE) { - user->WriteNumeric(482, "%s %s :You do not have permission to set bans on this channel", - user->nick.c_str(), channel->name.c_str()); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, channel->name, "You do not have permission to set bans on this channel"); return CMD_FAILURE; - } + } TimedBan T; - std::string channelname = parameters[0]; - long duration = ServerInstance->Duration(parameters[1]); + unsigned long duration = InspIRCd::Duration(parameters[1]); unsigned long expire = duration + ServerInstance->Time(); if (duration < 1) { - user->WriteServ("NOTICE "+user->nick+" :Invalid ban time"); + user->WriteNotice("Invalid ban time"); return CMD_FAILURE; } std::string mask = parameters[2]; - std::vector<std::string> setban; - setban.push_back(parameters[0]); - setban.push_back("+b"); bool isextban = ((mask.size() > 2) && (mask[1] == ':')); - if (!isextban && !ServerInstance->IsValidMask(mask)) + if (!isextban && !InspIRCd::IsValidMask(mask)) mask.append("!*@*"); - if ((mask.length() > 250) || (!ServerInstance->IsValidMask(mask) && !isextban)) - { - user->WriteServ("NOTICE "+user->nick+" :Invalid ban mask"); - return CMD_FAILURE; - } if (IsBanSet(channel, mask)) { - user->WriteServ("NOTICE %s :Ban already set", user->nick.c_str()); + user->WriteNotice("Ban already set"); return CMD_FAILURE; } - setban.push_back(mask); - // use CallHandler to make it so that the user sets the mode - // themselves - ServerInstance->Parser->CallHandler("MODE",setban,user); - if (!IsBanSet(channel, mask)) + Modes::ChangeList setban; + setban.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), mask); + // Pass the user (instead of ServerInstance->FakeClient) to ModeHandler::Process() to + // make it so that the user sets the mode themselves + ServerInstance->Modes->Process(user, channel, NULL, setban); + if (ServerInstance->Modes->GetLastChangeList().empty()) + { + user->WriteNotice("Invalid ban mask"); return CMD_FAILURE; + } - CUList tmp; - T.channel = channelname; T.mask = mask; T.expire = expire + (IS_REMOTE(user) ? 5 : 0); T.chan = channel; @@ -119,20 +120,49 @@ class CommandTban : public Command const std::string addban = user->nick + " added a timed ban on " + mask + " lasting for " + ConvToStr(duration) + " seconds."; // If halfop is loaded, send notice to halfops and above, otherwise send to ops and above - ModeHandler* mh = ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL); + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h'); char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@'; - channel->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, tmp, "NOTICE %s :%s", channel->name.c_str(), addban.c_str()); + ClientProtocol::Messages::Privmsg notice(ServerInstance->FakeClient, channel, addban, MSG_NOTICE); + channel->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar); ServerInstance->PI->SendChannelNotice(channel, pfxchar, addban); return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } }; +class BanWatcher : public ModeWatcher +{ + public: + BanWatcher(Module* parent) + : ModeWatcher(parent, "ban", MODETYPE_CHANNEL) + { + } + + void AfterMode(User* source, User* dest, Channel* chan, const std::string& banmask, bool adding) CXX11_OVERRIDE + { + if (adding) + return; + + for (timedbans::iterator i = TimedBanList.begin(); i != TimedBanList.end(); ++i) + { + if (i->chan != chan) + continue; + + const std::string& target = i->mask; + if (irc::equals(banmask, target)) + { + TimedBanList.erase(i); + break; + } + } + } +}; + class ChannelMatcher { Channel* const chan; @@ -152,37 +182,16 @@ class ChannelMatcher class ModuleTimedBans : public Module { CommandTban cmd; + BanWatcher banwatcher; + public: ModuleTimedBans() : cmd(this) + , banwatcher(this) { } - void init() - { - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_OnDelBan, I_OnBackgroundTimer, I_OnChannelDelete }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ModResult OnDelBan(User* source, Channel* chan, const std::string &banmask) - { - irc::string listitem = banmask.c_str(); - irc::string thischan = chan->name.c_str(); - for (timedbans::iterator i = TimedBanList.begin(); i != TimedBanList.end(); i++) - { - irc::string target = i->mask.c_str(); - irc::string tchan = i->channel.c_str(); - if ((listitem == target) && (tchan == thischan)) - { - TimedBanList.erase(i); - break; - } - } - return MOD_RES_PASSTHRU; - } - - virtual void OnBackgroundTimer(time_t curtime) + void OnBackgroundTimer(time_t curtime) CXX11_OVERRIDE { timedbans expired; for (timedbans::iterator i = TimedBanList.begin(); i != TimedBanList.end();) @@ -198,41 +207,35 @@ class ModuleTimedBans : public Module for (timedbans::iterator i = expired.begin(); i != expired.end(); i++) { - std::string chan = i->channel; std::string mask = i->mask; - Channel* cr = ServerInstance->FindChan(chan); - if (cr) + Channel* cr = i->chan; { - std::vector<std::string> setban; - setban.push_back(chan); - setban.push_back("-b"); - setban.push_back(mask); - - CUList empty; - const std::string expiry = "*** Timed ban on " + chan + " expired."; + const std::string expiry = "*** Timed ban on " + cr->name + " expired."; // If halfop is loaded, send notice to halfops and above, otherwise send to ops and above - ModeHandler* mh = ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL); + PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h'); char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@'; - cr->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, empty, "NOTICE %s :%s", cr->name.c_str(), expiry.c_str()); + ClientProtocol::Messages::Privmsg notice(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, cr, expiry, MSG_NOTICE); + cr->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar); ServerInstance->PI->SendChannelNotice(cr, pfxchar, expiry); - ServerInstance->SendGlobalMode(setban, ServerInstance->FakeClient); + Modes::ChangeList setban; + setban.push_remove(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), mask); + ServerInstance->Modes->Process(ServerInstance->FakeClient, cr, NULL, setban); } } } - void OnChannelDelete(Channel* chan) + void OnChannelDelete(Channel* chan) CXX11_OVERRIDE { // Remove all timed bans affecting the channel from internal bookkeeping TimedBanList.erase(std::remove_if(TimedBanList.begin(), TimedBanList.end(), ChannelMatcher(chan)), TimedBanList.end()); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Adds timed bans", VF_COMMON | VF_VENDOR); } }; MODULE_INIT(ModuleTimedBans) - diff --git a/src/modules/m_tline.cpp b/src/modules/m_tline.cpp index b4e7e5a99..692472e11 100644 --- a/src/modules/m_tline.cpp +++ b/src/modules/m_tline.cpp @@ -20,8 +20,6 @@ #include "inspircd.h" -/* $ModDesc: Provides /tline command used to test who a mask matches */ - /** Handle /TLINE */ class CommandTline : public Command @@ -32,16 +30,15 @@ class CommandTline : public Command flags_needed = 'o'; this->syntax = "<mask>"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - float n_counted = 0; - float n_matched = 0; - float n_match_host = 0; - float n_match_ip = 0; + unsigned int n_matched = 0; + unsigned int n_match_host = 0; + unsigned int n_match_ip = 0; - for (user_hash::const_iterator u = ServerInstance->Users->clientlist->begin(); u != ServerInstance->Users->clientlist->end(); u++) + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator u = users.begin(); u != users.end(); ++u) { - n_counted++; if (InspIRCd::Match(u->second->GetFullRealHost(),parameters[0])) { n_matched++; @@ -57,10 +54,15 @@ class CommandTline : public Command } } } + + unsigned long n_counted = users.size(); if (n_matched) - user->WriteServ( "NOTICE %s :*** TLINE: Counted %0.0f user(s). Matched '%s' against %0.0f user(s) (%0.2f%% of the userbase). %0.0f by hostname and %0.0f by IP address.",user->nick.c_str(), n_counted, parameters[0].c_str(), n_matched, (n_matched/n_counted)*100, n_match_host, n_match_ip); + { + float p = (n_matched / (float)n_counted) * 100; + user->WriteNotice(InspIRCd::Format("*** TLINE: Counted %lu user(s). Matched '%s' against %u user(s) (%0.2f%% of the userbase). %u by hostname and %u by IP address.", n_counted, parameters[0].c_str(), n_matched, p, n_match_host, n_match_ip)); + } else - user->WriteServ( "NOTICE %s :*** TLINE: Counted %0.0f user(s). Matched '%s' against no user(s).", user->nick.c_str(), n_counted, parameters[0].c_str()); + user->WriteNotice(InspIRCd::Format("*** TLINE: Counted %lu user(s). Matched '%s' against no user(s).", n_counted, parameters[0].c_str())); return CMD_SUCCESS; } @@ -75,20 +77,10 @@ class ModuleTLine : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleTLine() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides /tline command used to test who a mask matches", VF_VENDOR); } }; MODULE_INIT(ModuleTLine) - diff --git a/src/modules/m_topiclock.cpp b/src/modules/m_topiclock.cpp index 3e8a846e7..c65f27668 100644 --- a/src/modules/m_topiclock.cpp +++ b/src/modules/m_topiclock.cpp @@ -16,10 +16,14 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* $ModDesc: Implements server-side topic locks and the server-to-server command SVSTOPIC */ - #include "inspircd.h" +enum +{ + // InspIRCd-specific. + ERR_TOPICLOCK = 744 +}; + class CommandSVSTOPIC : public Command { public: @@ -29,9 +33,9 @@ class CommandSVSTOPIC : public Command flags_needed = FLAG_SERVERONLY; } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - if (!ServerInstance->ULine(user->server)) + if (!user->server->IsULine()) { // Ulines only return CMD_FAILURE; @@ -47,42 +51,23 @@ class CommandSVSTOPIC : public Command time_t topicts = ConvToInt(parameters[1]); if (!topicts) { - ServerInstance->Logs->Log("m_topiclock", DEFAULT, "Received SVSTOPIC with a 0 topicts, dropped."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Received SVSTOPIC with a 0 topicts, dropped."); return CMD_INVALID; } - std::string newtopic; - newtopic.assign(parameters[3], 0, ServerInstance->Config->Limits.MaxTopic); - bool topics_differ = (chan->topic != newtopic); - if ((topics_differ) || (chan->topicset != topicts) || (chan->setby != parameters[2])) - { - // Update when any parameter differs - chan->topicset = topicts; - chan->setby.assign(parameters[2], 0, 127); - chan->topic = newtopic; - // Send TOPIC to clients only if the actual topic has changed, be silent otherwise - if (topics_differ) - chan->WriteChannel(user, "TOPIC %s :%s", chan->name.c_str(), chan->topic.c_str()); - } + chan->SetTopic(user, parameters[3], topicts, ¶meters[2]); } else { // 1 parameter version, nuke the topic - bool topic_empty = chan->topic.empty(); - if (!topic_empty || !chan->setby.empty()) - { - chan->topicset = 0; - chan->setby.clear(); - chan->topic.clear(); - if (!topic_empty) - chan->WriteChannel(user, "TOPIC %s :", chan->name.c_str()); - } + chan->SetTopic(user, std::string(), 0); + chan->setby.clear(); } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { return ROUTE_BROADCAST; } @@ -92,11 +77,7 @@ class FlagExtItem : public ExtensionItem { public: FlagExtItem(const std::string& key, Module* owner) - : ExtensionItem(key, owner) - { - } - - virtual ~FlagExtItem() + : ExtensionItem(key, ExtensionItem::EXT_CHANNEL, owner) { } @@ -105,7 +86,7 @@ class FlagExtItem : public ExtensionItem return (get_raw(container) != NULL); } - std::string serialize(SerializeFormat format, const Extensible* container, void* item) const + std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE { if (format == FORMAT_USER) return "true"; @@ -113,7 +94,7 @@ class FlagExtItem : public ExtensionItem return "1"; } - void unserialize(SerializeFormat format, Extensible* container, const std::string& value) + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) CXX11_OVERRIDE { if (value == "1") set_raw(container, this); @@ -134,7 +115,7 @@ class FlagExtItem : public ExtensionItem unset_raw(container); } - void free(void* item) + void free(Extensible* container, void* item) CXX11_OVERRIDE { // nothing to free } @@ -151,26 +132,19 @@ class ModuleTopicLock : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(topiclock); - ServerInstance->Modules->Attach(I_OnPreTopicChange, this); - } - - ModResult OnPreTopicChange(User* user, Channel* chan, const std::string &topic) + ModResult OnPreTopicChange(User* user, Channel* chan, const std::string &topic) CXX11_OVERRIDE { // Only fired for local users currently, but added a check anyway if ((IS_LOCAL(user)) && (topiclock.get(chan))) { - user->WriteNumeric(744, "%s :TOPIC cannot be changed due to topic lock being active on the channel", chan->name.c_str()); + user->WriteNumeric(ERR_TOPICLOCK, chan->name, "TOPIC cannot be changed due to topic lock being active on the channel"); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Implements server-side topic locks and the server-to-server command SVSTOPIC", VF_COMMON | VF_VENDOR); } diff --git a/src/modules/m_uhnames.cpp b/src/modules/m_uhnames.cpp index 2cd090f97..a8814b343 100644 --- a/src/modules/m_uhnames.cpp +++ b/src/modules/m_uhnames.cpp @@ -20,40 +20,28 @@ #include "inspircd.h" -#include "m_cap.h" - -/* $ModDesc: Provides the UHNAMES facility. */ +#include "modules/cap.h" class ModuleUHNames : public Module { - public: - GenericCap cap; + Cap::Capability cap; + public: ModuleUHNames() : cap(this, "userhost-in-names") { } - void init() - { - Implementation eventlist[] = { I_OnEvent, I_OnPreCommand, I_OnNamesListItem, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - ~ModuleUHNames() - { - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the UHNAMES facility.",VF_VENDOR); } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" UHNAMES"); + tokens["UHNAMES"]; } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE { /* We don't actually create a proper command handler class for PROTOCTL, * because other modules might want to have PROTOCTL hooks too. @@ -64,27 +52,19 @@ class ModuleUHNames : public Module { if ((parameters.size()) && (!strcasecmp(parameters[0].c_str(),"UHNAMES"))) { - cap.ext.set(user, 1); + cap.set(user, true); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } - void OnNamesListItem(User* issuer, Membership* memb, std::string &prefixes, std::string &nick) + ModResult OnNamesListItem(User* issuer, Membership* memb, std::string& prefixes, std::string& nick) CXX11_OVERRIDE { - if (!cap.ext.get(issuer)) - return; + if (cap.get(issuer)) + nick = memb->user->GetFullHost(); - if (nick.empty()) - return; - - nick = memb->user->GetFullHost(); - } - - void OnEvent(Event& ev) - { - cap.HandleEvent(ev); + return MOD_RES_PASSTHRU; } }; diff --git a/src/modules/m_uninvite.cpp b/src/modules/m_uninvite.cpp index ff392edc3..edaabf9fe 100644 --- a/src/modules/m_uninvite.cpp +++ b/src/modules/m_uninvite.cpp @@ -20,22 +20,30 @@ */ -/* $ModDesc: Provides the UNINVITE command which lets users un-invite other users from channels (!) */ - #include "inspircd.h" +#include "modules/invite.h" + +enum +{ + // InspIRCd-specific. + RPL_UNINVITED = 493 +}; /** Handle /UNINVITE */ class CommandUninvite : public Command { + Invite::API invapi; public: - CommandUninvite(Module* Creator) : Command(Creator,"UNINVITE", 2) + CommandUninvite(Module* Creator) + : Command(Creator, "UNINVITE", 2) + , invapi(Creator) { syntax = "<nick> <channel>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + TRANSLATE2(TR_NICK, TR_TEXT); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { User* u; if (IS_LOCAL(user)) @@ -49,11 +57,11 @@ class CommandUninvite : public Command { if (!c) { - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[1].c_str()); + user->WriteNumeric(Numerics::NoSuchChannel(parameters[1])); } else { - user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[0].c_str()); + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); } return CMD_FAILURE; @@ -63,7 +71,7 @@ class CommandUninvite : public Command { if (c->GetPrefixValue(user) < HALFOP_VALUE) { - user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You must be a channel %soperator", user->nick.c_str(), c->name.c_str(), c->GetPrefixValue(u) == HALFOP_VALUE ? "" : "half-"); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, c->name, InspIRCd::Format("You must be a channel %soperator", c->GetPrefixValue(u) == HALFOP_VALUE ? "" : "half-")); return CMD_FAILURE; } } @@ -75,29 +83,35 @@ class CommandUninvite : public Command LocalUser* lu = IS_LOCAL(u); if (lu) { - irc::string xname(c->name.c_str()); - if (!lu->IsInvited(xname)) + // XXX: The source of the numeric we send must be the server of the user doing the /UNINVITE, + // so they don't see where the target user is connected to + if (!invapi->Remove(lu, c)) { - user->SendText(":%s 505 %s %s %s :Is not invited to channel %s", user->server.c_str(), user->nick.c_str(), u->nick.c_str(), c->name.c_str(), c->name.c_str()); + Numeric::Numeric n(505); + n.SetServer(user->server); + n.push(u->nick).push(c->name).push(InspIRCd::Format("Is not invited to channel %s", c->name.c_str())); + user->WriteRemoteNumeric(n); return CMD_FAILURE; } - user->SendText(":%s 494 %s %s %s :Uninvited", user->server.c_str(), user->nick.c_str(), c->name.c_str(), u->nick.c_str()); - lu->RemoveInvite(xname); - lu->WriteNumeric(493, "%s :You were uninvited from %s by %s", u->nick.c_str(), c->name.c_str(), user->nick.c_str()); + Numeric::Numeric n(494); + n.SetServer(user->server); + n.push(c->name).push(u->nick).push("Uninvited"); + user->WriteRemoteNumeric(n); + + lu->WriteNumeric(RPL_UNINVITED, InspIRCd::Format("You were uninvited from %s by %s", c->name.c_str(), user->nick.c_str())); std::string msg = "*** " + user->nick + " uninvited " + u->nick + "."; - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE " + c->name + " :" + msg); + c->WriteNotice(msg); ServerInstance->PI->SendChannelNotice(c, 0, msg); } return CMD_SUCCESS; } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { - User* u = ServerInstance->FindNick(parameters[0]); - return u ? ROUTE_OPT_UCAST(u->server) : ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -111,20 +125,10 @@ class ModuleUninvite : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleUninvite() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides the UNINVITE command which lets users un-invite other users from channels", VF_VENDOR | VF_OPTCOMMON); } }; MODULE_INIT(ModuleUninvite) - diff --git a/src/modules/m_userip.cpp b/src/modules/m_userip.cpp index 9502c91b1..c64f53684 100644 --- a/src/modules/m_userip.cpp +++ b/src/modules/m_userip.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for USERIP command */ - /** Handle /USERIP */ class CommandUserip : public Command @@ -33,14 +31,14 @@ class CommandUserip : public Command syntax = "<nick> [<nick> ...]"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { - std::string retbuf = "340 " + user->nick + " :"; + std::string retbuf; int nicks = 0; bool checked_privs = false; bool has_privs = false; - for (int i = 0; i < (int)parameters.size(); i++) + for (size_t i = 0; i < parameters.size(); i++) { User *u = ServerInstance->FindNickOnly(parameters[i]); if ((u) && (u->registered == REG_ALL)) @@ -54,15 +52,15 @@ class CommandUserip : public Command checked_privs = true; has_privs = user->HasPrivPermission("users/auspex"); if (!has_privs) - user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - You do not have the required operator privileges",user->nick.c_str()); + user->WriteNumeric(ERR_NOPRIVILEGES, "Permission Denied - You do not have the required operator privileges"); } if (!has_privs) continue; } - retbuf = retbuf + u->nick + (IS_OPER(u) ? "*" : "") + "="; - if (IS_AWAY(u)) + retbuf = retbuf + u->nick + (u->IsOper() ? "*" : "") + "="; + if (u->IsAway()) retbuf += "-"; else retbuf += "+"; @@ -72,7 +70,7 @@ class CommandUserip : public Command } if (nicks != 0) - user->WriteServ(retbuf); + user->WriteNumeric(RPL_USERIP, retbuf); return CMD_SUCCESS; } @@ -87,28 +85,15 @@ class ModuleUserIP : public Module { } - void init() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(cmd); - Implementation eventlist[] = { I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + tokens["USERIP"]; } - virtual void On005Numeric(std::string &output) - { - output = output + " USERIP"; - } - - virtual ~ModuleUserIP() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for USERIP command",VF_VENDOR); } - }; MODULE_INIT(ModuleUserIP) - diff --git a/src/modules/m_vhost.cpp b/src/modules/m_vhost.cpp index 31c504af8..01df33cae 100644 --- a/src/modules/m_vhost.cpp +++ b/src/modules/m_vhost.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -/* $ModDesc: Provides masking of user hostnames via traditional /VHOST command */ - /** Handle /VHOST */ class CommandVhost : public Command @@ -34,7 +32,7 @@ class CommandVhost : public Command syntax = "<username> <password>"; } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE { ConfigTagList tags = ServerInstance->Config->ConfTags("vhost"); for(ConfigIter i = tags.first; i != tags.second; ++i) @@ -45,25 +43,24 @@ class CommandVhost : public Command std::string pass = tag->getString("pass"); std::string hash = tag->getString("hash"); - if (parameters[0] == username && !ServerInstance->PassCompare(user, pass, parameters[1], hash)) + if (parameters[0] == username && ServerInstance->PassCompare(user, pass, parameters[1], hash)) { if (!mask.empty()) { - user->WriteServ("NOTICE "+user->nick+" :Setting your VHost: " + mask); - user->ChangeDisplayedHost(mask.c_str()); + user->WriteNotice("Setting your VHost: " + mask); + user->ChangeDisplayedHost(mask); return CMD_SUCCESS; } } } - user->WriteServ("NOTICE "+user->nick+" :Invalid username or password."); + user->WriteNotice("Invalid username or password."); return CMD_FAILURE; } }; class ModuleVHost : public Module { - private: CommandVhost cmd; public: @@ -71,22 +68,10 @@ class ModuleVHost : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleVHost() - { - } - - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides masking of user hostnames via traditional /VHOST command",VF_VENDOR); } - }; MODULE_INIT(ModuleVHost) - diff --git a/src/modules/m_watch.cpp b/src/modules/m_watch.cpp index a86483291..8b84132b2 100644 --- a/src/modules/m_watch.cpp +++ b/src/modules/m_watch.cpp @@ -1,10 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2005-2008 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -21,525 +18,256 @@ #include "inspircd.h" +#include "modules/away.h" -/* $ModDesc: Provides support for the /WATCH command */ +#define INSPIRCD_MONITOR_MANAGER_ONLY +#include "m_monitor.cpp" +enum +{ + RPL_GONEAWAY = 598, + RPL_NOTAWAY = 599, + RPL_LOGON = 600, + RPL_LOGOFF = 601, + RPL_WATCHOFF = 602, + RPL_WATCHSTAT = 603, + RPL_NOWON = 604, + RPL_NOWOFF = 605, + RPL_WATCHLIST = 606, + RPL_ENDOFWATCHLIST = 607, + // RPL_CLEARWATCH = 608, // unused + RPL_NOWISAWAY = 609, + ERR_TOOMANYWATCH = 512, + ERR_INVALIDWATCHNICK = 942 +}; -/* - * Okay, it's nice that this was documented and all, but I at least understood very little - * of it, so I'm going to attempt to explain the data structures in here a bit more. - * - * For efficiency, many data structures are kept. - * - * The first is a global list `watchentries': - * hash_map<irc::string, std::deque<User*> > - * - * That is, if nick 'w00t' is being watched by user pointer 'Brain' and 'Om', <w00t, (Brain, Om)> - * will be in the watchentries list. - * - * The second is that each user has a per-user data structure attached to their user record via Extensible: - * std::map<irc::string, std::string> watchlist; - * So, in the above example with w00t watched by Brain and Om, we'd have: - * Brain- - * `- w00t - * Om- - * `- w00t - * - * Hopefully this helps any brave soul that ventures into this file other than me. :-) - * -- w00t (mar 30, 2008) - */ - - -/* This module has been refactored to provide a very efficient (in terms of cpu time) - * implementation of /WATCH. - * - * To improve the efficiency of watch, many lists are kept. The first primary list is - * a hash_map of who's being watched by who. For example: - * - * KEY: Brain ---> Watched by: Boo, w00t, Om - * KEY: Boo ---> Watched by: Brain, w00t - * - * This is used when we want to tell all the users that are watching someone that - * they are now available or no longer available. For example, if the hash was - * populated as shown above, then when Brain signs on, messages are sent to Boo, w00t - * and Om by reading their 'watched by' list. When this occurs, their online status - * in each of these users lists (see below) is also updated. - * - * Each user also has a seperate (smaller) map attached to their User whilst they - * have any watch entries, which is managed by class Extensible. When they add or remove - * a watch entry from their list, it is inserted here, as well as the main list being - * maintained. This map also contains the user's online status. For users that are - * offline, the key points at an empty string, and for users that are online, the key - * points at a string containing "users-ident users-host users-signon-time". This is - * stored in this manner so that we don't have to FindUser() to fetch this info, the - * users signon can populate the field for us. - * - * For example, going again on the example above, this would be w00t's watchlist: - * - * KEY: Boo ---> Status: "Boo brains.sexy.babe 535342348" - * KEY: Brain ---> Status: "" - * - * In this list we can see that Boo is online, and Brain is offline. We can then - * use this list for 'WATCH L', and 'WATCH S' can be implemented as a combination - * of the above two data structures, with minimum CPU penalty for doing so. - * - * In short, the least efficient this ever gets is O(n), and thats only because - * there are parts that *must* loop (e.g. telling all users that are watching a - * nick that the user online), however this is a *major* improvement over the - * 1.0 implementation, which in places had O(n^n) and worse in it, because this - * implementation scales based upon the sizes of the watch entries, whereas the - * old system would scale (or not as the case may be) according to the total number - * of users using WATCH. - */ - -/* - * Before you start screaming, this definition is only used here, so moving it to a header is pointless. - * Yes, it's horrid. Blame cl for being different. -- w00t - */ - -typedef nspace::hash_map<irc::string, std::deque<User*>, irc::hash> watchentries; -typedef std::map<irc::string, std::string> watchlist; +class CommandWatch : public SplitCommand +{ + // Additional penalty for /WATCH commands that request a list from the server + static const unsigned int ListPenalty = 4000; -/* Who's watching each nickname. - * NOTE: We do NOT iterate this to display a user's WATCH list! - * See the comments above! - */ -watchentries* whos_watching_me; + IRCv3::Monitor::Manager& manager; -class CommandSVSWatch : public Command -{ - public: - CommandSVSWatch(Module* Creator) : Command(Creator,"SVSWATCH", 2) + static void SendOnlineOffline(LocalUser* user, const std::string& nick, bool show_offline = true) { - syntax = "<target> [C|L|S]|[+|-<nick>]"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); /* we watch for a nick. not a UID. */ + User* target = IRCv3::Monitor::Manager::FindNick(nick); + if (target) + { + // The away state should only be sent if the client requests away notifications for a nick but 2.0 always sends them so we do that too + if (target->IsAway()) + user->WriteNumeric(RPL_NOWISAWAY, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->awaytime, "is away"); + else + user->WriteNumeric(RPL_NOWON, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->age, "is online"); + } + else if (show_offline) + user->WriteNumeric(RPL_NOWOFF, nick, "*", "*", "0", "is offline"); } - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) + void HandlePlus(LocalUser* user, const std::string& nick) { - if (!ServerInstance->ULine(user->server)) - return CMD_FAILURE; - - User *u = ServerInstance->FindNick(parameters[0]); - if (!u) - return CMD_FAILURE; - - if (IS_LOCAL(u)) + IRCv3::Monitor::Manager::WatchResult result = manager.Watch(user, nick, maxwatch); + if (result == IRCv3::Monitor::Manager::WR_TOOMANY) + { + // List is full, send error numeric + user->WriteNumeric(ERR_TOOMANYWATCH, nick, "Too many WATCH entries"); + return; + } + else if (result == IRCv3::Monitor::Manager::WR_INVALIDNICK) { - ServerInstance->Parser->CallHandler("WATCH", parameters, u); + user->WriteNumeric(ERR_INVALIDWATCHNICK, nick, "Invalid nickname"); + return; } + else if (result != IRCv3::Monitor::Manager::WR_OK) + return; - return CMD_SUCCESS; + SendOnlineOffline(user, nick); } - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + void HandleMinus(LocalUser* user, const std::string& nick) { - User* target = ServerInstance->FindNick(parameters[0]); + if (!manager.Unwatch(user, nick)) + return; + + User* target = IRCv3::Monitor::Manager::FindNick(nick); if (target) - return ROUTE_OPT_UCAST(target->server); - return ROUTE_LOCALONLY; + user->WriteNumeric(RPL_WATCHOFF, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->age, "stopped watching"); + else + user->WriteNumeric(RPL_WATCHOFF, nick, "*", "*", "0", "stopped watching"); } -}; -/** Handle /WATCH - */ -class CommandWatch : public Command -{ - unsigned int& MAX_WATCH; - public: - SimpleExtItem<watchlist> ext; - CmdResult remove_watch(User* user, const char* nick) + void HandleList(LocalUser* user, bool show_offline) { - // removing an item from the list - if (!ServerInstance->IsNick(nick, ServerInstance->Config->Limits.NickMax)) + user->CommandFloodPenalty += ListPenalty; + const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user); + for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i) { - user->WriteNumeric(942, "%s %s :Invalid nickname", user->nick.c_str(), nick); - return CMD_FAILURE; + const IRCv3::Monitor::Entry* entry = *i; + SendOnlineOffline(user, entry->GetNick(), show_offline); } + user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH list"); + } - watchlist* wl = ext.get(user); - if (wl) - { - /* Yup, is on my list */ - watchlist::iterator n = wl->find(nick); - - if (n != wl->end()) - { - if (!n->second.empty()) - user->WriteNumeric(602, "%s %s %s :stopped watching", user->nick.c_str(), n->first.c_str(), n->second.c_str()); - else - user->WriteNumeric(602, "%s %s * * 0 :stopped watching", user->nick.c_str(), nick); + void HandleStats(LocalUser* user) + { + user->CommandFloodPenalty += ListPenalty; - wl->erase(n); - } + // Do not show how many clients are watching this nick, it's pointless + const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user); + user->WriteNumeric(RPL_WATCHSTAT, InspIRCd::Format("You have %lu and are on 0 WATCH entries", (unsigned long)list.size())); - if (wl->empty()) - { - ext.unset(user); - } - - watchentries::iterator x = whos_watching_me->find(nick); - if (x != whos_watching_me->end()) - { - /* People are watching this user, am i one of them? */ - std::deque<User*>::iterator n2 = std::find(x->second.begin(), x->second.end(), user); - if (n2 != x->second.end()) - /* I'm no longer watching you... */ - x->second.erase(n2); - - if (x->second.empty()) - /* nobody else is, either. */ - whos_watching_me->erase(nick); - } + Numeric::Builder<' '> out(user, RPL_WATCHLIST); + for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i) + { + const IRCv3::Monitor::Entry* entry = *i; + out.Add(entry->GetNick()); } + out.Flush(); + user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH S"); + } - return CMD_SUCCESS; + public: + unsigned int maxwatch; + + CommandWatch(Module* mod, IRCv3::Monitor::Manager& managerref) + : SplitCommand(mod, "WATCH") + , manager(managerref) + { + allow_empty_last_param = false; + syntax = "[<C|L|S|l|+<nick1>|-<nick>>]"; } - CmdResult add_watch(User* user, const char* nick) + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { - if (!ServerInstance->IsNick(nick, ServerInstance->Config->Limits.NickMax)) + if (parameters.empty()) { - user->WriteNumeric(942, "%s %s :Invalid nickname",user->nick.c_str(),nick); - return CMD_FAILURE; + HandleList(user, false); + return CMD_SUCCESS; } - watchlist* wl = ext.get(user); - if (!wl) - { - wl = new watchlist(); - ext.set(user, wl); - } + bool watch_l_done = false; + bool watch_s_done = false; - if (wl->size() >= MAX_WATCH) + for (std::vector<std::string>::const_iterator i = parameters.begin(); i != parameters.end(); ++i) { - user->WriteNumeric(512, "%s %s :Too many WATCH entries", user->nick.c_str(), nick); - return CMD_FAILURE; - } - - watchlist::iterator n = wl->find(nick); - if (n == wl->end()) - { - /* Don't already have the user on my watch list, proceed */ - watchentries::iterator x = whos_watching_me->find(nick); - if (x != whos_watching_me->end()) + const std::string& token = *i; + char subcmd = toupper(token[0]); + if (subcmd == '+') { - /* People are watching this user, add myself */ - x->second.push_back(user); + HandlePlus(user, token.substr(1)); } - else + else if (subcmd == '-') { - std::deque<User*> newlist; - newlist.push_back(user); - (*(whos_watching_me))[nick] = newlist; + HandleMinus(user, token.substr(1)); } - - User* target = ServerInstance->FindNick(nick); - if ((target) && (target->registered == REG_ALL)) + else if (subcmd == 'C') { - (*wl)[nick] = std::string(target->ident).append(" ").append(target->dhost).append(" ").append(ConvToStr(target->age)); - user->WriteNumeric(604, "%s %s %s :is online",user->nick.c_str(), nick, (*wl)[nick].c_str()); - if (IS_AWAY(target)) - { - user->WriteNumeric(609, "%s %s %s %s %lu :is away", user->nick.c_str(), target->nick.c_str(), target->ident.c_str(), target->dhost.c_str(), (unsigned long) target->awaytime); - } + manager.UnwatchAll(user); } - else + else if ((subcmd == 'L') && (!watch_l_done)) { - (*wl)[nick].clear(); - user->WriteNumeric(605, "%s %s * * 0 :is offline",user->nick.c_str(), nick); + watch_l_done = true; + // WATCH L requests a full list with online and offline nicks + // WATCH l requests a list with only online nicks + HandleList(user, (token[0] == 'L')); } - } - - return CMD_SUCCESS; - } - - CommandWatch(Module* parent, unsigned int &maxwatch) : Command(parent,"WATCH", 0), MAX_WATCH(maxwatch), ext("watchlist", parent) - { - syntax = "[C|L|S]|[+|-<nick>]"; - TRANSLATE2(TR_TEXT, TR_END); /* we watch for a nick. not a UID. */ - } - - CmdResult Handle (const std::vector<std::string> ¶meters, User *user) - { - if (parameters.empty()) - { - watchlist* wl = ext.get(user); - if (wl) + else if ((subcmd == 'S') && (!watch_s_done)) { - for (watchlist::iterator q = wl->begin(); q != wl->end(); q++) - { - if (!q->second.empty()) - user->WriteNumeric(604, "%s %s %s :is online", user->nick.c_str(), q->first.c_str(), q->second.c_str()); - } - } - user->WriteNumeric(607, "%s :End of WATCH list",user->nick.c_str()); - } - else if (parameters.size() > 0) - { - for (int x = 0; x < (int)parameters.size(); x++) - { - const char *nick = parameters[x].c_str(); - if (!strcasecmp(nick,"C")) - { - // watch clear - watchlist* wl = ext.get(user); - if (wl) - { - for (watchlist::iterator i = wl->begin(); i != wl->end(); i++) - { - watchentries::iterator i2 = whos_watching_me->find(i->first); - if (i2 != whos_watching_me->end()) - { - /* People are watching this user, am i one of them? */ - std::deque<User*>::iterator n = std::find(i2->second.begin(), i2->second.end(), user); - if (n != i2->second.end()) - /* I'm no longer watching you... */ - i2->second.erase(n); - - if (i2->second.empty()) - /* nobody else is, either. */ - whos_watching_me->erase(i2); - } - } - - ext.unset(user); - } - } - else if (!strcasecmp(nick,"L")) - { - watchlist* wl = ext.get(user); - if (wl) - { - for (watchlist::iterator q = wl->begin(); q != wl->end(); q++) - { - User* targ = ServerInstance->FindNick(q->first.c_str()); - if (targ && !q->second.empty()) - { - user->WriteNumeric(604, "%s %s %s :is online", user->nick.c_str(), q->first.c_str(), q->second.c_str()); - if (IS_AWAY(targ)) - { - user->WriteNumeric(609, "%s %s %s %s %lu :is away", user->nick.c_str(), targ->nick.c_str(), targ->ident.c_str(), targ->dhost.c_str(), (unsigned long) targ->awaytime); - } - } - else - user->WriteNumeric(605, "%s %s * * 0 :is offline", user->nick.c_str(), q->first.c_str()); - } - } - user->WriteNumeric(607, "%s :End of WATCH list",user->nick.c_str()); - } - else if (!strcasecmp(nick,"S")) - { - watchlist* wl = ext.get(user); - int you_have = 0; - int youre_on = 0; - std::string list; - - if (wl) - { - for (watchlist::iterator q = wl->begin(); q != wl->end(); q++) - list.append(q->first.c_str()).append(" "); - you_have = wl->size(); - } - - watchentries::iterator i2 = whos_watching_me->find(user->nick.c_str()); - if (i2 != whos_watching_me->end()) - youre_on = i2->second.size(); - - user->WriteNumeric(603, "%s :You have %d and are on %d WATCH entries", user->nick.c_str(), you_have, youre_on); - user->WriteNumeric(606, "%s :%s",user->nick.c_str(), list.c_str()); - user->WriteNumeric(607, "%s :End of WATCH S",user->nick.c_str()); - } - else if (nick[0] == '-') - { - nick++; - remove_watch(user, nick); - } - else if (nick[0] == '+') - { - nick++; - add_watch(user, nick); - } + watch_s_done = true; + HandleStats(user); } } return CMD_SUCCESS; } }; -class Modulewatch : public Module +class ModuleWatch + : public Module + , public Away::EventListener { - unsigned int maxwatch; - CommandWatch cmdw; - CommandSVSWatch sw; + IRCv3::Monitor::Manager manager; + CommandWatch cmd; - public: - Modulewatch() - : maxwatch(32), cmdw(this, maxwatch), sw(this) + void SendAlert(User* user, const std::string& nick, unsigned int numeric, const char* numerictext, time_t shownts) { - whos_watching_me = new watchentries(); + const IRCv3::Monitor::WatcherList* list = manager.GetWatcherList(nick); + if (!list) + return; + + Numeric::Numeric num(numeric); + num.push(nick).push(user->ident).push(user->GetDisplayedHost()).push(ConvToStr(shownts)).push(numerictext); + for (IRCv3::Monitor::WatcherList::const_iterator i = list->begin(); i != list->end(); ++i) + { + LocalUser* curr = *i; + curr->WriteNumeric(num); + } } - void init() + void Online(User* user) { - OnRehash(NULL); - ServerInstance->Modules->AddService(cmdw); - ServerInstance->Modules->AddService(sw); - ServerInstance->Modules->AddService(cmdw.ext); - Implementation eventlist[] = { I_OnRehash, I_OnGarbageCollect, I_OnUserQuit, I_OnPostConnect, I_OnUserPostNick, I_On005Numeric, I_OnSetAway }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + SendAlert(user, user->nick, RPL_LOGON, "arrived online", user->age); } - virtual void OnRehash(User* user) + void Offline(User* user, const std::string& nick) { - maxwatch = ServerInstance->Config->ConfValue("watch")->getInt("maxentries", 32); - if (!maxwatch) - maxwatch = 32; + SendAlert(user, nick, RPL_LOGOFF, "went offline", user->age); } - virtual ModResult OnSetAway(User *user, const std::string &awaymsg) + public: + ModuleWatch() + : Away::EventListener(this) + , manager(this, "watch") + , cmd(this, manager) { - std::string numeric; - int inum; - - if (awaymsg.empty()) - { - numeric = user->nick + " " + user->ident + " " + user->dhost + " " + ConvToStr(ServerInstance->Time()) + " :is no longer away"; - inum = 599; - } - else - { - numeric = user->nick + " " + user->ident + " " + user->dhost + " " + ConvToStr(ServerInstance->Time()) + " :" + awaymsg; - inum = 598; - } - - watchentries::iterator x = whos_watching_me->find(user->nick.c_str()); - if (x != whos_watching_me->end()) - { - for (std::deque<User*>::iterator n = x->second.begin(); n != x->second.end(); n++) - { - (*n)->WriteNumeric(inum, (*n)->nick + " " + numeric); - } - } - - return MOD_RES_PASSTHRU; } - virtual void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - watchentries::iterator x = whos_watching_me->find(user->nick.c_str()); - if (x != whos_watching_me->end()) - { - for (std::deque<User*>::iterator n = x->second.begin(); n != x->second.end(); n++) - { - (*n)->WriteNumeric(601, "%s %s %s %s %lu :went offline", (*n)->nick.c_str() ,user->nick.c_str(), user->ident.c_str(), user->dhost.c_str(), (unsigned long) ServerInstance->Time()); - - watchlist* wl = cmdw.ext.get(*n); - if (wl) - /* We were on somebody's notify list, set ourselves offline */ - (*wl)[user->nick.c_str()].clear(); - } - } - - /* Now im quitting, if i have a notify list, im no longer watching anyone */ - watchlist* wl = cmdw.ext.get(user); - if (wl) - { - /* Iterate every user on my watch list, and take me out of the whos_watching_me map for each one we're watching */ - for (watchlist::iterator i = wl->begin(); i != wl->end(); i++) - { - watchentries::iterator i2 = whos_watching_me->find(i->first); - if (i2 != whos_watching_me->end()) - { - /* People are watching this user, am i one of them? */ - std::deque<User*>::iterator n = std::find(i2->second.begin(), i2->second.end(), user); - if (n != i2->second.end()) - /* I'm no longer watching you... */ - i2->second.erase(n); - - if (i2->second.empty()) - /* and nobody else is, either. */ - whos_watching_me->erase(i2); - } - } - } + ConfigTag* tag = ServerInstance->Config->ConfValue("watch"); + cmd.maxwatch = tag->getUInt("maxwatch", 30, 1); } - virtual void OnGarbageCollect() + void OnPostConnect(User* user) CXX11_OVERRIDE { - watchentries* old_watch = whos_watching_me; - whos_watching_me = new watchentries(); - - for (watchentries::const_iterator n = old_watch->begin(); n != old_watch->end(); n++) - whos_watching_me->insert(*n); - - delete old_watch; + Online(user); } - virtual void OnPostConnect(User* user) + void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE { - watchentries::iterator x = whos_watching_me->find(user->nick.c_str()); - if (x != whos_watching_me->end()) - { - for (std::deque<User*>::iterator n = x->second.begin(); n != x->second.end(); n++) - { - (*n)->WriteNumeric(600, "%s %s %s %s %lu :arrived online", (*n)->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str(), (unsigned long) user->age); + // Detect and ignore nickname case change + if (ServerInstance->FindNickOnly(oldnick) == user) + return; - watchlist* wl = cmdw.ext.get(*n); - if (wl) - /* We were on somebody's notify list, set ourselves online */ - (*wl)[user->nick.c_str()] = std::string(user->ident).append(" ").append(user->dhost).append(" ").append(ConvToStr(user->age)); - } - } + Offline(user, oldnick); + Online(user); } - virtual void OnUserPostNick(User* user, const std::string &oldnick) + void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE { - watchentries::iterator new_offline = whos_watching_me->find(oldnick.c_str()); - watchentries::iterator new_online = whos_watching_me->find(user->nick.c_str()); - - if (new_offline != whos_watching_me->end()) - { - for (std::deque<User*>::iterator n = new_offline->second.begin(); n != new_offline->second.end(); n++) - { - watchlist* wl = cmdw.ext.get(*n); - if (wl) - { - (*n)->WriteNumeric(601, "%s %s %s %s %lu :went offline", (*n)->nick.c_str(), oldnick.c_str(), user->ident.c_str(), user->dhost.c_str(), (unsigned long) user->age); - (*wl)[oldnick.c_str()].clear(); - } - } - } + LocalUser* localuser = IS_LOCAL(user); + if (localuser) + manager.UnwatchAll(localuser); + Offline(user, user->nick); + } - if (new_online != whos_watching_me->end()) - { - for (std::deque<User*>::iterator n = new_online->second.begin(); n != new_online->second.end(); n++) - { - watchlist* wl = cmdw.ext.get(*n); - if (wl) - { - (*wl)[user->nick.c_str()] = std::string(user->ident).append(" ").append(user->dhost).append(" ").append(ConvToStr(user->age)); - (*n)->WriteNumeric(600, "%s %s %s :arrived online", (*n)->nick.c_str(), user->nick.c_str(), (*wl)[user->nick.c_str()].c_str()); - } - } - } + void OnUserAway(User* user) CXX11_OVERRIDE + { + SendAlert(user, user->nick, RPL_GONEAWAY, user->awaymsg.c_str(), user->awaytime); } - virtual void On005Numeric(std::string &output) + void OnUserBack(User* user) CXX11_OVERRIDE { - // we don't really have a limit... - output = output + " WATCH=" + ConvToStr(maxwatch); + SendAlert(user, user->nick, RPL_NOTAWAY, "is no longer away", ServerInstance->Time()); } - virtual ~Modulewatch() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - delete whos_watching_me; + tokens["WATCH"] = ConvToStr(cmd.maxwatch); } - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Provides support for the /WATCH command", VF_OPTCOMMON | VF_VENDOR); + return Version("Provides WATCH support", VF_VENDOR); } }; -MODULE_INIT(Modulewatch) - +MODULE_INIT(ModuleWatch) diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp new file mode 100644 index 000000000..12102d215 --- /dev/null +++ b/src/modules/m_websocket.cpp @@ -0,0 +1,405 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "iohook.h" +#include "modules/hash.h" + +static const char MagicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +static const char whitespace[] = " \t\r\n"; +static dynamic_reference_nocheck<HashProvider>* sha1; + +class WebSocketHookProvider : public IOHookProvider +{ + public: + WebSocketHookProvider(Module* mod) + : IOHookProvider(mod, "websocket", IOHookProvider::IOH_UNKNOWN, true) + { + } + + void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE; + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + } +}; + +class WebSocketHook : public IOHookMiddle +{ + class HTTPHeaderFinder + { + std::string::size_type bpos; + std::string::size_type len; + + public: + bool Find(const std::string& req, const char* header, std::string::size_type headerlen, std::string::size_type maxpos) + { + std::string::size_type keybegin = req.find(header); + if ((keybegin == std::string::npos) || (keybegin > maxpos) || (keybegin == 0) || (req[keybegin-1] != '\n')) + return false; + + keybegin += headerlen; + + bpos = req.find_first_not_of(whitespace, keybegin, sizeof(whitespace)-1); + if ((bpos == std::string::npos) || (bpos > maxpos)) + return false; + + const std::string::size_type epos = req.find_first_of(whitespace, bpos, sizeof(whitespace)-1); + len = epos - bpos; + + return true; + } + + std::string ExtractValue(const std::string& req) const + { + return std::string(req, bpos, len); + } + }; + + enum OpCode + { + OP_CONTINUATION = 0x00, + OP_TEXT = 0x01, + OP_BINARY = 0x02, + OP_CLOSE = 0x08, + OP_PING = 0x09, + OP_PONG = 0x0a + }; + + enum State + { + STATE_HTTPREQ, + STATE_ESTABLISHED + }; + + static const unsigned char WS_MASKBIT = (1 << 7); + static const unsigned char WS_FINBIT = (1 << 7); + static const unsigned char WS_PAYLOAD_LENGTH_MAGIC_LARGE = 126; + static const unsigned char WS_PAYLOAD_LENGTH_MAGIC_HUGE = 127; + static const size_t WS_MAX_PAYLOAD_LENGTH_SMALL = 125; + static const size_t WS_MAX_PAYLOAD_LENGTH_LARGE = 65535; + static const size_t MAXHEADERSIZE = sizeof(uint64_t) + 2; + + // Clients sending ping or pong frames faster than this are killed + static const time_t MINPINGPONGDELAY = 10; + + State state; + time_t lastpingpong; + + static size_t FillHeader(unsigned char* outbuf, size_t sendlength, OpCode opcode) + { + size_t pos = 0; + outbuf[pos++] = WS_FINBIT | opcode; + + if (sendlength <= WS_MAX_PAYLOAD_LENGTH_SMALL) + { + outbuf[pos++] = sendlength; + } + else if (sendlength <= WS_MAX_PAYLOAD_LENGTH_LARGE) + { + outbuf[pos++] = WS_PAYLOAD_LENGTH_MAGIC_LARGE; + outbuf[pos++] = (sendlength >> 8) & 0xff; + outbuf[pos++] = sendlength & 0xff; + } + else + { + outbuf[pos++] = WS_PAYLOAD_LENGTH_MAGIC_HUGE; + const uint64_t len = sendlength; + for (int i = sizeof(uint64_t)-1; i >= 0; i--) + outbuf[pos++] = ((len >> i*8) & 0xff); + } + + return pos; + } + + static StreamSocket::SendQueue::Element PrepareSendQElem(size_t size, OpCode opcode) + { + unsigned char header[MAXHEADERSIZE]; + const size_t n = FillHeader(header, size, opcode); + + return StreamSocket::SendQueue::Element(reinterpret_cast<const char*>(header), n); + } + + int HandleAppData(StreamSocket* sock, std::string& appdataout, bool allowlarge) + { + std::string& myrecvq = GetRecvQ(); + // Need 1 byte opcode, minimum 1 byte len, 4 bytes masking key + if (myrecvq.length() < 6) + return 0; + + const std::string& cmyrecvq = myrecvq; + unsigned char len1 = (unsigned char)cmyrecvq[1]; + if (!(len1 & WS_MASKBIT)) + { + sock->SetError("WebSocket protocol violation: unmasked client frame"); + return -1; + } + + len1 &= ~WS_MASKBIT; + + // Assume the length is a single byte, if not, update values later + unsigned int len = len1; + unsigned int payloadstartoffset = 6; + const unsigned char* maskkey = reinterpret_cast<const unsigned char*>(&cmyrecvq[2]); + + if (len1 == WS_PAYLOAD_LENGTH_MAGIC_LARGE) + { + // allowlarge is false for control frames according to the RFC meaning large pings, etc. are not allowed + if (!allowlarge) + { + sock->SetError("WebSocket protocol violation: large control frame"); + return -1; + } + + // Large frame, has 2 bytes len after the magic byte indicating the length + // Need 1 byte opcode, 3 bytes len, 4 bytes masking key + if (myrecvq.length() < 8) + return 0; + + unsigned char len2 = (unsigned char)cmyrecvq[2]; + unsigned char len3 = (unsigned char)cmyrecvq[3]; + len = (len2 << 8) | len3; + + if (len <= WS_MAX_PAYLOAD_LENGTH_SMALL) + { + sock->SetError("WebSocket protocol violation: non-minimal length encoding used"); + return -1; + } + + maskkey += 2; + payloadstartoffset += 2; + } + else if (len1 == WS_PAYLOAD_LENGTH_MAGIC_HUGE) + { + sock->SetError("WebSocket: Huge frames are not supported"); + return -1; + } + + if (myrecvq.length() < payloadstartoffset + len) + return 0; + + unsigned int maskkeypos = 0; + const std::string::iterator endit = myrecvq.begin() + payloadstartoffset + len; + for (std::string::const_iterator i = myrecvq.begin() + payloadstartoffset; i != endit; ++i) + { + const unsigned char c = (unsigned char)*i; + appdataout.push_back(c ^ maskkey[maskkeypos++]); + maskkeypos %= 4; + } + + myrecvq.erase(myrecvq.begin(), endit); + return 1; + } + + int HandlePingPongFrame(StreamSocket* sock, bool isping) + { + if (lastpingpong + MINPINGPONGDELAY >= ServerInstance->Time()) + { + sock->SetError("WebSocket: Ping/pong flood"); + return -1; + } + + lastpingpong = ServerInstance->Time(); + + std::string appdata; + const int result = HandleAppData(sock, appdata, false); + // If it's a pong stop here regardless of the result so we won't generate a reply + if ((result <= 0) || (!isping)) + return result; + + StreamSocket::SendQueue::Element elem = PrepareSendQElem(appdata.length(), OP_PONG); + elem.append(appdata); + GetSendQ().push_back(elem); + + SocketEngine::ChangeEventMask(sock, FD_ADD_TRIAL_WRITE); + return 1; + } + + int HandleWS(StreamSocket* sock, std::string& destrecvq) + { + if (GetRecvQ().empty()) + return 0; + + unsigned char opcode = (unsigned char)GetRecvQ().c_str()[0]; + opcode &= ~WS_FINBIT; + + switch (opcode) + { + case OP_CONTINUATION: + case OP_TEXT: + case OP_BINARY: + { + return HandleAppData(sock, destrecvq, true); + } + + case OP_PING: + { + return HandlePingPongFrame(sock, true); + } + + case OP_PONG: + { + // A pong frame may be sent unsolicited, so we have to handle it. + // It may carry application data which we need to remove from the recvq as well. + return HandlePingPongFrame(sock, false); + } + + case OP_CLOSE: + { + sock->SetError("Connection closed"); + return -1; + } + + default: + { + sock->SetError("WebSocket: Invalid opcode"); + return -1; + } + } + } + + void FailHandshake(StreamSocket* sock, const char* httpreply, const char* sockerror) + { + GetSendQ().push_back(StreamSocket::SendQueue::Element(httpreply)); + sock->DoWrite(); + sock->SetError(sockerror); + } + + int HandleHTTPReq(StreamSocket* sock) + { + std::string& recvq = GetRecvQ(); + const std::string::size_type reqend = recvq.find("\r\n\r\n"); + if (reqend == std::string::npos) + return 0; + + HTTPHeaderFinder keyheader; + if (!keyheader.Find(recvq, "Sec-WebSocket-Key:", 18, reqend)) + { + FailHandshake(sock, "HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n", "WebSocket: Received HTTP request which is not a websocket upgrade"); + return -1; + } + + if (!*sha1) + { + FailHandshake(sock, "HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\n\r\n", "WebSocket: SHA-1 provider missing"); + return -1; + } + + state = STATE_ESTABLISHED; + + std::string key = keyheader.ExtractValue(recvq); + key.append(MagicGUID); + + std::string reply = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; + reply.append(BinToBase64((*sha1)->GenerateRaw(key), NULL, '=')).append("\r\n\r\n"); + GetSendQ().push_back(StreamSocket::SendQueue::Element(reply)); + + SocketEngine::ChangeEventMask(sock, FD_ADD_TRIAL_WRITE); + + recvq.erase(0, reqend + 4); + + return 1; + } + + public: + WebSocketHook(IOHookProvider* Prov, StreamSocket* sock) + : IOHookMiddle(Prov) + , state(STATE_HTTPREQ) + , lastpingpong(0) + { + sock->AddIOHook(this); + } + + int OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& uppersendq) CXX11_OVERRIDE + { + StreamSocket::SendQueue& mysendq = GetSendQ(); + + // Return 1 to allow sending back an error HTTP response + if (state != STATE_ESTABLISHED) + return (mysendq.empty() ? 0 : 1); + + if (!uppersendq.empty()) + { + StreamSocket::SendQueue::Element elem = PrepareSendQElem(uppersendq.bytes(), OP_BINARY); + mysendq.push_back(elem); + mysendq.moveall(uppersendq); + } + + return 1; + } + + int OnStreamSocketRead(StreamSocket* sock, std::string& destrecvq) CXX11_OVERRIDE + { + if (state == STATE_HTTPREQ) + { + int httpret = HandleHTTPReq(sock); + if (httpret <= 0) + return httpret; + } + + int wsret; + do + { + wsret = HandleWS(sock, destrecvq); + } + while ((!GetRecvQ().empty()) && (wsret > 0)); + + return wsret; + } + + void OnStreamSocketClose(StreamSocket* sock) CXX11_OVERRIDE + { + } +}; + +void WebSocketHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) +{ + new WebSocketHook(this, sock); +} + +class ModuleWebSocket : public Module +{ + dynamic_reference_nocheck<HashProvider> hash; + reference<WebSocketHookProvider> hookprov; + + public: + ModuleWebSocket() + : hash(this, "hash/sha1") + , hookprov(new WebSocketHookProvider(this)) + { + sha1 = &hash; + } + + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE + { + if (type != ExtensionItem::EXT_USER) + return; + + LocalUser* user = IS_LOCAL(static_cast<User*>(item)); + if ((user) && (user->eh.GetModHook(this))) + ServerInstance->Users.QuitUser(user, "WebSocket module unloading"); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides RFC 6455 WebSocket support", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleWebSocket) diff --git a/src/modules/m_xline_db.cpp b/src/modules/m_xline_db.cpp index fb2a6f65a..90f9de9d2 100644 --- a/src/modules/m_xline_db.cpp +++ b/src/modules/m_xline_db.cpp @@ -20,45 +20,36 @@ #include "inspircd.h" #include "xline.h" - -/* $ModConfig: <xlinedb filename="data/xline.db"> - * Specify the filename for the xline database here*/ -/* $ModDesc: Keeps a dynamic log of all XLines created, and stores them in a seperate conf file (xline.db). */ +#include <fstream> class ModuleXLineDB : public Module { bool dirty; std::string xlinedbpath; public: - void init() + void init() CXX11_OVERRIDE { /* Load the configuration * Note: - * this is on purpose not in the OnRehash() method. It would be non-trivial to change the database on-the-fly. + * This is on purpose not changed on a rehash. It would be non-trivial to change the database on-the-fly. * Imagine a scenario where the new file already exists. Merging the current XLines with the existing database is likely a bad idea * ...and so is discarding all current in-memory XLines for the ones in the database. */ ConfigTag* Conf = ServerInstance->Config->ConfValue("xlinedb"); - xlinedbpath = Conf->getString("filename", DATA_PATH "/xline.db"); + xlinedbpath = ServerInstance->Config->Paths.PrependData(Conf->getString("filename", "xline.db")); // Read xlines before attaching to events ReadDatabase(); - Implementation eventlist[] = { I_OnAddLine, I_OnDelLine, I_OnExpireLine, I_OnBackgroundTimer }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); dirty = false; } - virtual ~ModuleXLineDB() - { - } - /** Called whenever an xline is added by a local user. * This method is triggered after the line is added. * @param source The sender of the line or NULL for local server * @param line The xline being added */ - void OnAddLine(User* source, XLine* line) + void OnAddLine(User* source, XLine* line) CXX11_OVERRIDE { dirty = true; } @@ -68,17 +59,12 @@ class ModuleXLineDB : public Module * @param source The user removing the line or NULL for local server * @param line the line being deleted */ - void OnDelLine(User* source, XLine* line) - { - dirty = true; - } - - void OnExpireLine(XLine *line) + void OnDelLine(User* source, XLine* line) CXX11_OVERRIDE { dirty = true; } - void OnBackgroundTimer(time_t now) + void OnBackgroundTimer(time_t now) CXX11_OVERRIDE { if (dirty) { @@ -89,25 +75,23 @@ class ModuleXLineDB : public Module bool WriteDatabase() { - FILE *f; - /* * We need to perform an atomic write so as not to fuck things up. - * So, let's write to a temporary file, flush and sync the FD, then rename the file.. + * So, let's write to a temporary file, flush it, then rename the file.. * Technically, that means that this can block, but I have *never* seen that. - * -- w00t + * -- w00t */ - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Opening temporary database"); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Opening temporary database"); std::string xlinenewdbpath = xlinedbpath + ".new"; - f = fopen(xlinenewdbpath.c_str(), "w"); - if (!f) + std::ofstream stream(xlinenewdbpath.c_str()); + if (!stream.is_open()) { - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Cannot create database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot create new db: %s (%d)", strerror(errno), errno); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot create database \"%s\"! %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('x', "database: cannot create new xline db \"%s\": %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno); return false; } - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Opened. Writing.."); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Opened. Writing.."); /* * Now, much as I hate writing semi-unportable formats, additional @@ -116,7 +100,7 @@ class ModuleXLineDB : public Module * semblance of backwards compatibility for reading on startup.. * -- w00t */ - fprintf(f, "VERSION 1\n"); + stream << "VERSION 1" << std::endl; // Now, let's write. std::vector<std::string> types = ServerInstance->XLines->GetAllTypes(); @@ -129,22 +113,21 @@ class ModuleXLineDB : public Module for (LookupIter i = lookup->begin(); i != lookup->end(); ++i) { XLine* line = i->second; - fprintf(f, "LINE %s %s %s %lu %lu :%s\n", line->type.c_str(), line->Displayable(), - line->source.c_str(), (unsigned long)line->set_time, (unsigned long)line->duration, line->reason.c_str()); + stream << "LINE " << line->type << " " << line->Displayable() << " " + << line->source << " " << line->set_time << " " + << line->duration << " :" << line->reason << std::endl; } } - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Finished writing XLines. Checking for error.."); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Finished writing XLines. Checking for error.."); - int write_error = 0; - write_error = ferror(f); - write_error |= fclose(f); - if (write_error) + if (stream.fail()) { - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Cannot write to new database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot write to new db: %s (%d)", strerror(errno), errno); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot write to new database \"%s\"! %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('x', "database: cannot write to new xline db \"%s\": %s (%d)", xlinenewdbpath.c_str(), strerror(errno), errno); return false; } + stream.close(); #ifdef _WIN32 remove(xlinedbpath.c_str()); @@ -152,8 +135,8 @@ class ModuleXLineDB : public Module // Use rename to move temporary to new db - this is guarenteed not to fuck up, even in case of a crash. if (rename(xlinenewdbpath.c_str(), xlinedbpath.c_str()) < 0) { - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Cannot move new to old database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot replace old with new db: %s (%d)", strerror(errno), errno); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot replace old database \"%s\" with new database \"%s\"! %s (%d)", xlinedbpath.c_str(), xlinenewdbpath.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('x', "database: cannot replace old xline db \"%s\" with new db \"%s\": %s (%d)", xlinedbpath.c_str(), xlinenewdbpath.c_str(), strerror(errno), errno); return false; } @@ -162,65 +145,42 @@ class ModuleXLineDB : public Module bool ReadDatabase() { - FILE *f; - char linebuf[MAXBUF]; + // If the xline database doesn't exist then we don't need to load it. + if (!FileSystem::FileExists(xlinedbpath)) + return true; - f = fopen(xlinedbpath.c_str(), "r"); - if (!f) + std::ifstream stream(xlinedbpath.c_str()); + if (!stream.is_open()) { - if (errno == ENOENT) - { - /* xline.db doesn't exist, fake good return value (we don't care about this) */ - return true; - } - else - { - /* this might be slightly more problematic. */ - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Cannot read database! %s (%d)", strerror(errno), errno); - ServerInstance->SNO->WriteToSnoMask('a', "database: cannot read db: %s (%d)", strerror(errno), errno); - return false; - } + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cannot read database \"%s\"! %s (%d)", xlinedbpath.c_str(), strerror(errno), errno); + ServerInstance->SNO->WriteToSnoMask('x', "database: cannot read xline db \"%s\": %s (%d)", xlinedbpath.c_str(), strerror(errno), errno); + return false; } - while (fgets(linebuf, MAXBUF, f)) + std::string line; + while (std::getline(stream, line)) { - char *c = linebuf; - - while (c && *c) - { - if (*c == '\n') - { - *c = '\0'; - } - - c++; - } - // Inspired by the command parser. :) - irc::tokenstream tokens(linebuf); + irc::tokenstream tokens(line); int items = 0; std::string command_p[7]; std::string tmp; - while (tokens.GetToken(tmp) && (items < 7)) + while (tokens.GetTrailing(tmp) && (items < 7)) { command_p[items] = tmp; items++; } - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Processing %s", linebuf); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Processing %s", line.c_str()); if (command_p[0] == "VERSION") { - if (command_p[1] == "1") + if (command_p[1] != "1") { - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: Reading db version %s", command_p[1].c_str()); - } - else - { - fclose(f); - ServerInstance->Logs->Log("m_xline_db",DEBUG, "xlinedb: I got database version %s - I don't understand it", command_p[1].c_str()); - ServerInstance->SNO->WriteToSnoMask('a', "database: I got a database version (%s) I don't understand", command_p[1].c_str()); + stream.close(); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "I got database version %s - I don't understand it", command_p[1].c_str()); + ServerInstance->SNO->WriteToSnoMask('x', "database: I got a database version (%s) I don't understand", command_p[1].c_str()); return false; } } @@ -231,7 +191,7 @@ class ModuleXLineDB : public Module if (!xlf) { - ServerInstance->SNO->WriteToSnoMask('a', "database: Unknown line type (%s).", command_p[1].c_str()); + ServerInstance->SNO->WriteToSnoMask('x', "database: Unknown line type (%s).", command_p[1].c_str()); continue; } @@ -246,18 +206,14 @@ class ModuleXLineDB : public Module delete xl; } } - - fclose(f); + stream.close(); return true; } - - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Keeps a dynamic log of all XLines created, and stores them in a separate conf file (xline.db).", VF_VENDOR); } }; MODULE_INIT(ModuleXLineDB) - diff --git a/src/modules/sql.h b/src/modules/sql.h deleted file mode 100644 index 436cd1da8..000000000 --- a/src/modules/sql.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2010 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#ifndef INSPIRCD_SQLAPI_3 -#define INSPIRCD_SQLAPI_3 - -/** Defines the error types which SQLerror may be set to - */ -enum SQLerrorNum { SQL_NO_ERROR, SQL_BAD_DBID, SQL_BAD_CONN, SQL_QSEND_FAIL, SQL_QREPLY_FAIL }; - -/** A list of format parameters for an SQLquery object. - */ -typedef std::vector<std::string> ParamL; - -typedef std::map<std::string, std::string> ParamM; - -class SQLEntry -{ - public: - std::string value; - bool nul; - SQLEntry() : nul(true) {} - SQLEntry(const std::string& v) : value(v), nul(false) {} - inline operator std::string&() { return value; } -}; - -typedef std::vector<SQLEntry> SQLEntries; - -/** - * Result of an SQL query. Only valid inside OnResult - */ -class SQLResult : public classbase -{ - public: - /** - * Return the number of rows in the result. - * - * Note that if you have perfomed an INSERT or UPDATE query or other - * query which will not return rows, this will return the number of - * affected rows. In this case you SHOULD NEVER access any of the result - * set rows, as there aren't any! - * @returns Number of rows in the result set. - */ - virtual int Rows() = 0; - - /** - * Return a single row (result of the query). The internal row counter - * is incremented by one. - * - * @param result Storage for the result data. - * @returns true if there was a row, false if no row exists (end of - * iteration) - */ - virtual bool GetRow(SQLEntries& result) = 0; - - /** Returns column names for the items in this row - */ - virtual void GetCols(std::vector<std::string>& result) = 0; -}; - -/** SQLerror holds the error state of a request. - * The error string varies from database software to database software - * and should be used to display informational error messages to users. - */ -class SQLerror -{ - public: - /** The error id - */ - SQLerrorNum id; - - /** The error string - */ - std::string str; - - /** Initialize an SQLerror - * @param i The error ID to set - * @param s The (optional) error string to set - */ - SQLerror(SQLerrorNum i, const std::string &s = "") - : id(i), str(s) - { - } - - /** Return the error string for an error - */ - const char* Str() - { - if(str.length()) - return str.c_str(); - - switch(id) - { - case SQL_BAD_DBID: - return "Invalid database ID"; - case SQL_BAD_CONN: - return "Invalid connection"; - case SQL_QSEND_FAIL: - return "Sending query failed"; - case SQL_QREPLY_FAIL: - return "Getting query result failed"; - default: - return "Unknown error"; - } - } -}; - -/** - * Object representing an SQL query. This should be allocated on the heap and - * passed to an SQLProvider, which will free it when the query is complete or - * when the querying module is unloaded. - * - * You should store whatever information is needed to have the callbacks work in - * this object (UID of user, channel name, etc). - */ -class SQLQuery : public classbase -{ - public: - ModuleRef creator; - - SQLQuery(Module* Creator) : creator(Creator) {} - virtual ~SQLQuery() {} - - virtual void OnResult(SQLResult& result) = 0; - /** - * Called when the query fails - */ - virtual void OnError(SQLerror& error) { } -}; - -/** - * Provider object for SQL servers - */ -class SQLProvider : public DataProvider -{ - public: - SQLProvider(Module* Creator, const std::string& Name) : DataProvider(Creator, Name) {} - /** Submit an asynchronous SQL request - * @param callback The result reporting point - * @param query The hardcoded query string. If you have parameters to substitute, see below. - */ - virtual void submit(SQLQuery* callback, const std::string& query) = 0; - - /** Submit an asynchronous SQL request - * @param callback The result reporting point - * @param format The simple parameterized query string ('?' parameters) - * @param p Parameters to fill in for the '?' entries - */ - virtual void submit(SQLQuery* callback, const std::string& format, const ParamL& p) = 0; - - /** Submit an asynchronous SQL request. - * @param callback The result reporting point - * @param format The parameterized query string ('$name' parameters) - * @param p Parameters to fill in for the '$name' entries - */ - virtual void submit(SQLQuery* callback, const std::string& format, const ParamM& p) = 0; - - /** Convenience function to prepare a map from a User* */ - void PopulateUserInfo(User* user, ParamM& userinfo) - { - userinfo["nick"] = user->nick; - userinfo["host"] = user->host; - userinfo["ip"] = user->GetIPString(); - userinfo["gecos"] = user->fullname; - userinfo["ident"] = user->ident; - userinfo["server"] = user->server; - userinfo["uuid"] = user->uuid; - } -}; - -#endif diff --git a/src/modules/ssl.h b/src/modules/ssl.h deleted file mode 100644 index 4c877551d..000000000 --- a/src/modules/ssl.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2006 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#ifndef SSL_H -#define SSL_H - -#include <map> -#include <string> - -/** ssl_cert is a class which abstracts SSL certificate - * and key information. - * - * Because gnutls and openssl represent key information in - * wildly different ways, this class allows it to be accessed - * in a unified manner. These classes are attached to ssl- - * connected local users using SSLCertExt - */ -class ssl_cert : public refcountbase -{ - public: - std::string dn; - std::string issuer; - std::string error; - std::string fingerprint; - bool trusted, invalid, unknownsigner, revoked; - - ssl_cert() : trusted(false), invalid(true), unknownsigner(true), revoked(false) {} - - /** Get certificate distinguished name - * @return Certificate DN - */ - const std::string& GetDN() - { - return dn; - } - - /** Get Certificate issuer - * @return Certificate issuer - */ - const std::string& GetIssuer() - { - return issuer; - } - - /** Get error string if an error has occured - * @return The error associated with this users certificate, - * or an empty string if there is no error. - */ - const std::string& GetError() - { - return error; - } - - /** Get key fingerprint. - * @return The key fingerprint as a hex string. - */ - const std::string& GetFingerprint() - { - return fingerprint; - } - - /** Get trust status - * @return True if this is a trusted certificate - * (the certificate chain validates) - */ - bool IsTrusted() - { - return trusted; - } - - /** Get validity status - * @return True if the certificate itself is - * correctly formed. - */ - bool IsInvalid() - { - return invalid; - } - - /** Get signer status - * @return True if the certificate appears to be - * self-signed. - */ - bool IsUnknownSigner() - { - return unknownsigner; - } - - /** Get revokation status. - * @return True if the certificate is revoked. - * Note that this only works properly for GnuTLS - * right now. - */ - bool IsRevoked() - { - return revoked; - } - - bool IsCAVerified() - { - return trusted && !invalid && !revoked && !unknownsigner && error.empty(); - } - - std::string GetMetaLine() - { - std::stringstream value; - bool hasError = !error.empty(); - value << (IsInvalid() ? "v" : "V") << (IsTrusted() ? "T" : "t") << (IsRevoked() ? "R" : "r") - << (IsUnknownSigner() ? "s" : "S") << (hasError ? "E" : "e") << " "; - if (hasError) - value << GetError(); - else - value << GetFingerprint() << " " << GetDN() << " " << GetIssuer(); - return value.str(); - } -}; - -/** Get certificate from a socket (only useful with an SSL module) */ -struct SocketCertificateRequest : public Request -{ - StreamSocket* const sock; - ssl_cert* cert; - - SocketCertificateRequest(StreamSocket* ss, Module* Me) - : Request(Me, ss->GetIOHook(), "GET_SSL_CERT"), sock(ss), cert(NULL) - { - Send(); - } - - std::string GetFingerprint() - { - if (cert) - return cert->GetFingerprint(); - return ""; - } -}; - -/** Get certificate from a user (requires m_sslinfo) */ -struct UserCertificateRequest : public Request -{ - User* const user; - ssl_cert* cert; - - UserCertificateRequest(User* u, Module* Me, Module* info = ServerInstance->Modules->Find("m_sslinfo.so")) - : Request(Me, info, "GET_USER_CERT"), user(u), cert(NULL) - { - Send(); - } - - std::string GetFingerprint() - { - if (cert) - return cert->GetFingerprint(); - return ""; - } -}; - -class SSLRawSessionRequest : public Request -{ - public: - const int fd; - void* data; - - SSLRawSessionRequest(int FD, Module* srcmod, Module* destmod) - : Request(srcmod, destmod, "GET_RAW_SSL_SESSION") - , fd(FD) - , data(NULL) - { - Send(); - } -}; - -#endif diff --git a/src/modules/u_listmode.h b/src/modules/u_listmode.h deleted file mode 100644 index a728eb839..000000000 --- a/src/modules/u_listmode.h +++ /dev/null @@ -1,425 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#ifndef INSPIRCD_LISTMODE_PROVIDER -#define INSPIRCD_LISTMODE_PROVIDER - -/** Get the time as a string - */ -inline std::string stringtime() -{ - std::ostringstream TIME; - TIME << ServerInstance->Time(); - return TIME.str(); -} - -/** An item in a listmode's list - */ -class ListItem -{ -public: - std::string nick; - std::string mask; - std::string time; -}; - -/** The number of items a listmode's list may contain - */ -class ListLimit -{ -public: - std::string mask; - unsigned int limit; -}; - -/** Items stored in the channel's list - */ -typedef std::list<ListItem> modelist; -/** Max items per channel by name - */ -typedef std::list<ListLimit> limitlist; - -/** The base class for list modes, should be inherited. - */ -class ListModeBase : public ModeHandler -{ - protected: - /** Numeric to use when outputting the list - */ - unsigned int listnumeric; - /** Numeric to indicate end of list - */ - unsigned int endoflistnumeric; - /** String to send for end of list - */ - std::string endofliststring; - /** Automatically tidy up entries - */ - bool tidy; - /** Config tag to check for max items per channel - */ - std::string configtag; - /** Limits on a per-channel basis read from the tag - * specified in ListModeBase::configtag - */ - limitlist chanlimits; - - public: - /** Storage key - */ - SimpleExtItem<modelist> extItem; - - /** Constructor. - * @param Instance The creator of this class - * @param modechar Mode character - * @param eolstr End of list string - * @pram lnum List numeric - * @param eolnum End of list numeric - * @param autotidy Automatically tidy list entries on add - * @param ctag Configuration tag to get limits from - */ - ListModeBase(Module* Creator, const std::string& Name, char modechar, const std::string &eolstr, unsigned int lnum, unsigned int eolnum, bool autotidy, const std::string &ctag = "banlist") - : ModeHandler(Creator, Name, modechar, PARAM_ALWAYS, MODETYPE_CHANNEL), - listnumeric(lnum), endoflistnumeric(eolnum), endofliststring(eolstr), tidy(autotidy), - configtag(ctag), extItem("listbase_mode_" + name + "_list", Creator) - { - list = true; - } - - /** See mode.h - */ - std::pair<bool,std::string> ModeSet(User*, User*, Channel* channel, const std::string ¶meter) - { - modelist* el = extItem.get(channel); - if (el) - { - for (modelist::iterator it = el->begin(); it != el->end(); it++) - { - if(parameter == it->mask) - { - return std::make_pair(true, parameter); - } - } - } - return std::make_pair(false, parameter); - } - - /** Display the list for this mode - * @param user The user to send the list to - * @param channel The channel the user is requesting the list for - */ - virtual void DisplayList(User* user, Channel* channel) - { - modelist* el = extItem.get(channel); - if (el) - { - for (modelist::reverse_iterator it = el->rbegin(); it != el->rend(); ++it) - { - user->WriteNumeric(listnumeric, "%s %s %s %s %s", user->nick.c_str(), channel->name.c_str(), it->mask.c_str(), (it->nick.length() ? it->nick.c_str() : ServerInstance->Config->ServerName.c_str()), it->time.c_str()); - } - } - user->WriteNumeric(endoflistnumeric, "%s %s :%s", user->nick.c_str(), channel->name.c_str(), endofliststring.c_str()); - } - - virtual void DisplayEmptyList(User* user, Channel* channel) - { - user->WriteNumeric(endoflistnumeric, "%s %s :%s", user->nick.c_str(), channel->name.c_str(), endofliststring.c_str()); - } - - /** Remove all instances of the mode from a channel. - * See mode.h - * @param channel The channel to remove all instances of the mode from - */ - virtual void RemoveMode(Channel* channel, irc::modestacker* stack) - { - modelist* el = extItem.get(channel); - if (el) - { - irc::modestacker modestack(false); - - for (modelist::iterator it = el->begin(); it != el->end(); it++) - { - if (stack) - stack->Push(this->GetModeChar(), it->mask); - else - modestack.Push(this->GetModeChar(), it->mask); - } - - if (stack) - return; - - std::vector<std::string> stackresult; - stackresult.push_back(channel->name); - while (modestack.GetStackedLine(stackresult)) - { - ServerInstance->SendMode(stackresult, ServerInstance->FakeClient); - stackresult.clear(); - stackresult.push_back(channel->name); - } - } - } - - /** See mode.h - */ - virtual void RemoveMode(User*, irc::modestacker* stack) - { - /* Listmodes dont get set on users */ - } - - /** Perform a rehash of this mode's configuration data - */ - virtual void DoRehash() - { - ConfigTagList tags = ServerInstance->Config->ConfTags(configtag); - - chanlimits.clear(); - - for (ConfigIter i = tags.first; i != tags.second; i++) - { - // For each <banlist> tag - ConfigTag* c = i->second; - ListLimit limit; - limit.mask = c->getString("chan"); - limit.limit = c->getInt("limit"); - - if (limit.mask.size() && limit.limit > 0) - chanlimits.push_back(limit); - } - - // Add the default entry. This is inserted last so if the user specifies a - // wildcard record in the config it will take precedence over this entry. - ListLimit limit; - limit.mask = "*"; - limit.limit = 64; - chanlimits.push_back(limit); - } - - /** Populate the Implements list with the correct events for a List Mode - */ - virtual void DoImplements(Module* m) - { - ServerInstance->Modules->AddService(extItem); - this->DoRehash(); - Implementation eventlist[] = { I_OnSyncChannel, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, m, sizeof(eventlist)/sizeof(Implementation)); - } - - /** Handle the list mode. - * See mode.h - */ - virtual ModeAction OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) - { - // Try and grab the list - modelist* el = extItem.get(channel); - - if (adding) - { - if (tidy) - ModeParser::CleanMask(parameter); - - if (parameter.length() > 250) - return MODEACTION_DENY; - - // If there was no list - if (!el) - { - // Make one - el = new modelist; - extItem.set(channel, el); - } - - // Check if the item already exists in the list - for (modelist::iterator it = el->begin(); it != el->end(); it++) - { - if (parameter == it->mask) - { - /* Give a subclass a chance to error about this */ - TellAlreadyOnList(source, channel, parameter); - - // it does, deny the change - return MODEACTION_DENY; - } - } - - unsigned int maxsize = 0; - - for (limitlist::iterator it = chanlimits.begin(); it != chanlimits.end(); it++) - { - if (InspIRCd::Match(channel->name, it->mask)) - { - // We have a pattern matching the channel... - maxsize = el->size(); - if (!IS_LOCAL(source) || (maxsize < it->limit)) - { - /* Ok, it *could* be allowed, now give someone subclassing us - * a chance to validate the parameter. - * The param is passed by reference, so they can both modify it - * and tell us if we allow it or not. - * - * eg, the subclass could: - * 1) allow - * 2) 'fix' parameter and then allow - * 3) deny - */ - if (ValidateParam(source, channel, parameter)) - { - // And now add the mask onto the list... - ListItem e; - e.mask = parameter; - e.nick = source->nick; - e.time = stringtime(); - - el->push_back(e); - return MODEACTION_ALLOW; - } - else - { - /* If they deny it they have the job of giving an error message */ - return MODEACTION_DENY; - } - } - else - break; - } - } - - /* List is full, give subclass a chance to send a custom message */ - if (!TellListTooLong(source, channel, parameter)) - { - source->WriteNumeric(478, "%s %s %s :Channel ban/ignore list is full", source->nick.c_str(), channel->name.c_str(), parameter.c_str()); - } - - parameter.clear(); - return MODEACTION_DENY; - } - else - { - // We're taking the mode off - if (el) - { - for (modelist::iterator it = el->begin(); it != el->end(); it++) - { - if (parameter == it->mask) - { - el->erase(it); - if (el->empty()) - { - extItem.unset(channel); - } - return MODEACTION_ALLOW; - } - } - /* Tried to remove something that wasn't set */ - TellNotSet(source, channel, parameter); - parameter.clear(); - return MODEACTION_DENY; - } - else - { - /* Hmm, taking an exception off a non-existant list, DIE */ - TellNotSet(source, channel, parameter); - parameter.clear(); - return MODEACTION_DENY; - } - } - return MODEACTION_DENY; - } - - /** Syncronize channel item list with another server. - * See modules.h - * @param chan Channel to syncronize - * @param proto Protocol module pointer - * @param opaque Opaque connection handle - */ - virtual void DoSyncChannel(Channel* chan, Module* proto, void* opaque) - { - modelist* mlist = extItem.get(chan); - irc::modestacker modestack(true); - std::vector<std::string> stackresult; - std::vector<TranslateType> types; - types.push_back(TR_TEXT); - if (mlist) - { - for (modelist::iterator it = mlist->begin(); it != mlist->end(); it++) - { - modestack.Push(std::string(1, mode)[0], it->mask); - } - } - while (modestack.GetStackedLine(stackresult)) - { - types.assign(stackresult.size(), this->GetTranslateType()); - proto->ProtoSendMode(opaque, TYPE_CHANNEL, chan, stackresult, types); - stackresult.clear(); - } - } - - /** Clean up module on unload - * @param target_type Type of target to clean - * @param item Item to clean - */ - virtual void DoCleanup(int, void*) - { - } - - /** Validate parameters. - * Overridden by implementing module. - * @param source Source user adding the parameter - * @param channel Channel the parameter is being added to - * @param parameter The actual parameter being added - * @return true if the parameter is valid - */ - virtual bool ValidateParam(User*, Channel*, std::string&) - { - return true; - } - - /** Tell the user the list is too long. - * Overridden by implementing module. - * @param source Source user adding the parameter - * @param channel Channel the parameter is being added to - * @param parameter The actual parameter being added - * @return Ignored - */ - virtual bool TellListTooLong(User*, Channel*, std::string&) - { - return false; - } - - /** Tell the user an item is already on the list. - * Overridden by implementing module. - * @param source Source user adding the parameter - * @param channel Channel the parameter is being added to - * @param parameter The actual parameter being added - */ - virtual void TellAlreadyOnList(User*, Channel*, std::string&) - { - } - - /** Tell the user that the parameter is not in the list. - * Overridden by implementing module. - * @param source Source user removing the parameter - * @param channel Channel the parameter is being removed from - * @param parameter The actual parameter being removed - */ - virtual void TellNotSet(User*, Channel*, std::string&) - { - } -}; - -#endif diff --git a/src/server.cpp b/src/server.cpp index d05ece8a4..ff8f6211f 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -23,7 +23,6 @@ #include <signal.h> #include "exitcodes.h" #include "inspircd.h" -#include "inspircd_version.h" void InspIRCd::SignalHandler(int signal) { @@ -32,12 +31,13 @@ void InspIRCd::SignalHandler(int signal) #else if (signal == SIGHUP) { - Rehash("Caught SIGHUP"); + ServerInstance->SNO->WriteGlobalSno('a', "Rehashing due to SIGHUP"); + Rehash(); } else if (signal == SIGTERM) #endif { - Exit(signal); + Exit(EXIT_STATUS_SIGTERM); } } @@ -46,55 +46,43 @@ void InspIRCd::Exit(int status) #ifdef _WIN32 SetServiceStopped(status); #endif - this->SendError("Exiting with status " + ConvToStr(status) + " (" + std::string(ExitCodes[status]) + ")"); this->Cleanup(); - delete this; ServerInstance = NULL; + delete this; exit (status); } -void RehashHandler::Call(const std::string &reason) +void InspIRCd::Rehash(const std::string& uuid) { - ServerInstance->SNO->WriteToSnoMask('a', "Rehashing config file %s %s",ServerConfig::CleanFilename(ServerInstance->ConfigFileName.c_str()), reason.c_str()); - ServerInstance->RehashUsersAndChans(); - FOREACH_MOD(I_OnGarbageCollect, OnGarbageCollect()); if (!ServerInstance->ConfigThread) { - ServerInstance->ConfigThread = new ConfigReaderThread(""); - ServerInstance->Threads->Start(ServerInstance->ConfigThread); + ServerInstance->ConfigThread = new ConfigReaderThread(uuid); + ServerInstance->Threads.Start(ServerInstance->ConfigThread); } } -std::string InspIRCd::GetVersionString(bool operstring) +std::string InspIRCd::GetVersionString(bool getFullVersion) { - char versiondata[MAXBUF]; - if (operstring) - { - std::string sename = SE->GetName(); - snprintf(versiondata,MAXBUF,"%s %s :%s [%s,%s,%s]",VERSION, Config->ServerName.c_str(), SYSTEM,REVISION, sename.c_str(), Config->sid.c_str()); - } - else - snprintf(versiondata,MAXBUF,"%s %s :%s",BRANCH,Config->ServerName.c_str(),Config->CustomVersion.c_str()); - return versiondata; + if (getFullVersion) + return INSPIRCD_VERSION ". " + Config->ServerName + " :[" + Config->sid + "] " + Config->CustomVersion; + return INSPIRCD_BRANCH ". " + Config->ServerName + " :" + Config->CustomVersion; } -const char InspIRCd::LogHeader[] = - "Log started for " VERSION " (" REVISION ", " MODULE_INIT_STR ")" - " - compiled on " SYSTEM; - -void InspIRCd::BuildISupport() +std::string UIDGenerator::GenerateSID(const std::string& servername, const std::string& serverdesc) { - // the neatest way to construct the initial 005 numeric, considering the number of configure constants to go in it... - std::stringstream v; - v << "WALLCHOPS WALLVOICES MODES=" << Config->Limits.MaxModes << " CHANTYPES=# PREFIX=" << this->Modes->BuildPrefixes() << " MAP MAXCHANNELS=" << Config->MaxChans << " MAXBANS=60 VBANLIST NICKLEN=" << Config->Limits.NickMax; - v << " CASEMAPPING=rfc1459 STATUSMSG=" << Modes->BuildPrefixes(false) << " CHARSET=ascii TOPICLEN=" << Config->Limits.MaxTopic << " KICKLEN=" << Config->Limits.MaxKick << " MAXTARGETS=" << Config->MaxTargets; - v << " AWAYLEN=" << Config->Limits.MaxAway << " CHANMODES=" << this->Modes->GiveModeList(MASK_CHANNEL) << " FNC NETWORK=" << Config->Network << " MAXPARA=32 ELIST=MU" << " CHANNELLEN=" << Config->Limits.ChanMax; - Config->data005 = v.str(); - FOREACH_MOD(I_On005Numeric,On005Numeric(Config->data005)); - Config->Update005(); + unsigned int sid = 0; + + for (std::string::const_iterator i = servername.begin(); i != servername.end(); ++i) + sid = 5 * sid + *i; + for (std::string::const_iterator i = serverdesc.begin(); i != serverdesc.end(); ++i) + sid = 5 * sid + *i; + + std::string sidstr = ConvToStr(sid % 1000); + sidstr.insert(0, 3 - sidstr.length(), '0'); + return sidstr; } -void InspIRCd::IncrementUID(int pos) +void UIDGenerator::IncrementUID(unsigned int pos) { /* * Okay. The rules for generating a UID go like this... @@ -103,85 +91,155 @@ void InspIRCd::IncrementUID(int pos) * A again, in an iterative fashion.. so.. * AAA9 -> AABA, and so on. -- w00t */ - if ((pos == 3) && (current_uid[3] == '9')) + + // If we hit Z, wrap around to 0. + if (current_uid[pos] == 'Z') + { + current_uid[pos] = '0'; + } + else if (current_uid[pos] == '9') { - // At pos 3, if we hit '9', we've run out of available UIDs, and need to reset to AAA..AAA. - for (int i = 3; i < UUID_LENGTH-1; i++) + /* + * Or, if we hit 9, wrap around to pos = 'A' and (pos - 1)++, + * e.g. A9 -> BA -> BB .. + */ + current_uid[pos] = 'A'; + if (pos == 3) { - current_uid[i] = 'A'; + // At pos 3, if we hit '9', we've run out of available UIDs, and reset to AAA..AAA. + return; } + this->IncrementUID(pos - 1); } else { - // If we hit Z, wrap around to 0. - if (current_uid[pos] == 'Z') - { - current_uid[pos] = '0'; - } - else if (current_uid[pos] == '9') - { - /* - * Or, if we hit 9, wrap around to pos = 'A' and (pos - 1)++, - * e.g. A9 -> BA -> BB .. - */ - current_uid[pos] = 'A'; - this->IncrementUID(pos - 1); - } - else - { - // Anything else, nobody gives a shit. Just increment. - current_uid[pos]++; - } + // Anything else, nobody gives a shit. Just increment. + current_uid[pos]++; } } -/* - * Retrieve the next valid UUID that is free for this server. - */ -std::string InspIRCd::GetUID() +void UIDGenerator::init(const std::string& sid) { - static bool inited = false; - /* - * If we're setting up, copy SID into the first three digits, 9's to the rest, null term at the end + * Copy SID into the first three digits, 9's to the rest, null term at the end * Why 9? Well, we increment before we find, otherwise we have an unnecessary copy, and I want UID to start at AAA..AA * and not AA..AB. So by initialising to 99999, we force it to rollover to AAAAA on the first IncrementUID call. * Kind of silly, but I like how it looks. * -- w */ - if (!inited) - { - inited = true; - current_uid[0] = Config->sid[0]; - current_uid[1] = Config->sid[1]; - current_uid[2] = Config->sid[2]; - for (int i = 3; i < (UUID_LENGTH - 1); i++) - current_uid[i] = '9'; - - // Null terminator. Important. - current_uid[UUID_LENGTH - 1] = '\0'; - } + current_uid.resize(UUID_LENGTH, '9'); + current_uid[0] = sid[0]; + current_uid[1] = sid[1]; + current_uid[2] = sid[2]; +} +/* + * Retrieve the next valid UUID that is free for this server. + */ +std::string UIDGenerator::GetUID() +{ while (1) { // Add one to the last UID - this->IncrementUID(UUID_LENGTH - 2); + this->IncrementUID(UUID_LENGTH - 1); - if (this->FindUUID(current_uid)) - { - /* - * It's in use. We need to try the loop again. - */ - continue; - } + if (!ServerInstance->FindUUID(current_uid)) + break; - return current_uid; + /* + * It's in use. We need to try the loop again. + */ } - /* not reached. */ - return ""; + return current_uid; } +void ISupportManager::AppendValue(std::string& buffer, const std::string& value) +{ + // If this token has no value then we have nothing to do. + if (value.empty()) + return; + + // This function implements value escaping according to the rules of the ISUPPORT draft: + // https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03 + buffer.push_back('='); + for (std::string::const_iterator iter = value.begin(); iter != value.end(); ++iter) + { + // The value must be escaped if: + // (1) It is a banned character in an IRC <middle> parameter (NUL, LF, CR, SPACE). + // (2) It has special meaning within an ISUPPORT token (EQUALS, BACKSLASH). + if (*iter == '\0' || *iter == '\n' || *iter == '\r' || *iter == ' ' || *iter == '=' || *iter == '\\') + buffer.append(InspIRCd::Format("\\x%X", *iter)); + else + buffer.push_back(*iter); + } +} + +void ISupportManager::Build() +{ + /** + * This is currently the neatest way we can build the initial ISUPPORT map. In + * the future we can use an initializer list here. + */ + std::map<std::string, std::string> tokens; + + tokens["AWAYLEN"] = ConvToStr(ServerInstance->Config->Limits.MaxAway); + tokens["CASEMAPPING"] = ServerInstance->Config->CaseMapping; + tokens["CHANLIMIT"] = InspIRCd::Format("#:%u", ServerInstance->Config->MaxChans); + tokens["CHANMODES"] = ServerInstance->Modes->GiveModeList(MODETYPE_CHANNEL); + tokens["CHANNELLEN"] = ConvToStr(ServerInstance->Config->Limits.ChanMax); + tokens["CHANTYPES"] = "#"; + tokens["HOSTLEN"] = ConvToStr(ServerInstance->Config->Limits.MaxHost); + tokens["KICKLEN"] = ConvToStr(ServerInstance->Config->Limits.MaxKick); + tokens["LINELEN"] = ConvToStr(ServerInstance->Config->Limits.MaxLine); + tokens["MAXTARGETS"] = ConvToStr(ServerInstance->Config->MaxTargets); + tokens["MODES"] = ConvToStr(ServerInstance->Config->Limits.MaxModes); + tokens["NETWORK"] = ServerInstance->Config->Network; + tokens["NICKLEN"] = ConvToStr(ServerInstance->Config->Limits.NickMax); + tokens["PREFIX"] = ServerInstance->Modes->BuildPrefixes(); + tokens["STATUSMSG"] = ServerInstance->Modes->BuildPrefixes(false); + tokens["TOPICLEN"] = ConvToStr(ServerInstance->Config->Limits.MaxTopic); + tokens["USERLEN"] = ConvToStr(ServerInstance->Config->Limits.IdentMax); + tokens["VBANLIST"]; + + // Modules can add new tokens and also edit or remove existing tokens + FOREACH_MOD(On005Numeric, (tokens)); + + // EXTBAN is a special case as we need to sort it and prepend a comma. + std::map<std::string, std::string>::iterator extban = tokens.find("EXTBAN"); + if (extban != tokens.end()) + { + std::sort(extban->second.begin(), extban->second.end()); + extban->second.insert(0, ","); + } + + // Transform the map into a list of lines, ready to be sent to clients + Numeric::Numeric numeric(RPL_ISUPPORT); + unsigned int token_count = 0; + cachedlines.clear(); + for (std::map<std::string, std::string>::const_iterator it = tokens.begin(); it != tokens.end(); ++it) + { + numeric.push(it->first); + std::string& token = numeric.GetParams().back(); + AppendValue(token, it->second); + + token_count++; + if (token_count % 13 == 12 || it == --tokens.end()) + { + // Reached maximum number of tokens for this line or the current token + // is the last one; finalize the line and store it for later use + numeric.push("are supported by this server"); + cachedlines.push_back(numeric); + numeric.GetParams().clear(); + } + } +} + +void ISupportManager::SendTo(LocalUser* user) +{ + for (std::vector<Numeric::Numeric>::const_iterator i = cachedlines.begin(); i != cachedlines.end(); ++i) + user->WriteNumeric(*i); +} diff --git a/src/snomasks.cpp b/src/snomasks.cpp index 4b9c9d86b..0b266ff7e 100644 --- a/src/snomasks.cpp +++ b/src/snomasks.cpp @@ -21,7 +21,6 @@ #include "inspircd.h" -#include <stdarg.h> void SnomaskManager::FlushSnotices() { @@ -47,31 +46,21 @@ void SnomaskManager::WriteGlobalSno(char letter, const std::string& text) { WriteToSnoMask(letter, text); letter = toupper(letter); - ServerInstance->PI->SendSNONotice(std::string(1, letter), text); + ServerInstance->PI->SendSNONotice(letter, text); } void SnomaskManager::WriteToSnoMask(char letter, const char* text, ...) { - char textbuffer[MAXBUF]; - va_list argsPtr; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteToSnoMask(letter, std::string(textbuffer)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteToSnoMask(letter, textbuffer); } void SnomaskManager::WriteGlobalSno(char letter, const char* text, ...) { - char textbuffer[MAXBUF]; - va_list argsPtr; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteGlobalSno(letter, std::string(textbuffer)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteGlobalSno(letter, textbuffer); } SnomaskManager::SnomaskManager() @@ -79,54 +68,41 @@ SnomaskManager::SnomaskManager() EnableSnomask('c',"CONNECT"); /* Local connect notices */ EnableSnomask('q',"QUIT"); /* Local quit notices */ EnableSnomask('k',"KILL"); /* Kill notices */ - EnableSnomask('l',"LINK"); /* Linking notices */ EnableSnomask('o',"OPER"); /* Oper up/down notices */ EnableSnomask('a',"ANNOUNCEMENT"); /* formerly WriteOpers() - generic notices to all opers */ - EnableSnomask('d',"DEBUG"); /* Debug notices */ EnableSnomask('x',"XLINE"); /* Xline notice (g/z/q/k/e) */ EnableSnomask('t',"STATS"); /* Local or remote stats request */ - EnableSnomask('f',"FLOOD"); /* Flooding notices */ } -/*************************************************************************************/ +bool SnomaskManager::IsSnomaskUsable(char ch) const +{ + return ((isalpha(ch)) && (!masks[tolower(ch) - 'a'].Description.empty())); +} -void Snomask::SendMessage(const std::string &message, char mysnomask) +Snomask::Snomask() + : Count(0) { - if (ServerInstance->Config->NoSnoticeStack || message != LastMessage || mysnomask != LastLetter) +} + +void Snomask::SendMessage(const std::string& message, char letter) +{ + if ((!ServerInstance->Config->NoSnoticeStack) && (message == LastMessage) && (letter == LastLetter)) { - this->Flush(); - LastMessage = message; - LastLetter = mysnomask; - - std::string desc = Description; - if (desc.empty()) - desc = std::string("SNO-") + (char)tolower(mysnomask); - if (isupper(mysnomask)) - desc = "REMOTE" + desc; - ModResult MOD_RESULT; - ServerInstance->Logs->Log("snomask", DEFAULT, "%s: %s", desc.c_str(), message.c_str()); - - FIRST_MOD_RESULT(OnSendSnotice, MOD_RESULT, (mysnomask, desc, message)); - - LastBlocked = (MOD_RESULT == MOD_RES_DENY); - - if (!LastBlocked) - { - /* Only opers can receive snotices, so we iterate the oper list */ - std::list<User*>::iterator i = ServerInstance->Users->all_opers.begin(); - - while (i != ServerInstance->Users->all_opers.end()) - { - User* a = *i; - if (IS_LOCAL(a) && a->IsModeSet('s') && a->IsNoticeMaskSet(mysnomask) && !a->quitting) - { - a->WriteServ("NOTICE %s :*** %s: %s", a->nick.c_str(), desc.c_str(), message.c_str()); - } - - i++; - } - } + Count++; + return; } + + this->Flush(); + + std::string desc = GetDescription(letter); + ModResult MOD_RESULT; + FIRST_MOD_RESULT(OnSendSnotice, MOD_RESULT, (letter, desc, message)); + if (MOD_RESULT == MOD_RES_DENY) + return; + + Snomask::Send(letter, desc, message); + LastMessage = message; + LastLetter = letter; Count++; } @@ -134,36 +110,41 @@ void Snomask::Flush() { if (Count > 1) { - std::string desc = Description; - if (desc.empty()) - desc = std::string("SNO-") + (char)tolower(LastLetter); - if (isupper(LastLetter)) - desc = "REMOTE" + desc; - std::string mesg = "(last message repeated "+ConvToStr(Count)+" times)"; - - ServerInstance->Logs->Log("snomask", DEFAULT, "%s: %s", desc.c_str(), mesg.c_str()); - - FOREACH_MOD(I_OnSendSnotice, OnSendSnotice(LastLetter, desc, mesg)); - - if (!LastBlocked) - { - /* Only opers can receive snotices, so we iterate the oper list */ - std::list<User*>::iterator i = ServerInstance->Users->all_opers.begin(); - - while (i != ServerInstance->Users->all_opers.end()) - { - User* a = *i; - if (IS_LOCAL(a) && a->IsModeSet('s') && a->IsNoticeMaskSet(LastLetter) && !a->quitting) - { - a->WriteServ("NOTICE %s :*** %s: %s", a->nick.c_str(), desc.c_str(), mesg.c_str()); - } - - i++; - } - } + std::string desc = GetDescription(LastLetter); + std::string msg = "(last message repeated " + ConvToStr(Count) + " times)"; + FOREACH_MOD(OnSendSnotice, (LastLetter, desc, msg)); + Snomask::Send(LastLetter, desc, msg); } + LastMessage.clear(); - LastBlocked = false; Count = 0; } + +void Snomask::Send(char letter, const std::string& desc, const std::string& msg) +{ + ServerInstance->Logs->Log(desc, LOG_DEFAULT, msg); + const std::string finalmsg = InspIRCd::Format("*** %s: %s", desc.c_str(), msg.c_str()); + + /* Only opers can receive snotices, so we iterate the oper list */ + const UserManager::OperList& opers = ServerInstance->Users->all_opers; + for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i) + { + User* user = *i; + // IsNoticeMaskSet() returns false for opers who aren't +s, no need to check for it seperately + if (IS_LOCAL(user) && user->IsNoticeMaskSet(letter)) + user->WriteNotice(finalmsg); + } +} + +std::string Snomask::GetDescription(char letter) const +{ + std::string ret; + if (isupper(letter)) + ret = "REMOTE"; + if (!Description.empty()) + ret += Description; + else + ret += std::string("SNO-") + (char)tolower(letter); + return ret; +} diff --git a/src/socket.cpp b/src/socket.cpp index e73d01af9..cdbc83e44 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -22,111 +22,102 @@ #include "inspircd.h" -#include "socket.h" -#include "socketengine.h" -using irc::sockets::sockaddrs; -/** This will bind a socket to a port. It works for UDP/TCP. - * It can only bind to IP addresses, if you wish to bind to hostnames - * you should first resolve them using class 'Resolver'. - */ -bool InspIRCd::BindSocket(int sockfd, int port, const char* addr, bool dolisten) +bool InspIRCd::BindPort(ConfigTag* tag, const irc::sockets::sockaddrs& sa, std::vector<ListenSocket*>& old_ports) { - sockaddrs servaddr; - int ret; - - if ((*addr == '*' || *addr == '\0') && port == -1) - { - /* Port -1: Means UDP IPV4 port binding - Special case - * used by DNS engine. - */ - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.in4.sin_family = AF_INET; - } - else if (!irc::sockets::aptosa(addr, port, servaddr)) - return false; - - ret = SE->Bind(sockfd, servaddr); - - if (ret < 0) - { - return false; - } - else + for (std::vector<ListenSocket*>::iterator n = old_ports.begin(); n != old_ports.end(); ++n) { - if (dolisten) - { - if (SE->Listen(sockfd, Config->MaxConn) == -1) - { - this->Logs->Log("SOCKET",DEFAULT,"ERROR in listen(): %s",strerror(errno)); - return false; - } - else - { - this->Logs->Log("SOCKET",DEBUG,"New socket binding for %d with listen: %s:%d", sockfd, addr, port); - SE->NonBlocking(sockfd); - return true; - } - } - else + if ((**n).bind_sa == sa) { - this->Logs->Log("SOCKET",DEBUG,"New socket binding for %d without listen: %s:%d", sockfd, addr, port); + // Replace tag, we know addr and port match, but other info (type, ssl) may not. + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Replacing listener on %s from old tag at %s with new tag from %s", + sa.str().c_str(), (*n)->bind_tag->getTagLocation().c_str(), tag->getTagLocation().c_str()); + (*n)->bind_tag = tag; + (*n)->ResetIOHookProvider(); + + old_ports.erase(n); return true; } } + + ListenSocket* ll = new ListenSocket(tag, sa); + if (ll->GetFd() < 0) + { + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Failed to listen on %s from tag at %s: %s", + sa.str().c_str(), tag->getTagLocation().c_str(), strerror(errno)); + delete ll; + return false; + } + + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "Added a listener on %s from tag at %s", sa.str().c_str(), tag->getTagLocation().c_str()); + ports.push_back(ll); + return true; } -int InspIRCd::BindPorts(FailedPortList &failed_ports) +int InspIRCd::BindPorts(FailedPortList& failed_ports) { int bound = 0; std::vector<ListenSocket*> old_ports(ports.begin(), ports.end()); ConfigTagList tags = ServerInstance->Config->ConfTags("bind"); - for(ConfigIter i = tags.first; i != tags.second; ++i) + for (ConfigIter i = tags.first; i != tags.second; ++i) { ConfigTag* tag = i->second; - std::string porttag = tag->getString("port"); - std::string Addr = tag->getString("address"); - if (strncasecmp(Addr.c_str(), "::ffff:", 7) == 0) - this->Logs->Log("SOCKET",DEFAULT, "Using 4in6 (::ffff:) isn't recommended. You should bind IPv4 addresses directly instead."); - - irc::portparser portrange(porttag, false); - int portno = -1; - while (0 != (portno = portrange.GetToken())) + // Are we creating a TCP/IP listener? + const std::string address = tag->getString("address"); + const std::string portlist = tag->getString("port"); + if (!address.empty() || !portlist.empty()) { - irc::sockets::sockaddrs bindspec; - if (!irc::sockets::aptosa(Addr, portno, bindspec)) - continue; - std::string bind_readable = bindspec.str(); + // InspIRCd supports IPv4 and IPv6 natively; no 4in6 required. + if (strncasecmp(address.c_str(), "::ffff:", 7) == 0) + this->Logs->Log("SOCKET", LOG_DEFAULT, "Using 4in6 (::ffff:) isn't recommended. You should bind IPv4 addresses directly instead."); - bool skip = false; - for (std::vector<ListenSocket*>::iterator n = old_ports.begin(); n != old_ports.end(); ++n) - { - if ((**n).bind_desc == bind_readable) - { - (*n)->bind_tag = tag; // Replace tag, we know addr and port match, but other info (type, ssl) may not - skip = true; - old_ports.erase(n); - break; - } - } - if (!skip) + // A TCP listener with no ports is not very useful. + if (portlist.empty()) + this->Logs->Log("SOCKET", LOG_DEFAULT, "TCP listener on %s at %s has no ports specified!", + address.empty() ? "*" : address.c_str(), tag->getTagLocation().c_str()); + + irc::portparser portrange(portlist, false); + for (int port; (port = portrange.GetToken()); ) { - ListenSocket* ll = new ListenSocket(tag, bindspec); + irc::sockets::sockaddrs bindspec; + if (!irc::sockets::aptosa(address, port, bindspec)) + continue; - if (ll->GetFd() > -1) - { - bound++; - ports.push_back(ll); - } + if (!BindPort(tag, bindspec, old_ports)) + failed_ports.push_back(std::make_pair(bindspec, errno)); else - { - failed_ports.push_back(std::make_pair(bind_readable, strerror(errno))); - delete ll; - } + bound++; } + continue; } + +#ifndef _WIN32 + // Are we creating a UNIX listener? + const std::string path = tag->getString("path"); + if (!path.empty()) + { + // UNIX socket paths are length limited to less than PATH_MAX. + irc::sockets::sockaddrs bindspec; + if (path.length() > std::min(ServerInstance->Config->Limits.MaxHost, sizeof(bindspec.un.sun_path))) + { + this->Logs->Log("SOCKET", LOG_DEFAULT, "UNIX listener on %s at %s specified a path that is too long!", + path.c_str(), tag->getTagLocation().c_str()); + continue; + } + + // Create the bindspec manually (aptosa doesn't work with AF_UNIX yet). + memset(&bindspec, 0, sizeof(bindspec)); + bindspec.un.sun_family = AF_UNIX; + memcpy(&bindspec.un.sun_path, path.c_str(), sizeof(bindspec.un.sun_path)); + + if (!BindPort(tag, bindspec, old_ports)) + failed_ports.push_back(std::make_pair(bindspec, errno)); + else + bound++; + } +#endif } std::vector<ListenSocket*>::iterator n = ports.begin(); @@ -136,12 +127,12 @@ int InspIRCd::BindPorts(FailedPortList &failed_ports) n++; if (n == ports.end()) { - this->Logs->Log("SOCKET",DEFAULT,"Port bindings slipped out of vector, aborting close!"); + this->Logs->Log("SOCKET", LOG_DEFAULT, "Port bindings slipped out of vector, aborting close!"); break; } - this->Logs->Log("SOCKET",DEFAULT, "Port binding %s was removed from the config file, closing.", - (**n).bind_desc.c_str()); + this->Logs->Log("SOCKET", LOG_DEFAULT, "Port binding %s was removed from the config file, closing.", + (**n).bind_sa.str().c_str()); delete *n; // this keeps the iterator valid, pointing to the next element @@ -183,111 +174,157 @@ bool irc::sockets::aptosa(const std::string& addr, int port, irc::sockets::socka return false; } -int irc::sockets::sockaddrs::port() const +int irc::sockets::sockaddrs::family() const { - if (sa.sa_family == AF_INET) - return ntohs(in4.sin_port); - if (sa.sa_family == AF_INET6) - return ntohs(in6.sin6_port); - return -1; + return sa.sa_family; } -std::string irc::sockets::sockaddrs::addr() const +int irc::sockets::sockaddrs::port() const { - char addrv[INET6_ADDRSTRLEN+1]; - if (sa.sa_family == AF_INET) - { - if (!inet_ntop(AF_INET, &in4.sin_addr, addrv, sizeof(addrv))) - return ""; - return addrv; - } - else if (sa.sa_family == AF_INET6) + switch (family()) { - if (!inet_ntop(AF_INET6, &in6.sin6_addr, addrv, sizeof(addrv))) - return ""; - return addrv; + case AF_INET: + return ntohs(in4.sin_port); + + case AF_INET6: + return ntohs(in6.sin6_port); + + case AF_UNIX: + return 0; } - return ""; + + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::port(): socket type %d is unknown!", family()); + return 0; } -bool irc::sockets::satoap(const irc::sockets::sockaddrs& sa, std::string& addr, int &port) +std::string irc::sockets::sockaddrs::addr() const { - port = sa.port(); - addr = sa.addr(); - return !addr.empty(); + switch (family()) + { + case AF_INET: + char ip4addr[INET_ADDRSTRLEN]; + if (!inet_ntop(AF_INET, (void*)&in4.sin_addr, ip4addr, sizeof(ip4addr))) + return "0.0.0.0"; + return ip4addr; + + case AF_INET6: + char ip6addr[INET6_ADDRSTRLEN]; + if (!inet_ntop(AF_INET6, (void*)&in6.sin6_addr, ip6addr, sizeof(ip6addr))) + return "0:0:0:0:0:0:0:0"; + return ip6addr; + + case AF_UNIX: + return un.sun_path; + } + + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::addr(): socket type %d is unknown!", family()); + return "<unknown>"; } std::string irc::sockets::sockaddrs::str() const { - char buffer[MAXBUF]; - if (sa.sa_family == AF_INET) - { - const uint8_t* bits = reinterpret_cast<const uint8_t*>(&in4.sin_addr); - sprintf(buffer, "%d.%d.%d.%d:%u", bits[0], bits[1], bits[2], bits[3], ntohs(in4.sin_port)); - } - else if (sa.sa_family == AF_INET6) + switch (family()) { - buffer[0] = '['; - if (!inet_ntop(AF_INET6, &in6.sin6_addr, buffer+1, MAXBUF - 10)) - return "<unknown>"; // should never happen, buffer is large enough - int len = strlen(buffer); - // no need for snprintf, buffer has at least 9 chars left, max short len = 5 - sprintf(buffer + len, "]:%u", ntohs(in6.sin6_port)); + case AF_INET: + char ip4addr[INET_ADDRSTRLEN]; + if (!inet_ntop(AF_INET, (void*)&in4.sin_addr, ip4addr, sizeof(ip4addr))) + strcpy(ip4addr, "0.0.0.0"); + return InspIRCd::Format("%s:%u", ip4addr, ntohs(in4.sin_port)); + + case AF_INET6: + char ip6addr[INET6_ADDRSTRLEN]; + if (!inet_ntop(AF_INET6, (void*)&in6.sin6_addr, ip6addr, sizeof(ip6addr))) + strcpy(ip6addr, "0:0:0:0:0:0:0:0"); + return InspIRCd::Format("[%s]:%u", ip6addr, ntohs(in6.sin6_port)); + + case AF_UNIX: + return un.sun_path; } - else - return "<unknown>"; - return std::string(buffer); + + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::str(): socket type %d is unknown!", family()); + return "<unknown>"; } -int irc::sockets::sockaddrs::sa_size() const +socklen_t irc::sockets::sockaddrs::sa_size() const { - if (sa.sa_family == AF_INET) - return sizeof(in4); - if (sa.sa_family == AF_INET6) - return sizeof(in6); + switch (family()) + { + case AF_INET: + return sizeof(in4); + + case AF_INET6: + return sizeof(in6); + + case AF_UNIX: + return sizeof(un); + } + + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::sa_size(): socket type %d is unknown!", family()); return 0; } bool irc::sockets::sockaddrs::operator==(const irc::sockets::sockaddrs& other) const { - if (sa.sa_family != other.sa.sa_family) + if (family() != other.family()) return false; - if (sa.sa_family == AF_INET) - return (in4.sin_port == other.in4.sin_port) && (in4.sin_addr.s_addr == other.in4.sin_addr.s_addr); - if (sa.sa_family == AF_INET6) - return (in6.sin6_port == other.in6.sin6_port) && !memcmp(in6.sin6_addr.s6_addr, other.in6.sin6_addr.s6_addr, 16); + + switch (family()) + { + case AF_INET: + return (in4.sin_port == other.in4.sin_port) && (in4.sin_addr.s_addr == other.in4.sin_addr.s_addr); + + case AF_INET6: + return (in6.sin6_port == other.in6.sin6_port) && !memcmp(in6.sin6_addr.s6_addr, other.in6.sin6_addr.s6_addr, 16); + + case AF_UNIX: + return !strcmp(un.sun_path, other.un.sun_path); + } + + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::sockaddrs::operator==(): socket type %d is unknown!", family()); return !memcmp(this, &other, sizeof(*this)); } -static void sa2cidr(irc::sockets::cidr_mask& cidr, const irc::sockets::sockaddrs& sa, int range) +static void sa2cidr(irc::sockets::cidr_mask& cidr, const irc::sockets::sockaddrs& sa, unsigned char range) { const unsigned char* base; unsigned char target_byte; - cidr.type = sa.sa.sa_family; memset(cidr.bits, 0, sizeof(cidr.bits)); - if (cidr.type == AF_INET) + cidr.type = sa.family(); + switch (cidr.type) { - target_byte = sizeof(sa.in4.sin_addr); - base = (unsigned char*)&sa.in4.sin_addr; - if (range > 32) - range = 32; - } - else if (cidr.type == AF_INET6) - { - target_byte = sizeof(sa.in6.sin6_addr); - base = (unsigned char*)&sa.in6.sin6_addr; - if (range > 128) - range = 128; - } - else - { - cidr.length = 0; - return; + case AF_UNIX: + // XXX: UNIX sockets don't support CIDR. This fix is non-ideal but I can't + // really think of another way to handle it. + cidr.length = 0; + return; + + case AF_INET: + cidr.length = range > 32 ? 32 : range; + target_byte = sizeof(sa.in4.sin_addr); + base = (unsigned char*)&sa.in4.sin_addr; + break; + + case AF_INET6: + cidr.length = range > 128 ? 128 : range; + target_byte = sizeof(sa.in6.sin6_addr); + base = (unsigned char*)&sa.in6.sin6_addr; + break; + + default: + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: sa2cidr(): socket type %d is unknown!", cidr.type); + cidr.length = 0; + return; } - cidr.length = range; - unsigned int border = range / 8; + + unsigned int border = cidr.length / 8; unsigned int bitmask = (0xFF00 >> (range & 7)) & 0xFF; for(unsigned int i=0; i < target_byte; i++) { @@ -300,7 +337,7 @@ static void sa2cidr(irc::sockets::cidr_mask& cidr, const irc::sockets::sockaddrs } } -irc::sockets::cidr_mask::cidr_mask(const irc::sockets::sockaddrs& sa, int range) +irc::sockets::cidr_mask::cidr_mask(const irc::sockets::sockaddrs& sa, unsigned char range) { sa2cidr(*this, sa, range); } @@ -327,20 +364,30 @@ std::string irc::sockets::cidr_mask::str() const { irc::sockets::sockaddrs sa; sa.sa.sa_family = type; + unsigned char* base; - int len; - if (type == AF_INET) - { - base = (unsigned char*)&sa.in4.sin_addr; - len = 4; - } - else if (type == AF_INET6) + size_t len; + switch (type) { - base = (unsigned char*)&sa.in6.sin6_addr; - len = 16; + case AF_INET: + base = (unsigned char*)&sa.in4.sin_addr; + len = 4; + break; + + case AF_INET6: + base = (unsigned char*)&sa.in6.sin6_addr; + len = 16; + break; + + case AF_UNIX: + return sa.un.sun_path; + + default: + // If we have reached this point then we have encountered a bug. + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::cidr_mask::str(): socket type %d is unknown!", type); + return "<unknown>"; } - else - return ""; + memcpy(base, bits, len); return sa.addr() + "/" + ConvToStr((int)length); } @@ -362,9 +409,8 @@ bool irc::sockets::cidr_mask::operator<(const cidr_mask& other) const bool irc::sockets::cidr_mask::match(const irc::sockets::sockaddrs& addr) const { - if (addr.sa.sa_family != type) + if (addr.family() != type) return false; irc::sockets::cidr_mask tmp(addr, length); return tmp == *this; } - diff --git a/src/socketengine.cpp b/src/socketengine.cpp index 4a9a2ef10..df6ff5a02 100644 --- a/src/socketengine.cpp +++ b/src/socketengine.cpp @@ -21,8 +21,29 @@ */ +#include "exitcodes.h" #include "inspircd.h" +#include <iostream> + +/** Reference table, contains all current handlers + **/ +std::vector<EventHandler*> SocketEngine::ref; + +/** Current number of descriptors in the engine + */ +size_t SocketEngine::CurrentSetSize = 0; + +/** List of handlers that want a trial read/write + */ +std::set<int> SocketEngine::trials; + +size_t SocketEngine::MaxSetSize = 0; + +/** Socket engine statistics: count of various events, bandwidth usage + */ +SocketEngine::Statistics SocketEngine::stats; + EventHandler::EventHandler() { fd = -1; @@ -34,20 +55,37 @@ void EventHandler::SetFd(int FD) this->fd = FD; } -SocketEngine::SocketEngine() +void EventHandler::OnEventHandlerWrite() { - TotalEvents = WriteEvents = ReadEvents = ErrorEvents = 0; - lastempty = ServerInstance->Time(); - indata = outdata = 0; } -SocketEngine::~SocketEngine() +void EventHandler::OnEventHandlerError(int errornum) { } -void SocketEngine::SetEventMask(EventHandler* eh, int mask) +void SocketEngine::InitError() { - eh->event_mask = mask; + std::cerr << con_red << "FATAL ERROR!" << con_reset << " Socket engine initialization failed. " << strerror(errno) << '.' << std::endl; + exit(EXIT_STATUS_SOCKETENGINE); +} + +void SocketEngine::LookupMaxFds() +{ +#if defined _WIN32 + MaxSetSize = FD_SETSIZE; +#else + struct rlimit limits; + if (!getrlimit(RLIMIT_NOFILE, &limits)) + MaxSetSize = limits.rlim_cur; + +#if defined __APPLE__ + limits.rlim_cur = limits.rlim_max == RLIM_INFINITY ? OPEN_MAX : limits.rlim_max; +#else + limits.rlim_cur = limits.rlim_max; +#endif + if (!setrlimit(RLIMIT_NOFILE, &limits)) + MaxSetSize = limits.rlim_cur; +#endif } void SocketEngine::ChangeEventMask(EventHandler* eh, int change) @@ -60,7 +98,7 @@ void SocketEngine::ChangeEventMask(EventHandler* eh, int change) new_m &= ~FD_WANT_READ_MASK; if (change & FD_WANT_WRITE_MASK) new_m &= ~FD_WANT_WRITE_MASK; - + // if adding a trial read/write, insert it into the set if (change & FD_TRIAL_NOTE_MASK && !(old_m & FD_TRIAL_NOTE_MASK)) trials.insert(eh->GetFd()); @@ -88,23 +126,44 @@ void SocketEngine::DispatchTrialWrites() int mask = eh->event_mask; eh->event_mask &= ~(FD_ADD_TRIAL_READ | FD_ADD_TRIAL_WRITE); if ((mask & (FD_ADD_TRIAL_READ | FD_READ_WILL_BLOCK)) == FD_ADD_TRIAL_READ) - eh->HandleEvent(EVENT_READ, 0); + eh->OnEventHandlerRead(); if ((mask & (FD_ADD_TRIAL_WRITE | FD_WRITE_WILL_BLOCK)) == FD_ADD_TRIAL_WRITE) - eh->HandleEvent(EVENT_WRITE, 0); + eh->OnEventHandlerWrite(); } } -bool SocketEngine::HasFd(int fd) +bool SocketEngine::AddFdRef(EventHandler* eh) { - if ((fd < 0) || (fd > GetMaxFds())) + int fd = eh->GetFd(); + if (HasFd(fd)) return false; - return (ref[fd] != NULL); + + while (static_cast<unsigned int>(fd) >= ref.size()) + ref.resize(ref.empty() ? 1 : (ref.size() * 2)); + ref[fd] = eh; + CurrentSetSize++; + return true; +} + +void SocketEngine::DelFdRef(EventHandler *eh) +{ + int fd = eh->GetFd(); + if (GetRef(fd) == eh) + { + ref[fd] = NULL; + CurrentSetSize--; + } +} + +bool SocketEngine::HasFd(int fd) +{ + return GetRef(fd) != NULL; } EventHandler* SocketEngine::GetRef(int fd) { - if ((fd < 0) || (fd > GetMaxFds())) - return 0; + if (fd < 0 || static_cast<unsigned int>(fd) >= ref.size()) + return NULL; return ref[fd]; } @@ -112,7 +171,7 @@ bool SocketEngine::BoundsCheckFd(EventHandler* eh) { if (!eh) return false; - if ((eh->GetFd() < 0) || (eh->GetFd() > GetMaxFds())) + if (eh->GetFd() < 0) return false; return true; } @@ -123,13 +182,12 @@ int SocketEngine::Accept(EventHandler* fd, sockaddr *addr, socklen_t *addrlen) return accept(fd->GetFd(), addr, addrlen); } -int SocketEngine::Close(EventHandler* fd) +int SocketEngine::Close(EventHandler* eh) { -#ifdef _WIN32 - return closesocket(fd->GetFd()); -#else - return close(fd->GetFd()); -#endif + DelFd(eh); + int ret = Close(eh->GetFd()); + eh->SetFd(-1); + return ret; } int SocketEngine::Close(int fd) @@ -172,38 +230,60 @@ void SocketEngine::SetReuse(int fd) int SocketEngine::RecvFrom(EventHandler* fd, void *buf, size_t len, int flags, sockaddr *from, socklen_t *fromlen) { int nbRecvd = recvfrom(fd->GetFd(), (char*)buf, len, flags, from, fromlen); - if (nbRecvd > 0) - this->UpdateStats(nbRecvd, 0); + stats.UpdateReadCounters(nbRecvd); return nbRecvd; } int SocketEngine::Send(EventHandler* fd, const void *buf, size_t len, int flags) { int nbSent = send(fd->GetFd(), (const char*)buf, len, flags); - if (nbSent > 0) - this->UpdateStats(0, nbSent); + stats.UpdateWriteCounters(nbSent); return nbSent; } int SocketEngine::Recv(EventHandler* fd, void *buf, size_t len, int flags) { int nbRecvd = recv(fd->GetFd(), (char*)buf, len, flags); - if (nbRecvd > 0) - this->UpdateStats(nbRecvd, 0); + stats.UpdateReadCounters(nbRecvd); return nbRecvd; } -int SocketEngine::SendTo(EventHandler* fd, const void *buf, size_t len, int flags, const sockaddr *to, socklen_t tolen) +int SocketEngine::SendTo(EventHandler* fd, const void* buf, size_t len, int flags, const irc::sockets::sockaddrs& address) { - int nbSent = sendto(fd->GetFd(), (const char*)buf, len, flags, to, tolen); - if (nbSent > 0) - this->UpdateStats(0, nbSent); + int nbSent = sendto(fd->GetFd(), (const char*)buf, len, flags, &address.sa, address.sa_size()); + stats.UpdateWriteCounters(nbSent); return nbSent; } -int SocketEngine::Connect(EventHandler* fd, const sockaddr *serv_addr, socklen_t addrlen) +int SocketEngine::WriteV(EventHandler* fd, const IOVector* iovec, int count) +{ + int sent = writev(fd->GetFd(), iovec, count); + stats.UpdateWriteCounters(sent); + return sent; +} + +#ifdef _WIN32 +int SocketEngine::WriteV(EventHandler* fd, const iovec* iovec, int count) +{ + // On Windows the fields in iovec are not in the order required by the Winsock API; IOVector has + // the fields in the correct order. + // Create temporary IOVectors from the iovecs and pass them to the WriteV() method that accepts the + // platform's native struct. + IOVector wiovec[128]; + count = std::min(count, static_cast<int>(sizeof(wiovec) / sizeof(IOVector))); + + for (int i = 0; i < count; i++) + { + wiovec[i].iov_len = iovec[i].iov_len; + wiovec[i].iov_base = reinterpret_cast<char*>(iovec[i].iov_base); + } + return WriteV(fd, wiovec, count); +} +#endif + +int SocketEngine::Connect(EventHandler* fd, const irc::sockets::sockaddrs& address) { - int ret = connect(fd->GetFd(), serv_addr, addrlen); + int ret = connect(fd->GetFd(), &address.sa, address.sa_size()); #ifdef _WIN32 if ((ret == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK)) errno = EINPROGRESS; @@ -231,24 +311,42 @@ int SocketEngine::Shutdown(int fd, int how) return shutdown(fd, how); } -void SocketEngine::RecoverFromFork() +void SocketEngine::Statistics::UpdateReadCounters(int len_in) +{ + CheckFlush(); + + ReadEvents++; + if (len_in > 0) + indata += len_in; + else if (len_in < 0) + ErrorEvents++; +} + +void SocketEngine::Statistics::UpdateWriteCounters(int len_out) { + CheckFlush(); + + WriteEvents++; + if (len_out > 0) + outdata += len_out; + else if (len_out < 0) + ErrorEvents++; } -void SocketEngine::UpdateStats(size_t len_in, size_t len_out) +void SocketEngine::Statistics::CheckFlush() const { - if (lastempty != ServerInstance->Time()) + // Reset the in/out byte counters if it has been more than a second + time_t now = ServerInstance->Time(); + if (lastempty != now) { - lastempty = ServerInstance->Time(); + lastempty = now; indata = outdata = 0; } - indata += len_in; - outdata += len_out; } -void SocketEngine::GetStats(float &kbitpersec_in, float &kbitpersec_out, float &kbitpersec_total) +void SocketEngine::Statistics::GetBandwidth(float& kbitpersec_in, float& kbitpersec_out, float& kbitpersec_total) const { - UpdateStats(0, 0); /* Forces emptying of the values if its been more than a second */ + CheckFlush(); float in_kbit = indata * 8; float out_kbit = outdata * 8; kbitpersec_total = ((in_kbit + out_kbit) / 1024); diff --git a/src/socketengines/socketengine_epoll.cpp b/src/socketengines/socketengine_epoll.cpp index d5f017347..60b365ee1 100644 --- a/src/socketengines/socketengine_epoll.cpp +++ b/src/socketengines/socketengine_epoll.cpp @@ -18,79 +18,40 @@ */ -#include <vector> -#include <string> -#include <map> #include "inspircd.h" -#include "exitcodes.h" -#include "socketengine.h" + #include <sys/epoll.h> #include <sys/resource.h> -#include <iostream> -#define EP_DELAY 5 /** A specialisation of the SocketEngine class, designed to use linux 2.6 epoll(). */ -class EPollEngine : public SocketEngine +namespace { -private: - /** These are used by epoll() to hold socket events - */ - struct epoll_event* events; int EngineHandle; -public: - /** Create a new EPollEngine - */ - EPollEngine(); - /** Delete an EPollEngine + + /** These are used by epoll() to hold socket events */ - virtual ~EPollEngine(); - virtual bool AddFd(EventHandler* eh, int event_mask); - virtual void OnSetEvent(EventHandler* eh, int old_mask, int new_mask); - virtual void DelFd(EventHandler* eh); - virtual int DispatchEvents(); - virtual std::string GetName(); -}; + std::vector<struct epoll_event> events(1); +} -EPollEngine::EPollEngine() +void SocketEngine::Init() { - CurrentSetSize = 0; - - struct rlimit limit; - if (!getrlimit(RLIMIT_NOFILE, &limit)) - { - MAX_DESCRIPTORS = limit.rlim_cur; - } - else - { - ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets!"); - std::cout << "ERROR: Can't determine maximum number of open sockets!" << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } - - // This is not a maximum, just a hint at the eventual number of sockets that may be polled. - EngineHandle = epoll_create(GetMaxFds() / 4); + LookupMaxFds(); + // 128 is not a maximum, just a hint at the eventual number of sockets that may be polled, + // and it is completely ignored by 2.6.8 and later kernels, except it must be larger than zero. + EngineHandle = epoll_create(128); if (EngineHandle == -1) - { - ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: Could not initialize socket engine: %s", strerror(errno)); - ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: Your kernel probably does not have the proper features. This is a fatal error, exiting now."); - std::cout << "ERROR: Could not initialize epoll socket engine: " << strerror(errno) << std::endl; - std::cout << "ERROR: Your kernel probably does not have the proper features. This is a fatal error, exiting now." << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } - - ref = new EventHandler* [GetMaxFds()]; - events = new struct epoll_event[GetMaxFds()]; + InitError(); +} - memset(ref, 0, GetMaxFds() * sizeof(EventHandler*)); +void SocketEngine::RecoverFromFork() +{ } -EPollEngine::~EPollEngine() +void SocketEngine::Deinit() { - this->Close(EngineHandle); - delete[] ref; - delete[] events; + Close(EngineHandle); } static unsigned mask_to_epoll(int event_mask) @@ -116,41 +77,41 @@ static unsigned mask_to_epoll(int event_mask) return rv; } -bool EPollEngine::AddFd(EventHandler* eh, int event_mask) +bool SocketEngine::AddFd(EventHandler* eh, int event_mask) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"AddFd out of range: (fd: %d, max: %d)", fd, GetMaxFds()); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "AddFd out of range: (fd: %d)", fd); return false; } - if (ref[fd]) + if (!SocketEngine::AddFdRef(eh)) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"Attempt to add duplicate fd: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Attempt to add duplicate fd: %d", fd); return false; } struct epoll_event ev; - memset(&ev,0,sizeof(ev)); + memset(&ev, 0, sizeof(ev)); ev.events = mask_to_epoll(event_mask); - ev.data.fd = fd; + ev.data.ptr = static_cast<void*>(eh); int i = epoll_ctl(EngineHandle, EPOLL_CTL_ADD, fd, &ev); if (i < 0) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"Error adding fd: %d to socketengine: %s", fd, strerror(errno)); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Error adding fd: %d to socketengine: %s", fd, strerror(errno)); return false; } - ServerInstance->Logs->Log("SOCKET",DEBUG,"New file descriptor: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "New file descriptor: %d", fd); + + eh->SetEventMask(event_mask); + ResizeDouble(events); - ref[fd] = eh; - SocketEngine::SetEventMask(eh, event_mask); - CurrentSetSize++; return true; } -void EPollEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) +void SocketEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) { unsigned old_events = mask_to_epoll(old_mask); unsigned new_events = mask_to_epoll(new_mask); @@ -158,75 +119,78 @@ void EPollEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) { // ok, we actually have something to tell the kernel about struct epoll_event ev; - memset(&ev,0,sizeof(ev)); + memset(&ev, 0, sizeof(ev)); ev.events = new_events; - ev.data.fd = eh->GetFd(); + ev.data.ptr = static_cast<void*>(eh); epoll_ctl(EngineHandle, EPOLL_CTL_MOD, eh->GetFd(), &ev); } } -void EPollEngine::DelFd(EventHandler* eh) +void SocketEngine::DelFd(EventHandler* eh) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"DelFd out of range: (fd: %d, max: %d)", fd, GetMaxFds()); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "DelFd out of range: (fd: %d)", fd); return; } + // Do not initialize epoll_event because for EPOLL_CTL_DEL operations the event is ignored and can be NULL. + // In kernel versions before 2.6.9, the EPOLL_CTL_DEL operation required a non-NULL pointer in event, + // even though this argument is ignored. Since Linux 2.6.9, event can be specified as NULL when using EPOLL_CTL_DEL. struct epoll_event ev; - memset(&ev,0,sizeof(ev)); - ev.data.fd = fd; int i = epoll_ctl(EngineHandle, EPOLL_CTL_DEL, fd, &ev); if (i < 0) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"epoll_ctl can't remove socket: %s", strerror(errno)); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "epoll_ctl can't remove socket: %s", strerror(errno)); } - ref[fd] = NULL; + SocketEngine::DelFdRef(eh); - ServerInstance->Logs->Log("SOCKET",DEBUG,"Remove file descriptor: %d", fd); - CurrentSetSize--; + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Remove file descriptor: %d", fd); } -int EPollEngine::DispatchEvents() +int SocketEngine::DispatchEvents() { - socklen_t codesize = sizeof(int); - int errcode; - int i = epoll_wait(EngineHandle, events, GetMaxFds() - 1, 1000); + int i = epoll_wait(EngineHandle, &events[0], events.size(), 1000); ServerInstance->UpdateTime(); - TotalEvents += i; + stats.TotalEvents += i; for (int j = 0; j < i; j++) { - EventHandler* eh = ref[events[j].data.fd]; - if (!eh) - { - ServerInstance->Logs->Log("SOCKET",DEBUG,"Got event on unknown fd: %d", events[j].data.fd); - epoll_ctl(EngineHandle, EPOLL_CTL_DEL, events[j].data.fd, &events[j]); + // Copy these in case the vector gets resized and ev invalidated + const epoll_event ev = events[j]; + + EventHandler* const eh = static_cast<EventHandler*>(ev.data.ptr); + const int fd = eh->GetFd(); + if (fd < 0) continue; - } - if (events[j].events & EPOLLHUP) + + if (ev.events & EPOLLHUP) { - ErrorEvents++; - eh->HandleEvent(EVENT_ERROR, 0); + stats.ErrorEvents++; + eh->OnEventHandlerError(0); continue; } - if (events[j].events & EPOLLERR) + + if (ev.events & EPOLLERR) { - ErrorEvents++; + stats.ErrorEvents++; /* Get error number */ - if (getsockopt(events[j].data.fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) + socklen_t codesize = sizeof(int); + int errcode; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) errcode = errno; - eh->HandleEvent(EVENT_ERROR, errcode); + eh->OnEventHandlerError(errcode); continue; } + int mask = eh->GetEventMask(); - if (events[j].events & EPOLLIN) + if (ev.events & EPOLLIN) mask &= ~FD_READ_WILL_BLOCK; - if (events[j].events & EPOLLOUT) + if (ev.events & EPOLLOUT) { mask &= ~FD_WRITE_WILL_BLOCK; if (mask & FD_WANT_SINGLE_WRITE) @@ -236,31 +200,19 @@ int EPollEngine::DispatchEvents() mask = nm; } } - SetEventMask(eh, mask); - if (events[j].events & EPOLLIN) + eh->SetEventMask(mask); + if (ev.events & EPOLLIN) { - ReadEvents++; - eh->HandleEvent(EVENT_READ); - if (eh != ref[events[j].data.fd]) + eh->OnEventHandlerRead(); + if (eh != GetRef(fd)) // whoa! we got deleted, better not give out the write event continue; } - if (events[j].events & EPOLLOUT) + if (ev.events & EPOLLOUT) { - WriteEvents++; - eh->HandleEvent(EVENT_WRITE); + eh->OnEventHandlerWrite(); } } return i; } - -std::string EPollEngine::GetName() -{ - return "epoll"; -} - -SocketEngine* CreateSocketEngine() -{ - return new EPollEngine; -} diff --git a/src/socketengines/socketengine_kqueue.cpp b/src/socketengines/socketengine_kqueue.cpp index 8694a0bdd..b23cfbd9d 100644 --- a/src/socketengines/socketengine_kqueue.cpp +++ b/src/socketengines/socketengine_kqueue.cpp @@ -20,70 +20,36 @@ #include "inspircd.h" -#include "exitcodes.h" + #include <sys/types.h> #include <sys/event.h> #include <sys/time.h> -#include "socketengine.h" -#include <iostream> +#include <sys/sysctl.h> /** A specialisation of the SocketEngine class, designed to use BSD kqueue(). */ -class KQueueEngine : public SocketEngine +namespace { -private: int EngineHandle; + unsigned int ChangePos = 0; /** These are used by kqueue() to hold socket events */ - struct kevent* ke_list; - /** This is a specialised time value used by kqueue() - */ - struct timespec ts; -public: - /** Create a new KQueueEngine - */ - KQueueEngine(); - /** Delete a KQueueEngine - */ - virtual ~KQueueEngine(); - bool AddFd(EventHandler* eh, int event_mask); - void OnSetEvent(EventHandler* eh, int old_mask, int new_mask); - virtual void DelFd(EventHandler* eh); - virtual int DispatchEvents(); - virtual std::string GetName(); - virtual void RecoverFromFork(); -}; + std::vector<struct kevent> ke_list(16); -#include <sys/sysctl.h> + /** Pending changes + */ + std::vector<struct kevent> changelist(8); +} -KQueueEngine::KQueueEngine() +/** Initialize the kqueue engine + */ +void SocketEngine::Init() { - MAX_DESCRIPTORS = 0; - int mib[2]; - size_t len; - - mib[0] = CTL_KERN; -#ifdef KERN_MAXFILESPERPROC - mib[1] = KERN_MAXFILESPERPROC; -#else - mib[1] = KERN_MAXFILES; -#endif - len = sizeof(MAX_DESCRIPTORS); - sysctl(mib, 2, &MAX_DESCRIPTORS, &len, NULL, 0); - if (MAX_DESCRIPTORS <= 0) - { - ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets!"); - std::cout << "ERROR: Can't determine maximum number of open sockets!" << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } - - this->RecoverFromFork(); - ke_list = new struct kevent[GetMaxFds()]; - ref = new EventHandler* [GetMaxFds()]; - memset(ref, 0, GetMaxFds() * sizeof(EventHandler*)); + LookupMaxFds(); + RecoverFromFork(); } -void KQueueEngine::RecoverFromFork() +void SocketEngine::RecoverFromFork() { /* * The only bad thing about kqueue is that its fd cant survive a fork and is not inherited. @@ -92,177 +58,141 @@ void KQueueEngine::RecoverFromFork() */ EngineHandle = kqueue(); if (EngineHandle == -1) - { - ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: Could not initialize socket engine. Your kernel probably does not have the proper features."); - ServerInstance->Logs->Log("SOCKET",DEFAULT, "ERROR: this is a fatal error, exiting now."); - std::cout << "ERROR: Could not initialize socket engine. Your kernel probably does not have the proper features." << std::endl; - std::cout << "ERROR: this is a fatal error, exiting now." << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } - CurrentSetSize = 0; + InitError(); } -KQueueEngine::~KQueueEngine() +/** Shutdown the kqueue engine + */ +void SocketEngine::Deinit() { - this->Close(EngineHandle); - delete[] ref; - delete[] ke_list; + Close(EngineHandle); } -bool KQueueEngine::AddFd(EventHandler* eh, int event_mask) +static struct kevent* GetChangeKE() +{ + if (ChangePos >= changelist.size()) + changelist.resize(changelist.size() * 2); + return &changelist[ChangePos++]; +} + +bool SocketEngine::AddFd(EventHandler* eh, int event_mask) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) return false; - if (ref[fd]) + if (!SocketEngine::AddFdRef(eh)) return false; // We always want to read from the socket... - struct kevent ke; - EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); + struct kevent* ke = GetChangeKE(); + EV_SET(ke, fd, EVFILT_READ, EV_ADD, 0, 0, static_cast<void*>(eh)); - int i = kevent(EngineHandle, &ke, 1, 0, 0, NULL); - if (i == -1) - { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Failed to add fd: %d %s", - fd, strerror(errno)); - return false; - } + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "New file descriptor: %d", fd); - ref[fd] = eh; - SocketEngine::SetEventMask(eh, event_mask); + eh->SetEventMask(event_mask); OnSetEvent(eh, 0, event_mask); - CurrentSetSize++; + ResizeDouble(ke_list); - ServerInstance->Logs->Log("SOCKET",DEBUG,"New file descriptor: %d", fd); return true; } -void KQueueEngine::DelFd(EventHandler* eh) +void SocketEngine::DelFd(EventHandler* eh) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"DelFd() on invalid fd: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "DelFd() on invalid fd: %d", fd); return; } - struct kevent ke; - // First remove the write filter ignoring errors, since we can't be // sure if there are actually any write filters registered. - EV_SET(&ke, eh->GetFd(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); - kevent(EngineHandle, &ke, 1, 0, 0, NULL); + struct kevent* ke = GetChangeKE(); + EV_SET(ke, eh->GetFd(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); // Then remove the read filter. - EV_SET(&ke, eh->GetFd(), EVFILT_READ, EV_DELETE, 0, 0, NULL); - int j = kevent(EngineHandle, &ke, 1, 0, 0, NULL); - - if (j < 0) - { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Failed to remove fd: %d %s", - fd, strerror(errno)); - } + ke = GetChangeKE(); + EV_SET(ke, eh->GetFd(), EVFILT_READ, EV_DELETE, 0, 0, NULL); - CurrentSetSize--; - ref[fd] = NULL; + SocketEngine::DelFdRef(eh); - ServerInstance->Logs->Log("SOCKET",DEBUG,"Remove file descriptor: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Remove file descriptor: %d", fd); } -void KQueueEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) +void SocketEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) { if ((new_mask & FD_WANT_POLL_WRITE) && !(old_mask & FD_WANT_POLL_WRITE)) { // new poll-style write - struct kevent ke; - EV_SET(&ke, eh->GetFd(), EVFILT_WRITE, EV_ADD, 0, 0, NULL); - int i = kevent(EngineHandle, &ke, 1, 0, 0, NULL); - if (i < 0) { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Failed to mark for writing: %d %s", - eh->GetFd(), strerror(errno)); - } + struct kevent* ke = GetChangeKE(); + EV_SET(ke, eh->GetFd(), EVFILT_WRITE, EV_ADD, 0, 0, static_cast<void*>(eh)); } else if ((old_mask & FD_WANT_POLL_WRITE) && !(new_mask & FD_WANT_POLL_WRITE)) { // removing poll-style write - struct kevent ke; - EV_SET(&ke, eh->GetFd(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); - int i = kevent(EngineHandle, &ke, 1, 0, 0, NULL); - if (i < 0) { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Failed to mark for writing: %d %s", - eh->GetFd(), strerror(errno)); - } + struct kevent* ke = GetChangeKE(); + EV_SET(ke, eh->GetFd(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); } if ((new_mask & (FD_WANT_FAST_WRITE | FD_WANT_SINGLE_WRITE)) && !(old_mask & (FD_WANT_FAST_WRITE | FD_WANT_SINGLE_WRITE))) { - // new one-shot write - struct kevent ke; - EV_SET(&ke, eh->GetFd(), EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, NULL); - int i = kevent(EngineHandle, &ke, 1, 0, 0, NULL); - if (i < 0) { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Failed to mark for writing: %d %s", - eh->GetFd(), strerror(errno)); - } + struct kevent* ke = GetChangeKE(); + EV_SET(ke, eh->GetFd(), EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, static_cast<void*>(eh)); } } -int KQueueEngine::DispatchEvents() +int SocketEngine::DispatchEvents() { + struct timespec ts; ts.tv_nsec = 0; ts.tv_sec = 1; - int i = kevent(EngineHandle, NULL, 0, &ke_list[0], GetMaxFds(), &ts); + int i = kevent(EngineHandle, &changelist.front(), ChangePos, &ke_list.front(), ke_list.size(), &ts); + ChangePos = 0; ServerInstance->UpdateTime(); - TotalEvents += i; + if (i < 0) + return i; + + stats.TotalEvents += i; for (int j = 0; j < i; j++) { - EventHandler* eh = ref[ke_list[j].ident]; + struct kevent& kev = ke_list[j]; + EventHandler* eh = static_cast<EventHandler*>(kev.udata); if (!eh) continue; - if (ke_list[j].flags & EV_EOF) + + // Copy these in case the vector gets resized and kev invalidated + const int fd = eh->GetFd(); + const short filter = kev.filter; + if (fd < 0) + continue; + + if (kev.flags & EV_EOF) { - ErrorEvents++; - eh->HandleEvent(EVENT_ERROR, ke_list[j].fflags); + stats.ErrorEvents++; + eh->OnEventHandlerError(kev.fflags); continue; } - if (ke_list[j].filter == EVFILT_WRITE) + if (filter == EVFILT_WRITE) { - WriteEvents++; /* When mask is FD_WANT_FAST_WRITE or FD_WANT_SINGLE_WRITE, * we set a one-shot write, so we need to clear that bit * to detect when it set again. */ const int bits_to_clr = FD_WANT_SINGLE_WRITE | FD_WANT_FAST_WRITE | FD_WRITE_WILL_BLOCK; - SetEventMask(eh, eh->GetEventMask() & ~bits_to_clr); - eh->HandleEvent(EVENT_WRITE); - - if (eh != ref[ke_list[j].ident]) - // whoops, deleted out from under us - continue; + eh->SetEventMask(eh->GetEventMask() & ~bits_to_clr); + eh->OnEventHandlerWrite(); } - if (ke_list[j].filter == EVFILT_READ) + else if (filter == EVFILT_READ) { - ReadEvents++; - SetEventMask(eh, eh->GetEventMask() & ~FD_READ_WILL_BLOCK); - eh->HandleEvent(EVENT_READ); + eh->SetEventMask(eh->GetEventMask() & ~FD_READ_WILL_BLOCK); + eh->OnEventHandlerRead(); } } return i; } - -std::string KQueueEngine::GetName() -{ - return "kqueue"; -} - -SocketEngine* CreateSocketEngine() -{ - return new KQueueEngine; -} diff --git a/src/socketengines/socketengine_poll.cpp b/src/socketengines/socketengine_poll.cpp index e38e0fac1..339045a8c 100644 --- a/src/socketengines/socketengine_poll.cpp +++ b/src/socketengines/socketengine_poll.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2014 Adam <Adam@anope.org> * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> * Copyright (C) 2009 Uli Schlachter <psychon@znc.in> * Copyright (C) 2009 Craig Edwards <craigedwards@brainbox.cc> @@ -21,88 +22,33 @@ #include "inspircd.h" -#include "exitcodes.h" -#ifndef SOCKETENGINE_POLL -#define SOCKETENGINE_POLL - -#include <iostream> -#include <vector> -#include <string> -#include <map> -#include "inspircd_config.h" -#include "inspircd.h" -#include "socketengine.h" - -#ifndef _WIN32 -# ifndef __USE_XOPEN -# define __USE_XOPEN /* fuck every fucking OS ever made. needed by poll.h to work.*/ -# endif -# include <poll.h> -# include <sys/poll.h> -# include <sys/resource.h> -#else -# define struct pollfd WSAPOLLFD -# define poll WSAPoll -#endif - -class InspIRCd; +#include <sys/poll.h> +#include <sys/resource.h> /** A specialisation of the SocketEngine class, designed to use poll(). */ -class PollEngine : public SocketEngine +namespace { -private: /** These are used by poll() to hold socket events */ - struct pollfd *events; - /** This map maps fds to an index in the events array. - */ - std::map<int, unsigned int> fd_mappings; -public: - /** Create a new PollEngine + std::vector<struct pollfd> events(16); + /** This vector maps fds to an index in the events array. */ - PollEngine(); - /** Delete a PollEngine - */ - virtual ~PollEngine(); - virtual bool AddFd(EventHandler* eh, int event_mask); - virtual void OnSetEvent(EventHandler* eh, int old_mask, int new_mask); - virtual EventHandler* GetRef(int fd); - virtual void DelFd(EventHandler* eh); - virtual int DispatchEvents(); - virtual std::string GetName(); -}; - -#endif - -PollEngine::PollEngine() -{ - CurrentSetSize = 0; - struct rlimit limits; - if (!getrlimit(RLIMIT_NOFILE, &limits)) - { - MAX_DESCRIPTORS = limits.rlim_cur; - } - else - { - ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets: %s", strerror(errno)); - std::cout << "ERROR: Can't determine maximum number of open sockets: " << strerror(errno) << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } + std::vector<int> fd_mappings(16, -1); +} - ref = new EventHandler* [GetMaxFds()]; - events = new struct pollfd[GetMaxFds()]; +void SocketEngine::Init() +{ + LookupMaxFds(); +} - memset(events, 0, GetMaxFds() * sizeof(struct pollfd)); - memset(ref, 0, GetMaxFds() * sizeof(EventHandler*)); +void SocketEngine::Deinit() +{ } -PollEngine::~PollEngine() +void SocketEngine::RecoverFromFork() { - // No destruction required, either. - delete[] ref; - delete[] events; } static int mask_to_poll(int event_mask) @@ -115,71 +61,70 @@ static int mask_to_poll(int event_mask) return rv; } -bool PollEngine::AddFd(EventHandler* eh, int event_mask) +bool SocketEngine::AddFd(EventHandler* eh, int event_mask) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"AddFd out of range: (fd: %d, max: %d)", fd, GetMaxFds()); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "AddFd out of range: (fd: %d)", fd); return false; } - if (fd_mappings.find(fd) != fd_mappings.end()) + if (static_cast<unsigned int>(fd) < fd_mappings.size() && fd_mappings[fd] != -1) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"Attempt to add duplicate fd: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Attempt to add duplicate fd: %d", fd); return false; } unsigned int index = CurrentSetSize; + if (!SocketEngine::AddFdRef(eh)) + { + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Attempt to add duplicate fd: %d", fd); + return false; + } + + while (static_cast<unsigned int>(fd) >= fd_mappings.size()) + fd_mappings.resize(fd_mappings.size() * 2, -1); fd_mappings[fd] = index; - ref[index] = eh; + + ResizeDouble(events); events[index].fd = fd; events[index].events = mask_to_poll(event_mask); - ServerInstance->Logs->Log("SOCKET", DEBUG,"New file descriptor: %d (%d; index %d)", fd, events[index].events, index); - SocketEngine::SetEventMask(eh, event_mask); - CurrentSetSize++; + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "New file descriptor: %d (%d; index %d)", fd, events[index].events, index); + eh->SetEventMask(event_mask); return true; } -EventHandler* PollEngine::GetRef(int fd) -{ - std::map<int, unsigned int>::iterator it = fd_mappings.find(fd); - if (it == fd_mappings.end()) - return NULL; - return ref[it->second]; -} - -void PollEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) +void SocketEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) { - std::map<int, unsigned int>::iterator it = fd_mappings.find(eh->GetFd()); - if (it == fd_mappings.end()) + int fd = eh->GetFd(); + if (fd < 0 || static_cast<unsigned int>(fd) >= fd_mappings.size() || fd_mappings[fd] == -1) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"SetEvents() on unknown fd: %d", eh->GetFd()); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "SetEvents() on unknown fd: %d", eh->GetFd()); return; } - events[it->second].events = mask_to_poll(new_mask); + events[fd_mappings[fd]].events = mask_to_poll(new_mask); } -void PollEngine::DelFd(EventHandler* eh) +void SocketEngine::DelFd(EventHandler* eh) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > MAX_DESCRIPTORS)) + if (fd < 0) { - ServerInstance->Logs->Log("SOCKET", DEBUG, "DelFd out of range: (fd: %d, max: %d)", fd, GetMaxFds()); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "DelFd out of range: (fd: %d)", fd); return; } - std::map<int, unsigned int>::iterator it = fd_mappings.find(fd); - if (it == fd_mappings.end()) + if (static_cast<unsigned int>(fd) >= fd_mappings.size() || fd_mappings[fd] == -1) { - ServerInstance->Logs->Log("SOCKET",DEBUG,"DelFd() on unknown fd: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "DelFd() on unknown fd: %d", fd); return; } - unsigned int index = it->second; + unsigned int index = fd_mappings[fd]; unsigned int last_index = CurrentSetSize - 1; int last_fd = events[last_index].fd; @@ -193,89 +138,78 @@ void PollEngine::DelFd(EventHandler* eh) // move last_fd from last_index into index events[index].fd = last_fd; events[index].events = events[last_index].events; - - ref[index] = ref[last_index]; } // Now remove all data for the last fd we got into out list. // Above code made sure this always is right - fd_mappings.erase(it); + fd_mappings[fd] = -1; events[last_index].fd = 0; events[last_index].events = 0; - ref[last_index] = NULL; - CurrentSetSize--; + SocketEngine::DelFdRef(eh); - ServerInstance->Logs->Log("SOCKET", DEBUG, "Remove file descriptor: %d (index: %d) " + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Remove file descriptor: %d (index: %d) " "(Filled gap with: %d (index: %d))", fd, index, last_fd, last_index); } -int PollEngine::DispatchEvents() +int SocketEngine::DispatchEvents() { - int i = poll(events, CurrentSetSize, 1000); - int index; - socklen_t codesize = sizeof(int); - int errcode; + int i = poll(&events[0], CurrentSetSize, 1000); int processed = 0; ServerInstance->UpdateTime(); - if (i > 0) + for (size_t index = 0; index < CurrentSetSize && processed < i; index++) { - for (index = 0; index < CurrentSetSize && processed != i; index++) + struct pollfd& pfd = events[index]; + + // Copy these in case the vector gets resized and pfd invalidated + const int fd = pfd.fd; + const short revents = pfd.revents; + + if (revents) + processed++; + + EventHandler* eh = GetRef(fd); + if (!eh) + continue; + + if (revents & POLLHUP) { - if (events[index].revents) - processed++; - EventHandler* eh = ref[index]; - if (!eh) - continue; + eh->OnEventHandlerError(0); + continue; + } + + if (revents & POLLERR) + { + // Get error number + socklen_t codesize = sizeof(int); + int errcode; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) + errcode = errno; + eh->OnEventHandlerError(errcode); + continue; + } - if (events[index].revents & POLLHUP) - { - eh->HandleEvent(EVENT_ERROR, 0); + if (revents & POLLIN) + { + eh->SetEventMask(eh->GetEventMask() & ~FD_READ_WILL_BLOCK); + eh->OnEventHandlerRead(); + if (eh != GetRef(fd)) + // whoops, deleted out from under us continue; - } + } - if (events[index].revents & POLLERR) - { - // Get fd - int fd = events[index].fd; + if (revents & POLLOUT) + { + int mask = eh->GetEventMask(); + mask &= ~(FD_WRITE_WILL_BLOCK | FD_WANT_SINGLE_WRITE); + eh->SetEventMask(mask); - // Get error number - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) - errcode = errno; - eh->HandleEvent(EVENT_ERROR, errcode); - continue; - } - - if (events[index].revents & POLLIN) - { - SetEventMask(eh, eh->GetEventMask() & ~FD_READ_WILL_BLOCK); - eh->HandleEvent(EVENT_READ); - if (eh != ref[index]) - // whoops, deleted out from under us - continue; - } - - if (events[index].revents & POLLOUT) - { - int mask = eh->GetEventMask(); - mask &= ~(FD_WRITE_WILL_BLOCK | FD_WANT_SINGLE_WRITE); - SetEventMask(eh, mask); - events[index].events = mask_to_poll(mask); - eh->HandleEvent(EVENT_WRITE); - } + // The vector could've been resized, reference can be invalid by now; don't use it + events[index].events = mask_to_poll(mask); + eh->OnEventHandlerWrite(); } } return i; } - -std::string PollEngine::GetName() -{ - return "poll"; -} - -SocketEngine* CreateSocketEngine() -{ - return new PollEngine; -} diff --git a/src/socketengines/socketengine_ports.cpp b/src/socketengines/socketengine_ports.cpp deleted file mode 100644 index f7c547d45..000000000 --- a/src/socketengines/socketengine_ports.cpp +++ /dev/null @@ -1,220 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "exitcodes.h" -#include <port.h> - -#ifndef SOCKETENGINE_PORTS -#define SOCKETENGINE_PORTS - -#ifndef __sun -# error You need Solaris 10 or later to make use of this code. -#endif - -#include <vector> -#include <string> -#include <map> -#include "inspircd_config.h" -#include "inspircd.h" -#include "socketengine.h" -#include <port.h> -#include <iostream> - -/** A specialisation of the SocketEngine class, designed to use solaris 10 I/O completion ports - */ -class PortsEngine : public SocketEngine -{ -private: - /** These are used by epoll() to hold socket events - */ - port_event_t* events; - int EngineHandle; -public: - /** Create a new PortsEngine - */ - PortsEngine(); - /** Delete a PortsEngine - */ - virtual ~PortsEngine(); - virtual bool AddFd(EventHandler* eh, int event_mask); - virtual void OnSetEvent(EventHandler* eh, int old_mask, int new_mask); - virtual void DelFd(EventHandler* eh); - virtual int DispatchEvents(); - virtual std::string GetName(); -}; - -#endif - - -#include <ulimit.h> - -PortsEngine::PortsEngine() -{ - int max = ulimit(4, 0); - if (max > 0) - { - MAX_DESCRIPTORS = max; - } - else - { - ServerInstance->Logs->Log("SOCKET", DEFAULT, "ERROR: Can't determine maximum number of open sockets!"); - std::cout << "ERROR: Can't determine maximum number of open sockets!" << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } - EngineHandle = port_create(); - - if (EngineHandle == -1) - { - ServerInstance->Logs->Log("SOCKET",SPARSE,"ERROR: Could not initialize socket engine: %s", strerror(errno)); - ServerInstance->Logs->Log("SOCKET",SPARSE,"ERROR: This is a fatal error, exiting now."); - std::cout << "ERROR: Could not initialize socket engine: " << strerror(errno) << std::endl; - std::cout << "ERROR: This is a fatal error, exiting now." << std::endl; - ServerInstance->QuickExit(EXIT_STATUS_SOCKETENGINE); - } - CurrentSetSize = 0; - - ref = new EventHandler* [GetMaxFds()]; - events = new port_event_t[GetMaxFds()]; - memset(ref, 0, GetMaxFds() * sizeof(EventHandler*)); -} - -PortsEngine::~PortsEngine() -{ - this->Close(EngineHandle); - delete[] ref; - delete[] events; -} - -static int mask_to_events(int event_mask) -{ - int rv = 0; - if (event_mask & (FD_WANT_POLL_READ | FD_WANT_FAST_READ)) - rv |= POLLRDNORM; - if (event_mask & (FD_WANT_POLL_WRITE | FD_WANT_FAST_WRITE | FD_WANT_SINGLE_WRITE)) - rv |= POLLWRNORM; - return rv; -} - -bool PortsEngine::AddFd(EventHandler* eh, int event_mask) -{ - int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) - return false; - - if (ref[fd]) - return false; - - ref[fd] = eh; - SocketEngine::SetEventMask(eh, event_mask); - port_associate(EngineHandle, PORT_SOURCE_FD, fd, mask_to_events(event_mask), eh); - - ServerInstance->Logs->Log("SOCKET",DEBUG,"New file descriptor: %d", fd); - CurrentSetSize++; - return true; -} - -void PortsEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) -{ - if (mask_to_events(new_mask) != mask_to_events(old_mask)) - port_associate(EngineHandle, PORT_SOURCE_FD, eh->GetFd(), mask_to_events(new_mask), eh); -} - -void PortsEngine::DelFd(EventHandler* eh) -{ - int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) - return; - - port_dissociate(EngineHandle, PORT_SOURCE_FD, fd); - - CurrentSetSize--; - ref[fd] = NULL; - - ServerInstance->Logs->Log("SOCKET",DEBUG,"Remove file descriptor: %d", fd); -} - -int PortsEngine::DispatchEvents() -{ - struct timespec poll_time; - - poll_time.tv_sec = 1; - poll_time.tv_nsec = 0; - - unsigned int nget = 1; // used to denote a retrieve request. - int ret = port_getn(EngineHandle, this->events, GetMaxFds() - 1, &nget, &poll_time); - ServerInstance->UpdateTime(); - - // first handle an error condition - if (ret == -1) - return -1; - - TotalEvents += nget; - - unsigned int i; - for (i = 0; i < nget; i++) - { - switch (this->events[i].portev_source) - { - case PORT_SOURCE_FD: - { - int fd = this->events[i].portev_object; - EventHandler* eh = ref[fd]; - if (eh) - { - int mask = eh->GetEventMask(); - if (events[i].portev_events & POLLWRNORM) - mask &= ~(FD_WRITE_WILL_BLOCK | FD_WANT_FAST_WRITE | FD_WANT_SINGLE_WRITE); - if (events[i].portev_events & POLLRDNORM) - mask &= ~FD_READ_WILL_BLOCK; - // reinsert port for next time around, pretending to be one-shot for writes - SetEventMask(eh, mask); - port_associate(EngineHandle, PORT_SOURCE_FD, fd, mask_to_events(mask), eh); - if (events[i].portev_events & POLLRDNORM) - { - ReadEvents++; - eh->HandleEvent(EVENT_READ); - if (eh != ref[fd]) - continue; - } - if (events[i].portev_events & POLLWRNORM) - { - WriteEvents++; - eh->HandleEvent(EVENT_WRITE); - } - } - } - default: - break; - } - } - - return (int)i; -} - -std::string PortsEngine::GetName() -{ - return "ports"; -} - -SocketEngine* CreateSocketEngine() -{ - return new PortsEngine; -} diff --git a/src/socketengines/socketengine_select.cpp b/src/socketengines/socketengine_select.cpp index 0b5abaf30..03f0aca62 100644 --- a/src/socketengines/socketengine_select.cpp +++ b/src/socketengines/socketengine_select.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2014 Adam <Adam@anope.org> * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> * @@ -18,10 +19,7 @@ */ -#include "inspircd_config.h" - #include "inspircd.h" -#include "socketengine.h" #ifndef _WIN32 #include <sys/select.h> @@ -29,76 +27,63 @@ /** A specialisation of the SocketEngine class, designed to use traditional select(). */ -class SelectEngine : public SocketEngine +namespace { fd_set ReadSet, WriteSet, ErrSet; - int MaxFD; - -public: - /** Create a new SelectEngine - */ - SelectEngine(); - /** Delete a SelectEngine - */ - virtual ~SelectEngine(); - virtual bool AddFd(EventHandler* eh, int event_mask); - virtual void DelFd(EventHandler* eh); - void OnSetEvent(EventHandler* eh, int, int); - virtual int DispatchEvents(); - virtual std::string GetName(); -}; - -SelectEngine::SelectEngine() -{ - MAX_DESCRIPTORS = FD_SETSIZE; - CurrentSetSize = 0; + int MaxFD = 0; +} - ref = new EventHandler* [GetMaxFds()]; - memset(ref, 0, GetMaxFds() * sizeof(EventHandler*)); +void SocketEngine::Init() +{ + MaxSetSize = FD_SETSIZE; FD_ZERO(&ReadSet); FD_ZERO(&WriteSet); FD_ZERO(&ErrSet); - MaxFD = 0; } -SelectEngine::~SelectEngine() +void SocketEngine::Deinit() { - delete[] ref; } -bool SelectEngine::AddFd(EventHandler* eh, int event_mask) +void SocketEngine::RecoverFromFork() +{ +} + +bool SocketEngine::AddFd(EventHandler* eh, int event_mask) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + + if (fd < 0) return false; - if (ref[fd]) + if (static_cast<size_t>(fd) >= GetMaxFds()) return false; - ref[fd] = eh; + if (!SocketEngine::AddFdRef(eh)) + return false; - SocketEngine::SetEventMask(eh, event_mask); + eh->SetEventMask(event_mask); OnSetEvent(eh, 0, event_mask); FD_SET(fd, &ErrSet); if (fd > MaxFD) MaxFD = fd; - CurrentSetSize++; - - ServerInstance->Logs->Log("SOCKET",DEBUG,"New file descriptor: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "New file descriptor: %d", fd); return true; } -void SelectEngine::DelFd(EventHandler* eh) +void SocketEngine::DelFd(EventHandler* eh) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) + return; + + if (static_cast<size_t>(fd) >= GetMaxFds()) return; - CurrentSetSize--; - ref[fd] = NULL; + SocketEngine::DelFdRef(eh); FD_CLR(fd, &ReadSet); FD_CLR(fd, &WriteSet); @@ -106,10 +91,10 @@ void SelectEngine::DelFd(EventHandler* eh) if (fd == MaxFD) --MaxFD; - ServerInstance->Logs->Log("SOCKET",DEBUG,"Remove file descriptor: %d", fd); + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "Remove file descriptor: %d", fd); } -void SelectEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) +void SocketEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) { int fd = eh->GetFd(); int diff = old_mask ^ new_mask; @@ -130,7 +115,7 @@ void SelectEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) } } -int SelectEngine::DispatchEvents() +int SocketEngine::DispatchEvents() { timeval tval; tval.tv_sec = 1; @@ -141,63 +126,48 @@ int SelectEngine::DispatchEvents() int sresult = select(MaxFD + 1, &rfdset, &wfdset, &errfdset, &tval); ServerInstance->UpdateTime(); - /* Nothing to process this time around */ - if (sresult < 1) - return 0; - for (int i = 0, j = sresult; i <= MaxFD && j > 0; i++) { int has_read = FD_ISSET(i, &rfdset), has_write = FD_ISSET(i, &wfdset), has_error = FD_ISSET(i, &errfdset); - if (has_read || has_write || has_error) - { - --j; + if (!(has_read || has_write || has_error)) + continue; - EventHandler* ev = ref[i]; - if (!ev) - continue; + --j; - if (has_error) - { - ErrorEvents++; + EventHandler* ev = GetRef(i); + if (!ev) + continue; - socklen_t codesize = sizeof(int); - int errcode = 0; - if (getsockopt(i, SOL_SOCKET, SO_ERROR, (char*)&errcode, &codesize) < 0) - errcode = errno; + if (has_error) + { + stats.ErrorEvents++; + + socklen_t codesize = sizeof(int); + int errcode = 0; + if (getsockopt(i, SOL_SOCKET, SO_ERROR, (char*)&errcode, &codesize) < 0) + errcode = errno; + + ev->OnEventHandlerError(errcode); + continue; + } - ev->HandleEvent(EVENT_ERROR, errcode); + if (has_read) + { + ev->SetEventMask(ev->GetEventMask() & ~FD_READ_WILL_BLOCK); + ev->OnEventHandlerRead(); + if (ev != GetRef(i)) continue; - } - - if (has_read) - { - ReadEvents++; - SetEventMask(ev, ev->GetEventMask() & ~FD_READ_WILL_BLOCK); - ev->HandleEvent(EVENT_READ); - if (ev != ref[i]) - continue; - } - if (has_write) - { - WriteEvents++; - int newmask = (ev->GetEventMask() & ~(FD_WRITE_WILL_BLOCK | FD_WANT_SINGLE_WRITE)); - this->OnSetEvent(ev, ev->GetEventMask(), newmask); - SetEventMask(ev, newmask); - ev->HandleEvent(EVENT_WRITE); - } + } + + if (has_write) + { + int newmask = (ev->GetEventMask() & ~(FD_WRITE_WILL_BLOCK | FD_WANT_SINGLE_WRITE)); + SocketEngine::OnSetEvent(ev, ev->GetEventMask(), newmask); + ev->SetEventMask(newmask); + ev->OnEventHandlerWrite(); } } return sresult; } - -std::string SelectEngine::GetName() -{ - return "select"; -} - -SocketEngine* CreateSocketEngine() -{ - return new SelectEngine; -} diff --git a/src/testsuite.cpp b/src/testsuite.cpp index 58b72ee3e..a7a9ec99b 100644 --- a/src/testsuite.cpp +++ b/src/testsuite.cpp @@ -19,11 +19,10 @@ */ -/* $Core */ +#ifdef INSPIRCD_ENABLE_TESTSUITE #include "inspircd.h" #include "testsuite.h" -#include "threadengine.h" #include <iostream> class TestSuiteThread : public Thread @@ -76,8 +75,12 @@ TestSuite::TestSuite() switch (choice) { case '1': - FOREACH_MOD(I_OnRunTestSuite, OnRunTestSuite()); + { + const ModuleManager::ModuleMap& mods = ServerInstance->Modules->GetModules(); + for (ModuleManager::ModuleMap::const_iterator i = mods.begin(); i != mods.end(); ++i) + i->second->OnRunTestSuite(); break; + } case '2': std::cout << "Enter module filename to load: "; std::cin >> modname; @@ -331,36 +334,25 @@ bool TestSuite::DoThreadTests() bool TestSuite::DoGenerateUIDTests() { - bool success = RealGenerateUIDTests(); + const unsigned int UUID_LENGTH = UIDGenerator::UUID_LENGTH; + UIDGenerator uidgen; + uidgen.init(ServerInstance->Config->GetSID()); + std::string first_uid = uidgen.GetUID(); - // Reset the UID generation state so running the tests multiple times won't mess things up - for (unsigned int i = 0; i < 3; i++) - ServerInstance->current_uid[i] = ServerInstance->Config->sid[i]; - for (unsigned int i = 3; i < UUID_LENGTH-1; i++) - ServerInstance->current_uid[i] = '9'; - - ServerInstance->current_uid[UUID_LENGTH-1] = '\0'; - - return success; -} - -bool TestSuite::RealGenerateUIDTests() -{ - std::string first_uid = ServerInstance->GetUID(); - if (first_uid.length() != UUID_LENGTH-1) + if (first_uid.length() != UUID_LENGTH) { std::cout << "GENERATEUID: Generated UID is " << first_uid.length() << " characters long instead of " << UUID_LENGTH-1 << std::endl; return false; } - if (ServerInstance->current_uid[UUID_LENGTH-1] != '\0') + if (uidgen.current_uid.c_str()[UUID_LENGTH] != '\0') { std::cout << "GENERATEUID: The null terminator is missing from the end of current_uid" << std::endl; return false; } // The correct UID when generating one for the first time is ...AAAAAA - std::string correct_uid = ServerInstance->Config->sid + std::string(UUID_LENGTH - 4, 'A'); + std::string correct_uid = ServerInstance->Config->sid + std::string(UUID_LENGTH - 3, 'A'); if (first_uid != correct_uid) { std::cout << "GENERATEUID: Generated an invalid first UID: " << first_uid << " instead of " << correct_uid << std::endl; @@ -368,16 +360,16 @@ bool TestSuite::RealGenerateUIDTests() } // Set current_uid to be ...Z99999 - ServerInstance->current_uid[3] = 'Z'; - for (unsigned int i = 4; i < UUID_LENGTH-1; i++) - ServerInstance->current_uid[i] = '9'; + uidgen.current_uid[3] = 'Z'; + for (unsigned int i = 4; i < UUID_LENGTH; i++) + uidgen.current_uid[i] = '9'; // Store the UID we'll be incrementing so we can display what's wrong later if necessary - std::string before_increment(ServerInstance->current_uid); - std::string generated_uid = ServerInstance->GetUID(); + std::string before_increment(uidgen.current_uid); + std::string generated_uid = uidgen.GetUID(); // Correct UID after incrementing ...Z99999 is ...0AAAAA - correct_uid = ServerInstance->Config->sid + "0" + std::string(UUID_LENGTH - 5, 'A'); + correct_uid = ServerInstance->Config->sid + "0" + std::string(UUID_LENGTH - 4, 'A'); if (generated_uid != correct_uid) { @@ -386,11 +378,11 @@ bool TestSuite::RealGenerateUIDTests() } // Set current_uid to be ...999999 to see if it rolls over correctly - for (unsigned int i = 3; i < UUID_LENGTH-1; i++) - ServerInstance->current_uid[i] = '9'; + for (unsigned int i = 3; i < UUID_LENGTH; i++) + uidgen.current_uid[i] = '9'; - before_increment.assign(ServerInstance->current_uid); - generated_uid = ServerInstance->GetUID(); + before_increment.assign(uidgen.current_uid); + generated_uid = uidgen.GetUID(); // Correct UID after rolling over is the first UID we've generated (...AAAAAA) if (generated_uid != first_uid) @@ -407,3 +399,4 @@ TestSuite::~TestSuite() std::cout << "\n\n*** END OF TEST SUITE ***\n"; } +#endif diff --git a/src/threadengine.cpp b/src/threadengine.cpp index 8f1895c0f..f757aa56c 100644 --- a/src/threadengine.cpp +++ b/src/threadengine.cpp @@ -17,14 +17,7 @@ */ -/* $Core */ - -/********* DEFAULTS **********/ -/* $ExtraSources: threadengines/threadengine_pthread.cpp */ -/* $ExtraObjects: threadengine_pthread.o */ - #include "inspircd.h" -#include "threadengine.h" void Thread::SetExitFlag() { @@ -33,14 +26,5 @@ void Thread::SetExitFlag() void Thread::join() { - state->FreeThread(this); - delete state; - state = 0; -} - -/** If this thread has a Creator set, call it to - * free the thread - */ -Thread::~Thread() -{ + ServerInstance->Threads.Stop(this); } diff --git a/src/threadengines/threadengine_pthread.cpp b/src/threadengines/threadengine_pthread.cpp index 40205da31..3249f442b 100644 --- a/src/threadengines/threadengine_pthread.cpp +++ b/src/threadengines/threadengine_pthread.cpp @@ -21,13 +21,8 @@ #include "inspircd.h" #include "threadengines/threadengine_pthread.h" #include <pthread.h> -#include <signal.h> #include <fcntl.h> -ThreadEngine::ThreadEngine() -{ -} - static void* entry_point(void* parameter) { /* Recommended by nenolod, signal safety on a per-thread basis */ @@ -44,25 +39,14 @@ static void* entry_point(void* parameter) void ThreadEngine::Start(Thread* thread) { - ThreadData* data = new ThreadData; - thread->state = data; - - if (pthread_create(&data->pthread_id, NULL, entry_point, thread) != 0) - { - thread->state = NULL; - delete data; + if (pthread_create(&thread->state.pthread_id, NULL, entry_point, thread) != 0) throw CoreException("Unable to create new thread: " + std::string(strerror(errno))); - } } -ThreadEngine::~ThreadEngine() -{ -} - -void ThreadData::FreeThread(Thread* thread) +void ThreadEngine::Stop(Thread* thread) { thread->SetExitFlag(); - pthread_join(pthread_id, NULL); + pthread_join(thread->state.pthread_id, NULL); } #ifdef HAS_EVENTFD @@ -75,13 +59,12 @@ class ThreadSignalSocket : public EventHandler ThreadSignalSocket(SocketThread* p, int newfd) : parent(p) { SetFd(newfd); - ServerInstance->SE->AddFd(this, FD_WANT_FAST_READ | FD_WANT_NO_WRITE); + SocketEngine::AddFd(this, FD_WANT_FAST_READ | FD_WANT_NO_WRITE); } ~ThreadSignalSocket() { - ServerInstance->SE->DelFd(this); - ServerInstance->SE->Close(GetFd()); + SocketEngine::Close(this); } void Notify() @@ -89,18 +72,21 @@ class ThreadSignalSocket : public EventHandler eventfd_write(fd, 1); } - void HandleEvent(EventType et, int errornum) + void OnEventHandlerRead() CXX11_OVERRIDE { - if (et == EVENT_READ) - { - eventfd_t dummy; - eventfd_read(fd, &dummy); - parent->OnNotify(); - } - else - { - ServerInstance->GlobalCulls.AddItem(this); - } + eventfd_t dummy; + eventfd_read(fd, &dummy); + parent->OnNotify(); + } + + void OnEventHandlerWrite() CXX11_OVERRIDE + { + ServerInstance->GlobalCulls.AddItem(this); + } + + void OnEventHandlerError(int errcode) CXX11_OVERRIDE + { + ThreadSignalSocket::OnEventHandlerWrite(); } }; @@ -123,15 +109,14 @@ class ThreadSignalSocket : public EventHandler parent(p), send_fd(sendfd) { SetFd(recvfd); - ServerInstance->SE->NonBlocking(fd); - ServerInstance->SE->AddFd(this, FD_WANT_FAST_READ | FD_WANT_NO_WRITE); + SocketEngine::NonBlocking(fd); + SocketEngine::AddFd(this, FD_WANT_FAST_READ | FD_WANT_NO_WRITE); } ~ThreadSignalSocket() { close(send_fd); - ServerInstance->SE->DelFd(this); - ServerInstance->SE->Close(GetFd()); + SocketEngine::Close(this); } void Notify() @@ -140,18 +125,21 @@ class ThreadSignalSocket : public EventHandler write(send_fd, &dummy, 1); } - void HandleEvent(EventType et, int errornum) + void OnEventHandlerRead() CXX11_OVERRIDE + { + char dummy[128]; + read(fd, dummy, 128); + parent->OnNotify(); + } + + void OnEventHandlerWrite() CXX11_OVERRIDE + { + ServerInstance->GlobalCulls.AddItem(this); + } + + void OnEventHandlerError(int errcode) CXX11_OVERRIDE { - if (et == EVENT_READ) - { - char dummy[128]; - read(fd, dummy, 128); - parent->OnNotify(); - } - else - { - ServerInstance->GlobalCulls.AddItem(this); - } + ThreadSignalSocket::OnEventHandlerWrite(); } }; diff --git a/src/threadengines/threadengine_win32.cpp b/src/threadengines/threadengine_win32.cpp index ea37892f8..0f0d1f277 100644 --- a/src/threadengines/threadengine_win32.cpp +++ b/src/threadengines/threadengine_win32.cpp @@ -21,33 +21,19 @@ #include "inspircd.h" #include "threadengines/threadengine_win32.h" -ThreadEngine::ThreadEngine() -{ -} - void ThreadEngine::Start(Thread* thread) { - ThreadData* data = new ThreadData; - thread->state = data; - - DWORD ThreadId = 0; - data->handle = CreateThread(NULL,0,ThreadEngine::Entry,thread,0,&ThreadId); + thread->state.handle = CreateThread(NULL, 0, ThreadEngine::Entry, thread, 0, NULL); - if (data->handle == NULL) + if (thread->state.handle == NULL) { DWORD lasterr = GetLastError(); - thread->state = NULL; - delete data; std::string err = "Unable to create new thread: " + ConvToStr(lasterr); SetLastError(ERROR_SUCCESS); throw CoreException(err); } } -ThreadEngine::~ThreadEngine() -{ -} - DWORD WINAPI ThreadEngine::Entry(void* parameter) { Thread* pt = static_cast<Thread*>(parameter); @@ -55,9 +41,10 @@ DWORD WINAPI ThreadEngine::Entry(void* parameter) return 0; } -void ThreadData::FreeThread(Thread* thread) +void ThreadEngine::Stop(Thread* thread) { thread->SetExitFlag(); + HANDLE handle = thread->state.handle; WaitForSingleObject(handle,INFINITE); CloseHandle(handle); } @@ -83,6 +70,24 @@ class ThreadSignalSocket : public BufferedSocket } }; +static bool BindAndListen(int sockfd, int port, const char* addr) +{ + irc::sockets::sockaddrs servaddr; + if (!irc::sockets::aptosa(addr, port, servaddr)) + return false; + + if (SocketEngine::Bind(sockfd, servaddr) != 0) + return false; + + if (SocketEngine::Listen(sockfd, ServerInstance->Config->MaxConn) != 0) + { + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR in listen(): %s", strerror(errno)); + return false; + } + + return true; +} + SocketThread::SocketThread() { int listenFD = socket(AF_INET, SOCK_STREAM, 0); @@ -92,22 +97,22 @@ SocketThread::SocketThread() if (connFD == -1) throw CoreException("Could not create ITC pipe"); - if (!ServerInstance->BindSocket(listenFD, 0, "127.0.0.1", true)) + if (!BindAndListen(listenFD, 0, "127.0.0.1")) throw CoreException("Could not create ITC pipe"); - ServerInstance->SE->NonBlocking(connFD); + SocketEngine::NonBlocking(connFD); struct sockaddr_in addr; socklen_t sz = sizeof(addr); getsockname(listenFD, reinterpret_cast<struct sockaddr*>(&addr), &sz); connect(connFD, reinterpret_cast<struct sockaddr*>(&addr), sz); - ServerInstance->SE->Blocking(listenFD); + SocketEngine::Blocking(listenFD); int nfd = accept(listenFD, reinterpret_cast<struct sockaddr*>(&addr), &sz); if (nfd < 0) throw CoreException("Could not create ITC pipe"); new ThreadSignalSocket(this, nfd); closesocket(listenFD); - ServerInstance->SE->Blocking(connFD); + SocketEngine::Blocking(connFD); this->signal.connFD = connFD; } diff --git a/src/timer.cpp b/src/timer.cpp index e04a186cf..48ad79df7 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -20,60 +20,64 @@ */ -/* $Core */ - #include "inspircd.h" -#include "timer.h" -TimerManager::TimerManager() +void Timer::SetInterval(unsigned int newinterval) { + ServerInstance->Timers.DelTimer(this); + secs = newinterval; + SetTrigger(ServerInstance->Time() + newinterval); + ServerInstance->Timers.AddTimer(this); } -TimerManager::~TimerManager() +Timer::Timer(unsigned int secs_from_now, bool repeating) + : trigger(ServerInstance->Time() + secs_from_now) + , secs(secs_from_now) + , repeat(repeating) { - for(std::vector<Timer *>::iterator i = Timers.begin(); i != Timers.end(); i++) - delete *i; +} + +Timer::~Timer() +{ + ServerInstance->Timers.DelTimer(this); } void TimerManager::TickTimers(time_t TIME) { - while ((Timers.size()) && (TIME > (*Timers.begin())->GetTimer())) + for (TimerMap::iterator i = Timers.begin(); i != Timers.end(); ) { - std::vector<Timer *>::iterator i = Timers.begin(); - Timer *t = (*i); + Timer* t = i->second; + if (t->GetTrigger() > TIME) + break; + + Timers.erase(i++); - // Probable fix: move vector manipulation to *before* we modify the vector. - Timers.erase(i); + if (!t->Tick(TIME)) + continue; - t->Tick(TIME); if (t->GetRepeat()) { - t->SetTimer(TIME + t->GetSecs()); + t->SetTrigger(TIME + t->GetInterval()); AddTimer(t); } - else - delete t; } } -void TimerManager::DelTimer(Timer* T) +void TimerManager::DelTimer(Timer* t) { - std::vector<Timer *>::iterator i = std::find(Timers.begin(), Timers.end(), T); + std::pair<TimerMap::iterator, TimerMap::iterator> itpair = Timers.equal_range(t->GetTrigger()); - if (i != Timers.end()) + for (TimerMap::iterator i = itpair.first; i != itpair.second; ++i) { - delete (*i); - Timers.erase(i); + if (i->second == t) + { + Timers.erase(i); + break; + } } } -void TimerManager::AddTimer(Timer* T) -{ - Timers.push_back(T); - std::sort(Timers.begin(), Timers.end(), TimerManager::TimerComparison); -} - -bool TimerManager::TimerComparison( Timer *one, Timer *two) +void TimerManager::AddTimer(Timer* t) { - return (one->GetTimer()) < (two->GetTimer()); + Timers.insert(std::make_pair(t->GetTrigger(), t)); } diff --git a/src/user_resolver.cpp b/src/user_resolver.cpp deleted file mode 100644 index f18fc9a03..000000000 --- a/src/user_resolver.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -UserResolver::UserResolver(LocalUser* user, std::string to_resolve, QueryType qt, bool &cache) : - Resolver(to_resolve, qt, cache, NULL), uuid(user->uuid) -{ - this->fwd = (qt == DNS_QUERY_A || qt == DNS_QUERY_AAAA); -} - -void UserResolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) -{ - UserResolver *res_forward; // for forward-resolution - LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid); - if (!bound_user) - { - ServerInstance->Logs->Log("RESOLVER", DEBUG, "Resolution finished for user '%s' who is gone", uuid.c_str()); - return; - } - - ServerInstance->Logs->Log("RESOLVER", DEBUG, "DNS result for %s: '%s' -> '%s'", uuid.c_str(), input.c_str(), result.c_str()); - - if (!fwd) - { - // first half of resolution is done. We now need to verify that the host matches. - bound_user->stored_host = result; - try - { - /* Check we didnt time out */ - if (bound_user->registered != REG_ALL) - { - bool lcached = false; - if (bound_user->client_sa.sa.sa_family == AF_INET6) - { - /* IPV6 forward lookup */ - res_forward = new UserResolver(bound_user, result, DNS_QUERY_AAAA, lcached); - } - else - { - /* IPV4 lookup */ - res_forward = new UserResolver(bound_user, result, DNS_QUERY_A, lcached); - } - ServerInstance->AddResolver(res_forward, lcached); - } - } - catch (CoreException& e) - { - ServerInstance->Logs->Log("RESOLVER", DEBUG,"Error in resolver: %s",e.GetReason()); - } - } - else - { - /* Both lookups completed */ - - irc::sockets::sockaddrs* user_ip = &bound_user->client_sa; - bool rev_match = false; - if (user_ip->sa.sa_family == AF_INET6) - { - struct in6_addr res_bin; - if (inet_pton(AF_INET6, result.c_str(), &res_bin)) - { - rev_match = !memcmp(&user_ip->in6.sin6_addr, &res_bin, sizeof(res_bin)); - } - } - else - { - struct in_addr res_bin; - if (inet_pton(AF_INET, result.c_str(), &res_bin)) - { - rev_match = !memcmp(&user_ip->in4.sin_addr, &res_bin, sizeof(res_bin)); - } - } - - if (rev_match) - { - std::string hostname = bound_user->stored_host; - if (hostname.length() < 65) - { - /* Check we didnt time out */ - if ((bound_user->registered != REG_ALL) && (!bound_user->dns_done)) - { - /* Hostnames starting with : are not a good thing (tm) */ - if (hostname[0] == ':') - hostname.insert(0, "0"); - - bound_user->WriteServ("NOTICE Auth :*** Found your hostname (%s)%s", hostname.c_str(), (cached ? " -- cached" : "")); - bound_user->dns_done = true; - bound_user->dhost.assign(hostname, 0, 64); - bound_user->host.assign(hostname, 0, 64); - /* Invalidate cache */ - bound_user->InvalidateCache(); - } - } - else - { - if (!bound_user->dns_done) - { - bound_user->WriteServ("NOTICE Auth :*** Your hostname is longer than the maximum of 64 characters, using your IP address (%s) instead.", bound_user->GetIPString()); - bound_user->dns_done = true; - } - } - } - else - { - if (!bound_user->dns_done) - { - bound_user->WriteServ("NOTICE Auth :*** Your hostname does not match up with your IP address. Sorry, using your IP address (%s) instead.", bound_user->GetIPString()); - bound_user->dns_done = true; - } - } - - // Save some memory by freeing this up; it's never used again in the user's lifetime. - bound_user->stored_host.resize(0); - } -} - -void UserResolver::OnError(ResolverError e, const std::string &errormessage) -{ - LocalUser* bound_user = (LocalUser*)ServerInstance->FindUUID(uuid); - if (bound_user) - { - bound_user->WriteServ("NOTICE Auth :*** Could not resolve your hostname: %s; using your IP address (%s) instead.", errormessage.c_str(), bound_user->GetIPString()); - bound_user->dns_done = true; - bound_user->stored_host.resize(0); - ServerInstance->stats->statsDnsBad++; - } -} diff --git a/src/usermanager.cpp b/src/usermanager.cpp index 2cb7ad511..7466f385b 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -22,83 +22,95 @@ #include "inspircd.h" #include "xline.h" -#include "bancache.h" +#include "iohook.h" + +namespace +{ + class WriteCommonQuit : public User::ForEachNeighborHandler + { + ClientProtocol::Messages::Quit quitmsg; + ClientProtocol::Event quitevent; + ClientProtocol::Messages::Quit operquitmsg; + ClientProtocol::Event operquitevent; + + void Execute(LocalUser* user) CXX11_OVERRIDE + { + user->Send(user->IsOper() ? operquitevent : quitevent); + } + + public: + WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg) + : quitmsg(user, msg) + , quitevent(ServerInstance->GetRFCEvents().quit, quitmsg) + , operquitmsg(user, opermsg) + , operquitevent(ServerInstance->GetRFCEvents().quit, operquitmsg) + { + user->ForEachNeighbor(*this, false); + } + }; +} UserManager::UserManager() - : unregistered_count(0), local_count(0) + : already_sent_id(0) + , unregistered_count(0) { } -/* add a client connection to the sockets list */ -void UserManager::AddUser(int socket, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) +UserManager::~UserManager() { - /* NOTE: Calling this one parameter constructor for User automatically - * allocates a new UUID and places it in the hash_map. - */ - LocalUser* New = NULL; - try + for (user_hash::iterator i = clientlist.begin(); i != clientlist.end(); ++i) { - New = new LocalUser(socket, client, server); + delete i->second; } - catch (...) +} + +void UserManager::AddUser(int socket, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) +{ + // User constructor allocates a new UUID for the user and inserts it into the uuidlist + LocalUser* const New = new LocalUser(socket, client, server); + UserIOHandler* eh = &New->eh; + + ServerInstance->Logs->Log("USERS", LOG_DEBUG, "New user fd: %d", socket); + + this->unregistered_count++; + this->clientlist[New->nick] = New; + this->AddClone(New); + this->local_users.push_front(New); + + if (!SocketEngine::AddFd(eh, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE)) { - ServerInstance->Logs->Log("USERS", DEFAULT,"*** WTF *** Duplicated UUID! -- Crack smoking monkeys have been unleashed."); - ServerInstance->SNO->WriteToSnoMask('a', "WARNING *** Duplicate UUID allocated!"); + ServerInstance->Logs->Log("USERS", LOG_DEBUG, "Internal error on new connection"); + this->QuitUser(New, "Internal error handling connection"); return; } - UserIOHandler* eh = &New->eh; - /* Give each of the modules an attempt to hook the user for I/O */ - FOREACH_MOD(I_OnHookIO, OnHookIO(eh, via)); - - if (eh->GetIOHook()) + // If this listener has an IO hook provider set then tell it about the connection + for (ListenSocket::IOHookProvList::iterator i = via->iohookprovs.begin(); i != via->iohookprovs.end(); ++i) { - try - { - eh->GetIOHook()->OnStreamSocketAccept(eh, client, server); - } - catch (CoreException& modexcept) + ListenSocket::IOHookProvRef& iohookprovref = *i; + if (!iohookprovref) + continue; + + iohookprovref->OnAccept(eh, client, server); + // IOHook could have encountered a fatal error, e.g. if the TLS ClientHello was already in the queue and there was no common TLS version + if (!eh->getError().empty()) { - ServerInstance->Logs->Log("SOCKET", DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason()); + QuitUser(New, eh->getError()); + return; } } - ServerInstance->Logs->Log("USERS", DEBUG,"New user fd: %d", socket); - - this->unregistered_count++; - - /* The users default nick is their UUID */ - New->nick = New->uuid; - (*(this->clientlist))[New->nick] = New; - - New->registered = REG_NONE; - New->signon = ServerInstance->Time(); - New->lastping = 1; - - ServerInstance->Users->AddLocalClone(New); - ServerInstance->Users->AddGlobalClone(New); - - New->localuseriter = this->local_users.insert(local_users.end(), New); - local_count++; - - if ((this->local_users.size() > ServerInstance->Config->SoftLimit) || (this->local_users.size() >= (unsigned int)ServerInstance->SE->GetMaxFds())) + if (this->local_users.size() > ServerInstance->Config->SoftLimit) { ServerInstance->SNO->WriteToSnoMask('a', "Warning: softlimit value has been reached: %d clients", ServerInstance->Config->SoftLimit); this->QuitUser(New,"No more connections allowed"); return; } - /* - * First class check. We do this again in FullConnect after DNS is done, and NICK/USER is recieved. - * See my note down there for why this is required. DO NOT REMOVE. :) -- w00t - */ + // First class check. We do this again in LocalUser::FullConnect() after DNS is done, and NICK/USER is received. New->SetClass(); - - /* - * Check connect class settings and initialise settings into User. - * This will be done again after DNS resolution. -- w00t - */ - New->CheckClass(); + // If the user doesn't have an acceptable connect class CheckClass() quits them + New->CheckClass(ServerInstance->Config->CCOnConnect); if (New->quitting) return; @@ -109,20 +121,21 @@ void UserManager::AddUser(int socket, ListenSocket* via, irc::sockets::sockaddrs */ New->exempt = (ServerInstance->XLines->MatchesLine("E",New) != NULL); - if (BanCacheHit *b = ServerInstance->BanCache->GetHit(New->GetIPString())) + BanCacheHit* const b = ServerInstance->BanCache.GetHit(New->GetIPString()); + if (b) { if (!b->Type.empty() && !New->exempt) { /* user banned */ - ServerInstance->Logs->Log("BANCACHE", DEBUG, std::string("BanCache: Positive hit for ") + New->GetIPString()); - if (!ServerInstance->Config->MoronBanner.empty()) - New->WriteServ("NOTICE %s :*** %s", New->nick.c_str(), ServerInstance->Config->MoronBanner.c_str()); + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Positive hit for " + New->GetIPString()); + if (!ServerInstance->Config->XLineMessage.empty()) + New->WriteNumeric(ERR_YOUREBANNEDCREEP, ServerInstance->Config->XLineMessage); this->QuitUser(New, b->Reason); return; } else { - ServerInstance->Logs->Log("BANCACHE", DEBUG, std::string("BanCache: Negative hit for ") + New->GetIPString()); + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Negative hit for " + New->GetIPString()); } } else @@ -139,277 +152,226 @@ void UserManager::AddUser(int socket, ListenSocket* via, irc::sockets::sockaddrs } } - if (!ServerInstance->SE->AddFd(eh, FD_WANT_FAST_READ | FD_WANT_EDGE_WRITE)) - { - ServerInstance->Logs->Log("USERS", DEBUG,"Internal error on new connection"); - this->QuitUser(New, "Internal error handling connection"); - return; - } - - /* NOTE: even if dns lookups are *off*, we still need to display this. - * BOPM and other stuff requires it. - */ - New->WriteServ("NOTICE Auth :*** Looking up your hostname..."); if (ServerInstance->Config->RawLog) - New->WriteServ("NOTICE Auth :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded."); + New->WriteNotice("*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded."); - FOREACH_MOD(I_OnSetUserIP,OnSetUserIP(New)); + FOREACH_MOD(OnSetUserIP, (New)); if (New->quitting) return; - FOREACH_MOD(I_OnUserInit,OnUserInit(New)); - - if (ServerInstance->Config->NoUserDns) - { - New->WriteServ("NOTICE %s :*** Skipping host resolution (disabled by server administrator)", New->nick.c_str()); - New->dns_done = true; - } - else - { - New->StartDNSLookup(); - } + FOREACH_MOD(OnUserInit, (New)); } -void UserManager::QuitUser(User *user, const std::string &quitreason, const char* operreason) +void UserManager::QuitUser(User* user, const std::string& quitreason, const std::string* operreason) { if (user->quitting) { - ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Tried to quit quitting user: " + user->nick); + ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Tried to quit quitting user: " + user->nick); return; } if (IS_SERVER(user)) { - ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Tried to quit server user: " + user->nick); + ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Tried to quit server user: " + user->nick); return; } user->quitting = true; - ServerInstance->Logs->Log("USERS", DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str()); - user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->host.c_str(), *operreason ? operreason : quitreason.c_str()); + ServerInstance->Logs->Log("USERS", LOG_DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str()); + LocalUser* const localuser = IS_LOCAL(user); + if (localuser) + { + ClientProtocol::Messages::Error errormsg(InspIRCd::Format("Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str())); + localuser->Send(ServerInstance->GetRFCEvents().error, errormsg); + } std::string reason; - std::string oper_reason; reason.assign(quitreason, 0, ServerInstance->Config->Limits.MaxQuit); - if (operreason && *operreason) - oper_reason.assign(operreason, 0, ServerInstance->Config->Limits.MaxQuit); - else - oper_reason = quitreason; + if (!operreason) + operreason = &reason; ServerInstance->GlobalCulls.AddItem(user); if (user->registered == REG_ALL) { - FOREACH_MOD(I_OnUserQuit,OnUserQuit(user, reason, oper_reason)); - user->WriteCommonQuit(reason, oper_reason); + FOREACH_MOD(OnUserQuit, (user, reason, *operreason)); + WriteCommonQuit(user, reason, *operreason); } - - if (user->registered != REG_ALL) - if (ServerInstance->Users->unregistered_count) - ServerInstance->Users->unregistered_count--; + else + unregistered_count--; if (IS_LOCAL(user)) { LocalUser* lu = IS_LOCAL(user); - FOREACH_MOD(I_OnUserDisconnect,OnUserDisconnect(lu)); + FOREACH_MOD(OnUserDisconnect, (lu)); lu->eh.Close(); - } - /* - * this must come before the ServerInstance->SNO->WriteToSnoMaskso that it doesnt try to fill their buffer with anything - * if they were an oper with +s +qQ. - */ - if (user->registered == REG_ALL) - { - if (IS_LOCAL(user)) - { - if (!user->quietquit) - { - ServerInstance->SNO->WriteToSnoMask('q',"Client exiting: %s (%s) [%s]", - user->GetFullRealHost().c_str(), user->GetIPString(), oper_reason.c_str()); - } - } - else - { - if ((!ServerInstance->SilentULine(user->server)) && (!user->quietquit)) - { - ServerInstance->SNO->WriteToSnoMask('Q',"Client exiting on server %s: %s (%s) [%s]", - user->server.c_str(), user->GetFullRealHost().c_str(), user->GetIPString(), oper_reason.c_str()); - } - } - user->AddToWhoWas(); + if (lu->registered == REG_ALL) + ServerInstance->SNO->WriteToSnoMask('q',"Client exiting: %s (%s) [%s]", user->GetFullRealHost().c_str(), user->GetIPString().c_str(), operreason->c_str()); + local_users.erase(lu); } - user_hash::iterator iter = this->clientlist->find(user->nick); - - if (iter != this->clientlist->end()) - this->clientlist->erase(iter); - else - ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Nick not found in clientlist, cannot remove: " + user->nick); - - ServerInstance->Users->uuidlist->erase(user->uuid); -} - + if (!clientlist.erase(user->nick)) + ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Nick not found in clientlist, cannot remove: " + user->nick); -void UserManager::AddLocalClone(User *user) -{ - local_clones[user->GetCIDRMask()]++; + uuidlist.erase(user->uuid); + user->PurgeEmptyChannels(); } -void UserManager::AddGlobalClone(User *user) +void UserManager::AddClone(User* user) { - global_clones[user->GetCIDRMask()]++; + CloneCounts& counts = clonemap[user->GetCIDRMask()]; + counts.global++; + if (IS_LOCAL(user)) + counts.local++; } void UserManager::RemoveCloneCounts(User *user) { - if (IS_LOCAL(user)) + CloneMap::iterator it = clonemap.find(user->GetCIDRMask()); + if (it != clonemap.end()) { - clonemap::iterator x = local_clones.find(user->GetCIDRMask()); - if (x != local_clones.end()) + CloneCounts& counts = it->second; + counts.global--; + if (counts.global == 0) { - x->second--; - if (!x->second) - { - local_clones.erase(x); - } + // No more users from this IP, remove entry from the map + clonemap.erase(it); + return; } - } - clonemap::iterator y = global_clones.find(user->GetCIDRMask()); - if (y != global_clones.end()) - { - y->second--; - if (!y->second) - { - global_clones.erase(y); - } + if (IS_LOCAL(user)) + counts.local--; } } void UserManager::RehashCloneCounts() { - local_clones.clear(); - global_clones.clear(); + clonemap.clear(); - const user_hash& hash = *ServerInstance->Users->clientlist; + const user_hash& hash = ServerInstance->Users.GetUsers(); for (user_hash::const_iterator i = hash.begin(); i != hash.end(); ++i) { User* u = i->second; - - if (IS_LOCAL(u)) - AddLocalClone(u); - AddGlobalClone(u); + AddClone(u); } } -unsigned long UserManager::GlobalCloneCount(User *user) +const UserManager::CloneCounts& UserManager::GetCloneCounts(User* user) const { - clonemap::iterator x = global_clones.find(user->GetCIDRMask()); - if (x != global_clones.end()) - return x->second; + CloneMap::const_iterator it = clonemap.find(user->GetCIDRMask()); + if (it != clonemap.end()) + return it->second; else - return 0; -} - -unsigned long UserManager::LocalCloneCount(User *user) -{ - clonemap::iterator x = local_clones.find(user->GetCIDRMask()); - if (x != local_clones.end()) - return x->second; - else - return 0; -} - -/* this function counts all users connected, wether they are registered or NOT. */ -unsigned int UserManager::UserCount() -{ - /* - * XXX: Todo: - * As part of this restructuring, move clientlist/etc fields into usermanager. - * -- w00t - */ - return this->clientlist->size(); -} - -/* this counts only registered users, so that the percentages in /MAP don't mess up */ -unsigned int UserManager::RegisteredUserCount() -{ - return this->clientlist->size() - this->UnregisteredUserCount(); + return zeroclonecounts; } -/* return how many users are opered */ -unsigned int UserManager::OperCount() +void UserManager::ServerNoticeAll(const char* text, ...) { - return this->all_opers.size(); -} + std::string message; + VAFORMAT(message, text, text); + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, ServerInstance->Config->ServerName, message, MSG_NOTICE); + ClientProtocol::Event msgevent(ServerInstance->GetRFCEvents().privmsg, msg); -/* return how many users are unregistered */ -unsigned int UserManager::UnregisteredUserCount() -{ - return this->unregistered_count; + for (LocalList::const_iterator i = local_users.begin(); i != local_users.end(); ++i) + { + LocalUser* user = *i; + user->Send(msgevent); + } } -/* return how many local registered users there are */ -unsigned int UserManager::LocalUserCount() +/* this returns true when all modules are satisfied that the user should be allowed onto the irc server + * (until this returns true, a user will block in the waiting state, waiting to connect up to the + * registration timeout maximum seconds) + */ +bool UserManager::AllModulesReportReady(LocalUser* user) { - /* Doesnt count unregistered clients */ - return (this->local_count - this->UnregisteredUserCount()); + ModResult res; + FIRST_MOD_RESULT(OnCheckReady, res, (user)); + return (res == MOD_RES_PASSTHRU); } -void UserManager::ServerNoticeAll(const char* text, ...) +/** + * This function is called once a second from the mainloop. + * It is intended to do background checking on all the users, e.g. do + * ping checks, registration timeouts, etc. + */ +void UserManager::DoBackgroundUserStuff() { - if (!text) - return; - - char textbuffer[MAXBUF]; - char formatbuffer[MAXBUF]; - va_list argsPtr; - va_start (argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - snprintf(formatbuffer,MAXBUF,"NOTICE $%s :%s", ServerInstance->Config->ServerName.c_str(), textbuffer); - - for (LocalUserList::const_iterator i = local_users.begin(); i != local_users.end(); i++) + for (LocalList::iterator i = local_users.begin(); i != local_users.end(); ) { - User* t = *i; - t->WriteServ(std::string(formatbuffer)); - } -} - -void UserManager::ServerPrivmsgAll(const char* text, ...) -{ - if (!text) - return; + // It's possible that we quit the user below due to ping timeout etc. and QuitUser() removes it from the list + LocalUser* curr = *i; + ++i; - char textbuffer[MAXBUF]; - char formatbuffer[MAXBUF]; - va_list argsPtr; - va_start (argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); + if (curr->CommandFloodPenalty || curr->eh.getSendQSize()) + { + unsigned int rate = curr->MyClass->GetCommandRate(); + if (curr->CommandFloodPenalty > rate) + curr->CommandFloodPenalty -= rate; + else + curr->CommandFloodPenalty = 0; + curr->eh.OnDataReady(); + } - snprintf(formatbuffer,MAXBUF,"PRIVMSG $%s :%s", ServerInstance->Config->ServerName.c_str(), textbuffer); + switch (curr->registered) + { + case REG_ALL: + if (ServerInstance->Time() >= curr->nping) + { + // This user didn't answer the last ping, remove them + if (!curr->lastping) + { + time_t time = ServerInstance->Time() - (curr->nping - curr->MyClass->GetPingTime()); + const std::string message = "Ping timeout: " + ConvToStr(time) + (time != 1 ? " seconds" : " second"); + this->QuitUser(curr, message); + continue; + } + ClientProtocol::Messages::Ping ping; + curr->Send(ServerInstance->GetRFCEvents().ping, ping); + curr->lastping = 0; + curr->nping = ServerInstance->Time() + curr->MyClass->GetPingTime(); + } + break; + case REG_NICKUSER: + if (AllModulesReportReady(curr)) + { + /* User has sent NICK/USER, modules are okay, DNS finished. */ + curr->FullConnect(); + continue; + } + + // If the user has been quit in OnCheckReady then we shouldn't + // quit them again for having a registration timeout. + if (curr->quitting) + continue; + break; + } - for (LocalUserList::const_iterator i = local_users.begin(); i != local_users.end(); i++) - { - User* t = *i; - t->WriteServ(std::string(formatbuffer)); + if (curr->registered != REG_ALL && curr->MyClass && (ServerInstance->Time() > (curr->signon + curr->MyClass->GetRegTimeout()))) + { + /* + * registration timeout -- didnt send USER/NICK/HOST + * in the time specified in their connection class. + */ + this->QuitUser(curr, "Registration timeout"); + continue; + } } } - -/* return how many users have a given mode e.g. 'a' */ -int UserManager::ModeCount(const char mode) +already_sent_t UserManager::NextAlreadySentId() { - int c = 0; - for(user_hash::iterator i = clientlist->begin(); i != clientlist->end(); ++i) + if (++already_sent_id == 0) { - User* u = i->second; - if (u->modes[mode-65]) - c++; + // Wrapped around, reset the already_sent ids of all users + already_sent_id = 1; + for (LocalList::iterator i = local_users.begin(); i != local_users.end(); ++i) + { + LocalUser* user = *i; + user->already_sent = 0; + } } - return c; + return already_sent_id; } diff --git a/src/userprocess.cpp b/src/userprocess.cpp deleted file mode 100644 index 69c31f840..000000000 --- a/src/userprocess.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net> - * Copyright (C) 2005-2007 Craig Edwards <craigedwards@brainbox.cc> - * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> - * Copyright (C) 2006 Craig McLure <craig@chatspike.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -/* $Core */ - -#include "inspircd.h" -#include "xline.h" -#include "socketengine.h" -#include "command_parse.h" - -void FloodQuitUserHandler::Call(User* current) -{ - ServerInstance->Logs->Log("USERS",DEFAULT,"Excess flood from: %s@%s", current->ident.c_str(), current->host.c_str()); - ServerInstance->SNO->WriteToSnoMask('f',"Excess flood from: %s%s%s@%s", - current->registered == REG_ALL ? current->nick.c_str() : "", - current->registered == REG_ALL ? "!" : "", current->ident.c_str(), current->host.c_str()); - ServerInstance->Users->QuitUser(current, "Excess flood"); - - if (current->registered != REG_ALL) - { - ZLine* zl = new ZLine(ServerInstance->Time(), 0, ServerInstance->Config->ServerName, "Flood from unregistered connection", current->GetIPString()); - if (ServerInstance->XLines->AddLine(zl,NULL)) - ServerInstance->XLines->ApplyLines(); - else - delete zl; - } -} - -/** - * This function is called once a second from the mainloop. - * It is intended to do background checking on all the user structs, e.g. - * stuff like ping checks, registration timeouts, etc. - */ -void InspIRCd::DoBackgroundUserStuff() -{ - /* - * loop over all local users.. - */ - LocalUserList::reverse_iterator count2 = this->Users->local_users.rbegin(); - while (count2 != this->Users->local_users.rend()) - { - LocalUser *curr = *count2; - count2++; - - if (curr->quitting) - continue; - - if (curr->CommandFloodPenalty || curr->eh.getSendQSize()) - { - unsigned int rate = curr->MyClass->GetCommandRate(); - if (curr->CommandFloodPenalty > rate) - curr->CommandFloodPenalty -= rate; - else - curr->CommandFloodPenalty = 0; - curr->eh.OnDataReady(); - } - - switch (curr->registered) - { - case REG_ALL: - if (Time() > curr->nping) - { - // This user didn't answer the last ping, remove them - if (!curr->lastping) - { - time_t time = this->Time() - (curr->nping - curr->MyClass->GetPingTime()); - char message[MAXBUF]; - snprintf(message, MAXBUF, "Ping timeout: %ld second%s", (long)time, time > 1 ? "s" : ""); - curr->lastping = 1; - curr->nping = Time() + curr->MyClass->GetPingTime(); - this->Users->QuitUser(curr, message); - continue; - } - - curr->Write("PING :%s",this->Config->ServerName.c_str()); - curr->lastping = 0; - curr->nping = Time() +curr->MyClass->GetPingTime(); - } - break; - case REG_NICKUSER: - if (AllModulesReportReady(curr) && curr->dns_done) - { - /* User has sent NICK/USER, modules are okay, DNS finished. */ - curr->FullConnect(); - continue; - } - - // If the user has been quit in OnCheckReady then we shouldn't - // quit them again for having a registration timeout. - if (curr->quitting) - continue; - break; - } - - if (curr->registered != REG_ALL && curr->MyClass && (Time() > (curr->signon + curr->MyClass->GetRegTimeout()))) - { - /* - * registration timeout -- didnt send USER/NICK/HOST - * in the time specified in their connection class. - */ - this->Users->QuitUser(curr, "Registration timeout"); - continue; - } - } -} - diff --git a/src/users.cpp b/src/users.cpp index ac87f1187..8f20b7523 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -24,111 +24,9 @@ #include "inspircd.h" -#include <stdarg.h> -#include "socketengine.h" #include "xline.h" -#include "bancache.h" -#include "commands/cmd_whowas.h" -already_sent_t LocalUser::already_sent_id = 0; - -std::string User::ProcessNoticeMasks(const char *sm) -{ - bool adding = true, oldadding = false; - const char *c = sm; - std::string output; - - while (c && *c) - { - switch (*c) - { - case '+': - adding = true; - break; - case '-': - adding = false; - break; - case '*': - for (unsigned char d = 'a'; d <= 'z'; d++) - { - if (!ServerInstance->SNO->masks[d - 'a'].Description.empty()) - { - if ((!IsNoticeMaskSet(d) && adding) || (IsNoticeMaskSet(d) && !adding)) - { - if ((oldadding != adding) || (!output.length())) - output += (adding ? '+' : '-'); - - this->SetNoticeMask(d, adding); - - output += d; - } - oldadding = adding; - char u = toupper(d); - if ((!IsNoticeMaskSet(u) && adding) || (IsNoticeMaskSet(u) && !adding)) - { - if ((oldadding != adding) || (!output.length())) - output += (adding ? '+' : '-'); - - this->SetNoticeMask(u, adding); - - output += u; - } - oldadding = adding; - } - } - break; - default: - if (isalpha(*c)) - { - if ((!IsNoticeMaskSet(*c) && adding) || (IsNoticeMaskSet(*c) && !adding)) - { - if ((oldadding != adding) || (!output.length())) - output += (adding ? '+' : '-'); - - this->SetNoticeMask(*c, adding); - - output += *c; - oldadding = adding; - } - } - else - this->WriteNumeric(ERR_UNKNOWNSNOMASK, "%s %c :is unknown snomask char to me", this->nick.c_str(), *c); - - break; - } - - c++; - } - - std::string s = this->FormatNoticeMasks(); - if (s.length() == 0) - { - this->modes[UM_SNOMASK] = false; - } - - return output; -} - -void LocalUser::StartDNSLookup() -{ - try - { - bool cached = false; - const char* sip = this->GetIPString(); - UserResolver *res_reverse; - - QueryType resolvtype = this->client_sa.sa.sa_family == AF_INET6 ? DNS_QUERY_PTR6 : DNS_QUERY_PTR4; - res_reverse = new UserResolver(this, sip, resolvtype, cached); - - ServerInstance->AddResolver(res_reverse, cached); - } - catch (CoreException& e) - { - ServerInstance->Logs->Log("USERS", DEBUG,"Error in resolver: %s",e.GetReason()); - dns_done = true; - ServerInstance->stats->statsDnsBad++; - } -} +ClientProtocol::MessageList LocalUser::sendmsglist; bool User::IsNoticeMaskSet(unsigned char sm) { @@ -137,104 +35,85 @@ bool User::IsNoticeMaskSet(unsigned char sm) return (snomasks[sm-65]); } -void User::SetNoticeMask(unsigned char sm, bool value) +bool User::IsModeSet(unsigned char m) const { - if (!isalpha(sm)) - return; - snomasks[sm-65] = value; + ModeHandler* mh = ServerInstance->Modes->FindMode(m, MODETYPE_USER); + return (mh && modes[mh->GetId()]); } -const char* User::FormatNoticeMasks() +std::string User::GetModeLetters(bool includeparams) const { - static char data[MAXBUF]; - int offset = 0; - - for (int n = 0; n < 64; n++) - { - if (snomasks[n]) - data[offset++] = n+65; - } - - data[offset] = 0; - return data; -} - -bool User::IsModeSet(unsigned char m) -{ - if (!isalpha(m)) - return false; - return (modes[m-65]); -} - -void User::SetMode(unsigned char m, bool value) -{ - if (!isalpha(m)) - return; - modes[m-65] = value; -} - -const char* User::FormatModes(bool showparameters) -{ - static char data[MAXBUF]; + std::string ret(1, '+'); std::string params; - int offset = 0; - for (unsigned char n = 0; n < 64; n++) + for (unsigned char i = 'A'; i < 'z'; i++) { - if (modes[n]) + const ModeHandler* const mh = ServerInstance->Modes.FindMode(i, MODETYPE_USER); + if ((!mh) || (!IsModeSet(mh))) + continue; + + ret.push_back(mh->GetModeChar()); + if ((includeparams) && (mh->NeedsParam(true))) { - data[offset++] = n + 65; - ModeHandler* mh = ServerInstance->Modes->FindMode(n + 65, MODETYPE_USER); - if (showparameters && mh && mh->GetNumParams(true)) - { - std::string p = mh->GetUserParameter(this); - if (p.length()) - params.append(" ").append(p); - } + const std::string val = mh->GetUserParameter(this); + if (!val.empty()) + params.append(1, ' ').append(val); } } - data[offset] = 0; - strlcat(data, params.c_str(), MAXBUF); - return data; + + ret += params; + return ret; } -User::User(const std::string &uid, const std::string& sid, int type) - : uuid(uid), server(sid), usertype(type) +User::User(const std::string& uid, Server* srv, UserType type) + : age(ServerInstance->Time()) + , signon(0) + , uuid(uid) + , server(srv) + , registered(REG_NONE) + , quitting(false) + , usertype(type) { - age = ServerInstance->Time(); - signon = idle_lastmsg = 0; - registered = 0; - quietquit = quitting = exempt = dns_done = false; - quitting_sendq = false; client_sa.sa.sa_family = AF_UNSPEC; - ServerInstance->Logs->Log("USERS", DEBUG, "New UUID for user: %s", uuid.c_str()); + ServerInstance->Logs->Log("USERS", LOG_DEBUG, "New UUID for user: %s", uuid.c_str()); - user_hash::iterator finduuid = ServerInstance->Users->uuidlist->find(uuid); - if (finduuid == ServerInstance->Users->uuidlist->end()) - (*ServerInstance->Users->uuidlist)[uuid] = this; - else - throw CoreException("Duplicate UUID "+std::string(uuid)+" in User constructor"); + // Do not insert FakeUsers into the uuidlist so FindUUID() won't return them which is the desired behavior + if (type != USERTYPE_SERVER) + { + if (!ServerInstance->Users.uuidlist.insert(std::make_pair(uuid, this)).second) + throw CoreException("Duplicate UUID in User constructor: " + uuid); + } } LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* servaddr) - : User(ServerInstance->GetUID(), ServerInstance->Config->ServerName, USERTYPE_LOCAL), eh(this), - localuseriter(ServerInstance->Users->local_users.end()), - bytes_in(0), bytes_out(0), cmds_in(0), cmds_out(0), nping(0), CommandFloodPenalty(0), - already_sent(0) -{ + : User(ServerInstance->UIDGen.GetUID(), ServerInstance->FakeClient->server, USERTYPE_LOCAL) + , eh(this) + , serializer(NULL) + , bytes_in(0) + , bytes_out(0) + , cmds_in(0) + , cmds_out(0) + , quitting_sendq(false) + , lastping(true) + , exempt(false) + , nping(0) + , idle_lastmsg(0) + , CommandFloodPenalty(0) + , already_sent(0) +{ + signon = ServerInstance->Time(); + // The user's default nick is their UUID + nick = uuid; ident = "unknown"; - lastping = 0; eh.SetFd(myfd); memcpy(&client_sa, client, sizeof(irc::sockets::sockaddrs)); memcpy(&server_sa, servaddr, sizeof(irc::sockets::sockaddrs)); - dhost = host = GetIPString(); + ChangeRealHost(GetIPString(), true); } User::~User() { - if (ServerInstance->Users->uuidlist->find(uuid) != ServerInstance->Users->uuidlist->end()) - ServerInstance->Logs->Log("USERS", DEFAULT, "User destructor for %s called without cull", uuid.c_str()); } const std::string& User::MakeHost() @@ -242,18 +121,8 @@ const std::string& User::MakeHost() if (!this->cached_makehost.empty()) return this->cached_makehost; - char nhost[MAXBUF]; - /* This is much faster than snprintf */ - char* t = nhost; - for(const char* n = ident.c_str(); *n; n++) - *t++ = *n; - *t++ = '@'; - for(const char* n = host.c_str(); *n; n++) - *t++ = *n; - *t = 0; - - this->cached_makehost.assign(nhost); - + // XXX: Is there really a need to cache this? + this->cached_makehost = ident + "@" + GetRealHost(); return this->cached_makehost; } @@ -262,18 +131,8 @@ const std::string& User::MakeHostIP() if (!this->cached_hostip.empty()) return this->cached_hostip; - char ihost[MAXBUF]; - /* This is much faster than snprintf */ - char* t = ihost; - for(const char* n = ident.c_str(); *n; n++) - *t++ = *n; - *t++ = '@'; - for(const char* n = this->GetIPString(); *n; n++) - *t++ = *n; - *t = 0; - - this->cached_hostip = ihost; - + // XXX: Is there really a need to cache this? + this->cached_hostip = ident + "@" + this->GetIPString(); return this->cached_hostip; } @@ -282,111 +141,35 @@ const std::string& User::GetFullHost() if (!this->cached_fullhost.empty()) return this->cached_fullhost; - char result[MAXBUF]; - char* t = result; - for(const char* n = nick.c_str(); *n; n++) - *t++ = *n; - *t++ = '!'; - for(const char* n = ident.c_str(); *n; n++) - *t++ = *n; - *t++ = '@'; - for(const char* n = dhost.c_str(); *n; n++) - *t++ = *n; - *t = 0; - - this->cached_fullhost = result; - + // XXX: Is there really a need to cache this? + this->cached_fullhost = nick + "!" + ident + "@" + GetDisplayedHost(); return this->cached_fullhost; } -char* User::MakeWildHost() -{ - static char nresult[MAXBUF]; - char* t = nresult; - *t++ = '*'; *t++ = '!'; - *t++ = '*'; *t++ = '@'; - for(const char* n = dhost.c_str(); *n; n++) - *t++ = *n; - *t = 0; - return nresult; -} - const std::string& User::GetFullRealHost() { if (!this->cached_fullrealhost.empty()) return this->cached_fullrealhost; - char fresult[MAXBUF]; - char* t = fresult; - for(const char* n = nick.c_str(); *n; n++) - *t++ = *n; - *t++ = '!'; - for(const char* n = ident.c_str(); *n; n++) - *t++ = *n; - *t++ = '@'; - for(const char* n = host.c_str(); *n; n++) - *t++ = *n; - *t = 0; - - this->cached_fullrealhost = fresult; - + // XXX: Is there really a need to cache this? + this->cached_fullrealhost = nick + "!" + ident + "@" + GetRealHost(); return this->cached_fullrealhost; } -bool LocalUser::IsInvited(const irc::string &channel) -{ - Channel* chan = ServerInstance->FindChan(channel.c_str()); - if (!chan) - return false; - - return (Invitation::Find(chan, this) != NULL); -} - -InviteList& LocalUser::GetInviteList() -{ - RemoveExpiredInvites(); - return invites; -} - -void LocalUser::InviteTo(const irc::string &channel, time_t invtimeout) -{ - Channel* chan = ServerInstance->FindChan(channel.c_str()); - if (chan) - Invitation::Create(chan, this, invtimeout); -} - -void LocalUser::RemoveInvite(const irc::string &channel) -{ - Channel* chan = ServerInstance->FindChan(channel.c_str()); - if (chan) - { - Invitation* inv = Invitation::Find(chan, this); - if (inv) - { - inv->cull(); - delete inv; - } - } -} - -void LocalUser::RemoveExpiredInvites() -{ - Invitation::Find(NULL, this); -} - -bool User::HasModePermission(unsigned char, ModeType) +bool User::HasModePermission(const ModeHandler* mh) const { return true; } -bool LocalUser::HasModePermission(unsigned char mode, ModeType type) +bool LocalUser::HasModePermission(const ModeHandler* mh) const { - if (!IS_OPER(this)) + if (!this->IsOper()) return false; + const unsigned char mode = mh->GetModeChar(); if (mode < 'A' || mode > ('A' + 64)) return false; - return ((type == MODETYPE_USER ? oper->AllowedUserModes : oper->AllowedChanModes))[(mode - 'A')]; + return ((mh->GetModeType() == MODETYPE_USER ? oper->AllowedUserModes : oper->AllowedChanModes))[(mode - 'A')]; } /* @@ -404,17 +187,12 @@ bool User::HasPermission(const std::string&) bool LocalUser::HasPermission(const std::string &command) { // are they even an oper at all? - if (!IS_OPER(this)) + if (!this->IsOper()) { return false; } - if (oper->AllowedOperCommands.find(command) != oper->AllowedOperCommands.end()) - return true; - else if (oper->AllowedOperCommands.find("*") != oper->AllowedOperCommands.end()) - return true; - - return false; + return oper->AllowedOperCommands.Contains(command); } bool User::HasPrivPermission(const std::string &privstr, bool noisy) @@ -424,24 +202,19 @@ bool User::HasPrivPermission(const std::string &privstr, bool noisy) bool LocalUser::HasPrivPermission(const std::string &privstr, bool noisy) { - if (!IS_OPER(this)) + if (!this->IsOper()) { if (noisy) - this->WriteServ("NOTICE %s :You are not an oper", this->nick.c_str()); + this->WriteNotice("You are not an oper"); return false; } - if (oper->AllowedPrivs.find(privstr) != oper->AllowedPrivs.end()) - { + if (oper->AllowedPrivs.Contains(privstr)) return true; - } - else if (oper->AllowedPrivs.find("*") != oper->AllowedPrivs.end()) - { - return true; - } if (noisy) - this->WriteServ("NOTICE %s :Oper type %s does not have access to priv %s", this->nick.c_str(), oper->NameStr(), privstr.c_str()); + this->WriteNotice("Oper type " + oper->name + " does not have access to priv " + privstr); + return false; } @@ -457,49 +230,72 @@ void UserIOHandler::OnDataReady() user->nick.c_str(), (unsigned long)recvq.length(), user->MyClass->GetRecvqMax()); return; } + unsigned long sendqmax = ULONG_MAX; if (!user->HasPrivPermission("users/flood/increased-buffers")) sendqmax = user->MyClass->GetSendqSoftMax(); + unsigned long penaltymax = ULONG_MAX; if (!user->HasPrivPermission("users/flood/no-fakelag")) penaltymax = user->MyClass->GetPenaltyThreshold() * 1000; + // The maximum size of an IRC message minus the terminating CR+LF. + const size_t maxmessage = ServerInstance->Config->Limits.MaxLine - 2; + std::string line; + line.reserve(maxmessage); + + bool eol_found; + std::string::size_type qpos; + while (user->CommandFloodPenalty < penaltymax && getSendQSize() < sendqmax) { - std::string line; - line.reserve(MAXBUF); - std::string::size_type qpos = 0; - while (qpos < recvq.length()) + qpos = 0; + eol_found = false; + + const size_t qlen = recvq.length(); + while (qpos < qlen) { char c = recvq[qpos++]; switch (c) { - case '\0': - c = ' '; - break; - case '\r': - continue; - case '\n': - goto eol_found; + case '\0': + c = ' '; + break; + case '\r': + continue; + case '\n': + eol_found = true; + break; } - if (line.length() < MAXBUF - 2) + + if (eol_found) + break; + + if (line.length() < maxmessage) line.push_back(c); } - // if we got here, the recvq ran out before we found a newline - return; -eol_found: + + // if we return here, we haven't found a newline and make no modifications to recvq + // so we can wait for more data + if (!eol_found) + return; + // just found a newline. Terminate the string, and pull it out of recvq - recvq = recvq.substr(qpos); + recvq.erase(0, qpos); // TODO should this be moved to when it was inserted in recvq? - ServerInstance->stats->statsRecv += qpos; + ServerInstance->stats.Recv += qpos; user->bytes_in += qpos; user->cmds_in++; - ServerInstance->Parser->ProcessBuffer(line, user); + ServerInstance->Parser.ProcessBuffer(user, line); if (user->quitting) return; + + // clear() does not reclaim memory associated with the string, so our .reserve() call is safe + line.clear(); } + if (user->CommandFloodPenalty >= penaltymax && !user->MyClass->fakelag) ServerInstance->Users->QuitUser(user, "Excess Flood"); } @@ -522,6 +318,12 @@ void UserIOHandler::AddWriteBuf(const std::string &data) WriteData(data); } +void UserIOHandler::OnSetEndPoint(const irc::sockets::sockaddrs& server, const irc::sockets::sockaddrs& client) +{ + memcpy(&user->server_sa, &server, sizeof(irc::sockets::sockaddrs)); + user->SetClientIP(client); +} + void UserIOHandler::OnError(BufferedSocketError) { ServerInstance->Users->QuitUser(user, getError()); @@ -531,9 +333,8 @@ CullResult User::cull() { if (!quitting) ServerInstance->Users->QuitUser(this, "Culled without QuitUser"); - PurgeEmptyChannels(); - if (client_sa.sa.sa_family != AF_UNSPEC) + if (client_sa.family() != AF_UNSPEC) ServerInstance->Users->RemoveCloneCounts(this); return Extensible::cull(); @@ -541,18 +342,6 @@ CullResult User::cull() CullResult LocalUser::cull() { - // The iterator is initialized to local_users.end() in the constructor. It is - // overwritten in UserManager::AddUser() with the real iterator so this check - // is only a precaution currently. - if (localuseriter != ServerInstance->Users->local_users.end()) - { - ServerInstance->Users->local_count--; - ServerInstance->Users->local_users.erase(localuseriter); - } - else - ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: LocalUserIter does not point to a valid entry for " + this->nick); - - ClearInvites(); eh.cull(); return User::cull(); } @@ -561,20 +350,29 @@ CullResult FakeUser::cull() { // Fake users don't quit, they just get culled. quitting = true; - ServerInstance->Users->clientlist->erase(nick); - ServerInstance->Users->uuidlist->erase(uuid); + // Fake users are not inserted into UserManager::clientlist or uuidlist, so we don't need to modify those here return User::cull(); } void User::Oper(OperInfo* info) { - if (this->IsModeSet('o')) + ModeHandler* opermh = ServerInstance->Modes->FindMode('o', MODETYPE_USER); + if (this->IsModeSet(opermh)) this->UnOper(); - this->modes[UM_OPERATOR] = 1; + this->SetMode(opermh, true); this->oper = info; - this->WriteServ("MODE %s :+o", this->nick.c_str()); - FOREACH_MOD(I_OnOper, OnOper(this, info->name)); + + LocalUser* localuser = IS_LOCAL(this); + if (localuser) + { + Modes::ChangeList changelist; + changelist.push_add(opermh); + ClientProtocol::Events::Mode modemsg(ServerInstance->FakeClient, NULL, localuser, changelist); + localuser->Send(modemsg); + } + + FOREACH_MOD(OnOper, (this, info->name)); std::string opername; if (info->oper_block) @@ -585,30 +383,29 @@ void User::Oper(OperInfo* info) LocalUser* l = IS_LOCAL(this); std::string vhost = oper->getConfig("vhost"); if (!vhost.empty()) - l->ChangeDisplayedHost(vhost.c_str()); + l->ChangeDisplayedHost(vhost); std::string opClass = oper->getConfig("class"); if (!opClass.empty()) l->SetClass(opClass); } ServerInstance->SNO->WriteToSnoMask('o',"%s (%s@%s) is now an IRC operator of type %s (using oper '%s')", - nick.c_str(), ident.c_str(), host.c_str(), oper->NameStr(), opername.c_str()); - this->WriteNumeric(381, "%s :You are now %s %s", nick.c_str(), strchr("aeiouAEIOU", oper->name[0]) ? "an" : "a", oper->NameStr()); + nick.c_str(), ident.c_str(), GetRealHost().c_str(), oper->name.c_str(), opername.c_str()); + this->WriteNumeric(RPL_YOUAREOPER, InspIRCd::Format("You are now %s %s", strchr("aeiouAEIOU", oper->name[0]) ? "an" : "a", oper->name.c_str())); - ServerInstance->Logs->Log("OPER", DEFAULT, "%s opered as type: %s", GetFullRealHost().c_str(), oper->NameStr()); ServerInstance->Users->all_opers.push_back(this); // Expand permissions from config for faster lookup - if (IS_LOCAL(this)) + if (localuser) oper->init(); - FOREACH_MOD(I_OnPostOper,OnPostOper(this, oper->name, opername)); + FOREACH_MOD(OnPostOper, (this, oper->name, opername)); } void OperInfo::init() { - AllowedOperCommands.clear(); - AllowedPrivs.clear(); + AllowedOperCommands.Clear(); + AllowedPrivs.Clear(); AllowedUserModes.reset(); AllowedChanModes.reset(); AllowedUserModes['o' - 'A'] = true; // Call me paranoid if you want. @@ -616,19 +413,9 @@ void OperInfo::init() for(std::vector<reference<ConfigTag> >::iterator iter = class_blocks.begin(); iter != class_blocks.end(); ++iter) { ConfigTag* tag = *iter; - std::string mycmd, mypriv; - /* Process commands */ - irc::spacesepstream CommandList(tag->getString("commands")); - while (CommandList.GetToken(mycmd)) - { - AllowedOperCommands.insert(mycmd); - } - irc::spacesepstream PrivList(tag->getString("privs")); - while (PrivList.GetToken(mypriv)) - { - AllowedPrivs.insert(mypriv); - } + AllowedOperCommands.AddList(tag->getString("commands")); + AllowedPrivs.AddList(tag->getString("privs")); std::string modes = tag->getString("usermodes"); for (std::string::const_iterator c = modes.begin(); c != modes.end(); ++c) @@ -660,7 +447,7 @@ void OperInfo::init() void User::UnOper() { - if (!IS_OPER(this)) + if (!this->IsOper()) return; /* @@ -672,45 +459,29 @@ void User::UnOper() /* Remove all oper only modes from the user when the deoper - Bug #466*/ - std::string moderemove("-"); - - for (unsigned char letter = 'A'; letter <= 'z'; letter++) + Modes::ChangeList changelist; + const ModeParser::ModeHandlerMap& usermodes = ServerInstance->Modes->GetModes(MODETYPE_USER); + for (ModeParser::ModeHandlerMap::const_iterator i = usermodes.begin(); i != usermodes.end(); ++i) { - ModeHandler* mh = ServerInstance->Modes->FindMode(letter, MODETYPE_USER); - if (mh && mh->NeedsOper()) - moderemove += letter; + ModeHandler* mh = i->second; + if (mh->NeedsOper()) + changelist.push_remove(mh); } + ServerInstance->Modes->Process(this, NULL, this, changelist); - std::vector<std::string> parameters; - parameters.push_back(this->nick); - parameters.push_back(moderemove); + // Remove the user from the oper list + stdalgo::vector::swaperase(ServerInstance->Users->all_opers, this); - ServerInstance->Parser->CallHandler("MODE", parameters, this); - - /* remove the user from the oper list. Will remove multiple entries as a safeguard against bug #404 */ - ServerInstance->Users->all_opers.remove(this); - - this->modes[UM_OPERATOR] = 0; - FOREACH_MOD(I_OnPostDeoper, OnPostDeoper(this)); -} - -/* adds or updates an entry in the whowas list */ -void User::AddToWhoWas() -{ - Module* whowas = ServerInstance->Modules->Find("cmd_whowas.so"); - if (whowas) - { - WhowasRequest req(NULL, whowas, WhowasRequest::WHOWAS_ADD); - req.user = this; - req.Send(); - } + ModeHandler* opermh = ServerInstance->Modes->FindMode('o', MODETYPE_USER); + this->SetMode(opermh, false); + FOREACH_MOD(OnPostDeoper, (this)); } /* * Check class restrictions */ -void LocalUser::CheckClass() +void LocalUser::CheckClass(bool clone_count) { ConnectClass* a = this->MyClass; @@ -724,25 +495,29 @@ void LocalUser::CheckClass() ServerInstance->Users->QuitUser(this, a->config->getString("reason", "Unauthorised connection")); return; } - else if ((a->GetMaxLocal()) && (ServerInstance->Users->LocalCloneCount(this) > a->GetMaxLocal())) + else if (clone_count) { - ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (local)"); - if (a->maxconnwarn) - ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum LOCAL connections (%ld) exceeded for IP %s", a->GetMaxLocal(), this->GetIPString()); - return; - } - else if ((a->GetMaxGlobal()) && (ServerInstance->Users->GlobalCloneCount(this) > a->GetMaxGlobal())) - { - ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (global)"); - if (a->maxconnwarn) - ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum GLOBAL connections (%ld) exceeded for IP %s", a->GetMaxGlobal(), this->GetIPString()); - return; + const UserManager::CloneCounts& clonecounts = ServerInstance->Users->GetCloneCounts(this); + if ((a->GetMaxLocal()) && (clonecounts.local > a->GetMaxLocal())) + { + ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (local)"); + if (a->maxconnwarn) + ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum LOCAL connections (%ld) exceeded for IP %s", a->GetMaxLocal(), this->GetIPString().c_str()); + return; + } + else if ((a->GetMaxGlobal()) && (clonecounts.global > a->GetMaxGlobal())) + { + ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (global)"); + if (a->maxconnwarn) + ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum GLOBAL connections (%ld) exceeded for IP %s", a->GetMaxGlobal(), this->GetIPString().c_str()); + return; + } } - this->nping = ServerInstance->Time() + a->GetPingTime() + ServerInstance->Config->dns_timeout; + this->nping = ServerInstance->Time() + a->GetPingTime(); } -bool User::CheckLines(bool doZline) +bool LocalUser::CheckLines(bool doZline) { const char* check[] = { "G" , "K", (doZline) ? "Z" : NULL, NULL }; @@ -765,7 +540,7 @@ bool User::CheckLines(bool doZline) void LocalUser::FullConnect() { - ServerInstance->stats->statsConnects++; + ServerInstance->stats.Connects++; this->idle_lastmsg = ServerInstance->Time(); /* @@ -782,55 +557,23 @@ void LocalUser::FullConnect() if (quitting) return; - if (ServerInstance->Config->WelcomeNotice) - this->WriteServ("NOTICE Auth :Welcome to \002%s\002!",ServerInstance->Config->Network.c_str()); - this->WriteNumeric(RPL_WELCOME, "%s :Welcome to the %s IRC Network %s",this->nick.c_str(), ServerInstance->Config->Network.c_str(), GetFullRealHost().c_str()); - this->WriteNumeric(RPL_YOURHOSTIS, "%s :Your host is %s, running version %s",this->nick.c_str(),ServerInstance->Config->ServerName.c_str(),BRANCH); - this->WriteNumeric(RPL_SERVERCREATED, "%s :This server was created %s %s", this->nick.c_str(), __TIME__, __DATE__); - - std::string umlist = ServerInstance->Modes->UserModeList(); - std::string cmlist = ServerInstance->Modes->ChannelModeList(); - std::string pmlist = ServerInstance->Modes->ParaModeList(); - this->WriteNumeric(RPL_SERVERVERSION, "%s %s %s %s %s %s", this->nick.c_str(), ServerInstance->Config->ServerName.c_str(), BRANCH, umlist.c_str(), cmlist.c_str(), pmlist.c_str()); - - ServerInstance->Config->Send005(this); - this->WriteNumeric(RPL_YOURUUID, "%s %s :your unique ID", this->nick.c_str(), this->uuid.c_str()); - - /* Now registered */ - if (ServerInstance->Users->unregistered_count) - ServerInstance->Users->unregistered_count--; - - /* Trigger MOTD and LUSERS output, give modules a chance too */ - ModResult MOD_RESULT; - std::string command("MOTD"); - std::vector<std::string> parameters; - FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, this, true, command)); - if (!MOD_RESULT) - ServerInstance->Parser->CallHandler(command, parameters, this); - - MOD_RESULT = MOD_RES_PASSTHRU; - command = "LUSERS"; - FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, this, true, command)); - if (!MOD_RESULT) - ServerInstance->Parser->CallHandler(command, parameters, this); - - if (ServerInstance->Config->RawLog) - WriteServ("PRIVMSG %s :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.", nick.c_str()); - /* * We don't set REG_ALL until triggering OnUserConnect, so some module events don't spew out stuff * for a user that doesn't exist yet. */ - FOREACH_MOD(I_OnUserConnect,OnUserConnect(this)); + FOREACH_MOD(OnUserConnect, (this)); + /* Now registered */ + if (ServerInstance->Users->unregistered_count) + ServerInstance->Users->unregistered_count--; this->registered = REG_ALL; - FOREACH_MOD(I_OnPostConnect,OnPostConnect(this)); + FOREACH_MOD(OnPostConnect, (this)); ServerInstance->SNO->WriteToSnoMask('c',"Client connecting on port %d (class %s): %s (%s) [%s]", - this->GetServerPort(), this->MyClass->name.c_str(), GetFullRealHost().c_str(), this->GetIPString(), this->fullname.c_str()); - ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCache: Adding NEGATIVE hit for %s", this->GetIPString()); - ServerInstance->BanCache->AddHit(this->GetIPString(), "", ""); + this->GetServerPort(), this->MyClass->name.c_str(), GetFullRealHost().c_str(), this->GetIPString().c_str(), this->GetRealName().c_str()); + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Adding NEGATIVE hit for " + this->GetIPString()); + ServerInstance->BanCache.AddHit(this->GetIPString(), "", ""); // reset the flood penalty (which could have been raised due to things like auto +x) CommandFloodPenalty = 0; } @@ -845,73 +588,26 @@ void User::InvalidateCache() cached_fullrealhost.clear(); } -bool User::ChangeNick(const std::string& newnick, bool force) +bool User::ChangeNick(const std::string& newnick, time_t newts) { if (quitting) { - ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Attempted to change nick of a quitting user: " + this->nick); - return false; - } - - ModResult MOD_RESULT; - - if (force) - ServerInstance->NICKForced.set(this, 1); - FIRST_MOD_RESULT(OnUserPreNick, MOD_RESULT, (this, newnick)); - ServerInstance->NICKForced.set(this, 0); - - if (MOD_RESULT == MOD_RES_DENY) - { - ServerInstance->stats->statsCollisions++; + ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Attempted to change nick of a quitting user: " + this->nick); return false; } - if (assign(newnick) == assign(nick)) + User* const InUse = ServerInstance->FindNickOnly(newnick); + if (InUse == this) { - // case change, don't need to check Q:lines and such + // case change, don't need to check campers // and, if it's identical including case, we can leave right now + // We also don't update the nick TS if it's a case change, either if (newnick == nick) return true; } else { /* - * Don't check Q:Lines if it's a server-enforced change, just on the off-chance some fucking *moron* - * tries to Q:Line SIDs, also, this means we just get our way period, as it really should be. - * Thanks Kein for finding this. -- w00t - * - * Also don't check Q:Lines for remote nickchanges, they should have our Q:Lines anyway to enforce themselves. - * -- w00t - */ - if (IS_LOCAL(this) && !force) - { - XLine* mq = ServerInstance->XLines->MatchesLine("Q",newnick); - if (mq) - { - if (this->registered == REG_ALL) - { - ServerInstance->SNO->WriteGlobalSno('a', "Q-Lined nickname %s from %s: %s", - newnick.c_str(), GetFullRealHost().c_str(), mq->reason.c_str()); - } - this->WriteNumeric(432, "%s %s :Invalid nickname: %s",this->nick.c_str(), newnick.c_str(), mq->reason.c_str()); - return false; - } - - if (ServerInstance->Config->RestrictBannedUsers) - { - for (UCListIter i = this->chans.begin(); i != this->chans.end(); i++) - { - Channel *chan = *i; - if (chan->GetPrefixValue(this) < VOICE_VALUE && chan->IsBanned(this)) - { - this->WriteNumeric(404, "%s %s :Cannot send to channel (you're banned)", this->nick.c_str(), chan->name.c_str()); - return false; - } - } - } - } - - /* * Uh oh.. if the nickname is in use, and it's not in use by the person using it (doh) -- * then we have a potential collide. Check whether someone else is camping on the nick * (i.e. connect -> send NICK, don't send USER.) If they are camping, force-change the @@ -920,76 +616,99 @@ bool User::ChangeNick(const std::string& newnick, bool force) * If the guy using the nick is already using it, tell the incoming nick change to gtfo, * because the nick is already (rightfully) in use. -- w00t */ - User* InUse = ServerInstance->FindNickOnly(newnick); - if (InUse && (InUse != this)) + if (InUse) { if (InUse->registered != REG_ALL) { /* force the camper to their UUID, and ask them to re-send a NICK. */ - InUse->WriteTo(InUse, "NICK %s", InUse->uuid.c_str()); - InUse->WriteNumeric(433, "%s %s :Nickname overruled.", InUse->nick.c_str(), InUse->nick.c_str()); - - ServerInstance->Users->clientlist->erase(InUse->nick); - (*(ServerInstance->Users->clientlist))[InUse->uuid] = InUse; - - InUse->nick = InUse->uuid; - InUse->InvalidateCache(); - InUse->registered &= ~REG_NICK; + LocalUser* const localuser = static_cast<LocalUser*>(InUse); + localuser->OverruleNick(); } else { /* No camping, tell the incoming user to stop trying to change nick ;p */ - this->WriteNumeric(433, "%s %s :Nickname is already in use.", this->registered >= REG_NICK ? this->nick.c_str() : "*", newnick.c_str()); + this->WriteNumeric(ERR_NICKNAMEINUSE, newnick, "Nickname is already in use."); return false; } } + + age = newts ? newts : ServerInstance->Time(); } if (this->registered == REG_ALL) - this->WriteCommon("NICK %s",newnick.c_str()); - std::string oldnick = nick; + { + ClientProtocol::Messages::Nick nickmsg(this, newnick); + ClientProtocol::Event nickevent(ServerInstance->GetRFCEvents().nick, nickmsg); + this->WriteCommonRaw(nickevent, true); + } + const std::string oldnick = nick; nick = newnick; InvalidateCache(); - ServerInstance->Users->clientlist->erase(oldnick); - (*(ServerInstance->Users->clientlist))[newnick] = this; + ServerInstance->Users->clientlist.erase(oldnick); + ServerInstance->Users->clientlist[newnick] = this; if (registered == REG_ALL) - FOREACH_MOD(I_OnUserPostNick,OnUserPostNick(this,oldnick)); + FOREACH_MOD(OnUserPostNick, (this,oldnick)); return true; } -int LocalUser::GetServerPort() +void LocalUser::OverruleNick() { - switch (this->server_sa.sa.sa_family) { - case AF_INET6: - return htons(this->server_sa.in6.sin6_port); - case AF_INET: - return htons(this->server_sa.in4.sin_port); + ClientProtocol::Messages::Nick nickmsg(this, this->uuid); + this->Send(ServerInstance->GetRFCEvents().nick, nickmsg); } - return 0; + this->WriteNumeric(ERR_NICKNAMEINUSE, this->nick, "Nickname overruled."); + + // Clear the bit before calling ChangeNick() to make it NOT run the OnUserPostNick() hook + this->registered &= ~REG_NICK; + this->ChangeNick(this->uuid); +} + +int LocalUser::GetServerPort() +{ + return this->server_sa.port(); } -const char* User::GetIPString() +const std::string& User::GetIPString() { - int port; if (cachedip.empty()) { - irc::sockets::satoap(client_sa, cachedip, port); + cachedip = client_sa.addr(); /* IP addresses starting with a : on irc are a Bad Thing (tm) */ - if (cachedip.c_str()[0] == ':') + if (cachedip[0] == ':') cachedip.insert(cachedip.begin(),1,'0'); } - return cachedip.c_str(); + return cachedip; +} + +const std::string& User::GetHost(bool uncloak) const +{ + return uncloak ? GetRealHost() : GetDisplayedHost(); +} + +const std::string& User::GetDisplayedHost() const +{ + return displayhost.empty() ? realhost : displayhost; +} + +const std::string& User::GetRealHost() const +{ + return realhost; +} + +const std::string& User::GetRealName() const +{ + return realname; } irc::sockets::cidr_mask User::GetCIDRMask() { - int range = 0; - switch (client_sa.sa.sa_family) + unsigned char range = 0; + switch (client_sa.family()) { case AF_INET6: range = ServerInstance->Config->c_ipv6_range; @@ -1001,23 +720,33 @@ irc::sockets::cidr_mask User::GetCIDRMask() return irc::sockets::cidr_mask(client_sa, range); } -bool User::SetClientIP(const char* sip, bool recheck_eline) +bool User::SetClientIP(const std::string& address, bool recheck_eline) { - this->InvalidateCache(); - return irc::sockets::aptosa(sip, 0, client_sa); + irc::sockets::sockaddrs sa; + if (!irc::sockets::aptosa(address, client_sa.port(), sa)) + return false; + + User::SetClientIP(sa, recheck_eline); + return true; } void User::SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_eline) { - this->InvalidateCache(); + const std::string oldip(GetIPString()); memcpy(&client_sa, &sa, sizeof(irc::sockets::sockaddrs)); + this->InvalidateCache(); + + // If the users hostname was their IP then update it. + if (GetRealHost() == oldip) + ChangeRealHost(GetIPString(), false); + if (GetDisplayedHost() == oldip) + ChangeDisplayedHost(GetIPString()); } -bool LocalUser::SetClientIP(const char* sip, bool recheck_eline) +bool LocalUser::SetClientIP(const std::string& address, bool recheck_eline) { irc::sockets::sockaddrs sa; - if (!irc::sockets::aptosa(sip, 0, sa)) - // Invalid + if (!irc::sockets::aptosa(address, client_sa.port(), sa)) return false; LocalUser::SetClientIP(sa, recheck_eline); @@ -1032,308 +761,178 @@ void LocalUser::SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_elin if (recheck_eline) this->exempt = (ServerInstance->XLines->MatchesLine("E", this) != NULL); - FOREACH_MOD(I_OnSetUserIP,OnSetUserIP(this)); + FOREACH_MOD(OnSetUserIP, (this)); } } -static std::string wide_newline("\r\n"); - -void User::Write(const std::string& text) -{ -} - -void User::Write(const char *text, ...) -{ -} - -void LocalUser::Write(const std::string& text) +void LocalUser::Write(const ClientProtocol::SerializedMessage& text) { - if (!ServerInstance->SE->BoundsCheckFd(&eh)) + if (!SocketEngine::BoundsCheckFd(&eh)) return; - if (text.length() > MAXBUF - 2) + if (ServerInstance->Config->RawLog) { - // this should happen rarely or never. Crop the string at 512 and try again. - std::string try_again = text.substr(0, MAXBUF - 2); - Write(try_again); - return; - } + if (text.empty()) + return; - ServerInstance->Logs->Log("USEROUTPUT", RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str()); + std::string::size_type nlpos = text.find_first_of("\r\n", 0, 2); + if (nlpos == std::string::npos) + nlpos = text.length(); // TODO is this ok, test it + + ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %.*s", uuid.c_str(), (int) nlpos, text.c_str()); + } eh.AddWriteBuf(text); - eh.AddWriteBuf(wide_newline); - ServerInstance->stats->statsSent += text.length() + 2; - this->bytes_out += text.length() + 2; + const size_t bytessent = text.length() + 2; + ServerInstance->stats.Sent += bytessent; + this->bytes_out += bytessent; this->cmds_out++; } -/** Write() - */ -void LocalUser::Write(const char *text, ...) +void LocalUser::Send(ClientProtocol::Event& protoev) { - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->Write(std::string(textbuffer)); -} + if (!serializer) + return; -void User::WriteServ(const std::string& text) -{ - this->Write(":%s %s",ServerInstance->Config->ServerName.c_str(),text.c_str()); + // In the most common case a static LocalUser field, sendmsglist, is passed to the event to be + // populated. The list is cleared before returning. + // To handle re-enters, if sendmsglist is non-empty upon entering the method then a temporary + // list is used instead of the static one. + if (sendmsglist.empty()) + { + Send(protoev, sendmsglist); + sendmsglist.clear(); + } + else + { + ClientProtocol::MessageList msglist; + Send(protoev, msglist); + } } -/** WriteServ() - * Same as Write(), except `text' is prefixed with `:server.name '. - */ -void User::WriteServ(const char* text, ...) +void LocalUser::Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist) { - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteServ(std::string(textbuffer)); + // Modules can personalize the messages sent per user for the event + protoev.GetMessagesForUser(this, msglist); + for (ClientProtocol::MessageList::const_iterator i = msglist.begin(); i != msglist.end(); ++i) + { + ClientProtocol::Message& curr = **i; + ModResult res; + FIRST_MOD_RESULT(OnUserWrite, res, (this, curr)); + if (res != MOD_RES_DENY) + Write(serializer->SerializeForUser(this, curr)); + } } - -void User::WriteNumeric(unsigned int numeric, const char* text, ...) +void User::WriteNumeric(const Numeric::Numeric& numeric) { - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteNumeric(numeric, std::string(textbuffer)); -} + LocalUser* const localuser = IS_LOCAL(this); + if (!localuser) + return; -void User::WriteNumeric(unsigned int numeric, const std::string &text) -{ - char textbuffer[MAXBUF]; ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric, text)); + FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric)); if (MOD_RESULT == MOD_RES_DENY) return; - snprintf(textbuffer,MAXBUF,":%s %03u %s",ServerInstance->Config->ServerName.c_str(), numeric, text.c_str()); - this->Write(std::string(textbuffer)); -} - -void User::WriteFrom(User *user, const std::string &text) -{ - char tb[MAXBUF]; - - snprintf(tb,MAXBUF,":%s %s",user->GetFullHost().c_str(),text.c_str()); - - this->Write(std::string(tb)); -} - - -/* write text from an originating user to originating user */ - -void User::WriteFrom(User *user, const char* text, ...) -{ - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteFrom(user, std::string(textbuffer)); + ClientProtocol::Messages::Numeric numericmsg(numeric, localuser); + localuser->Send(ServerInstance->GetRFCEvents().numeric, numericmsg); } - -/* write text to an destination user from a source user (e.g. user privmsg) */ - -void User::WriteTo(User *dest, const char *data, ...) +void User::WriteRemoteNotice(const std::string& text) { - char textbuffer[MAXBUF]; - va_list argsPtr; - - va_start(argsPtr, data); - vsnprintf(textbuffer, MAXBUF, data, argsPtr); - va_end(argsPtr); - - this->WriteTo(dest, std::string(textbuffer)); + ServerInstance->PI->SendUserNotice(this, text); } -void User::WriteTo(User *dest, const std::string &data) +void LocalUser::WriteRemoteNotice(const std::string& text) { - dest->WriteFrom(this, data); + WriteNotice(text); } -void User::WriteCommon(const char* text, ...) +namespace { - char textbuffer[MAXBUF]; - va_list argsPtr; - - if (this->registered != REG_ALL || quitting) - return; - - int len = snprintf(textbuffer,MAXBUF,":%s ",this->GetFullHost().c_str()); + class WriteCommonRawHandler : public User::ForEachNeighborHandler + { + ClientProtocol::Event& ev; - va_start(argsPtr, text); - vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr); - va_end(argsPtr); + void Execute(LocalUser* user) CXX11_OVERRIDE + { + user->Send(ev); + } - this->WriteCommonRaw(std::string(textbuffer), true); + public: + WriteCommonRawHandler(ClientProtocol::Event& protoev) + : ev(protoev) + { + } + }; } -void User::WriteCommonExcept(const char* text, ...) +void User::WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self) { - char textbuffer[MAXBUF]; - va_list argsPtr; - - if (this->registered != REG_ALL || quitting) - return; - - int len = snprintf(textbuffer,MAXBUF,":%s ",this->GetFullHost().c_str()); - - va_start(argsPtr, text); - vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr); - va_end(argsPtr); - - this->WriteCommonRaw(std::string(textbuffer), false); + WriteCommonRawHandler handler(protoev); + ForEachNeighbor(handler, include_self); } -void User::WriteCommonRaw(const std::string &line, bool include_self) +void User::ForEachNeighbor(ForEachNeighborHandler& handler, bool include_self) { - if (this->registered != REG_ALL || quitting) - return; - - LocalUser::already_sent_id++; - - UserChanList include_c(chans); - std::map<User*,bool> exceptions; + // The basic logic for visiting the neighbors of a user is to iterate the channel list of the user + // and visit all users on those channels. Because two users may share more than one common channel, + // we must skip users that we have already visited. + // To do this, we make use of a global counter and an integral 'already_sent' field in LocalUser. + // The global counter is incremented every time we do something for each neighbor of a user. Then, + // before visiting a member we examine user->already_sent. If it's equal to the current counter, we + // skip the member. Otherwise, we set it to the current counter and visit the member. + // Ask modules to build a list of exceptions. + // Mods may also exclude entire channels by erasing them from include_chans. + IncludeChanList include_chans(chans.begin(), chans.end()); + std::map<User*, bool> exceptions; exceptions[this] = include_self; + FOREACH_MOD(OnBuildNeighborList, (this, include_chans, exceptions)); - FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions)); + // Get next id, guaranteed to differ from the already_sent field of all users + const already_sent_t newid = ServerInstance->Users.NextAlreadySentId(); - for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i) + // Handle exceptions first + for (std::map<User*, bool>::const_iterator i = exceptions.begin(); i != exceptions.end(); ++i) { - LocalUser* u = IS_LOCAL(i->first); - if (u && !u->quitting) + LocalUser* curr = IS_LOCAL(i->first); + if (curr) { - u->already_sent = LocalUser::already_sent_id; - if (i->second) - u->Write(line); + // Mark as visited to ensure we won't visit again if there is a common channel + curr->already_sent = newid; + // Always treat quitting users as excluded + if ((i->second) && (!curr->quitting)) + handler.Execute(curr); } } - for (UCListIter v = include_c.begin(); v != include_c.end(); ++v) - { - Channel* c = *v; - const UserMembList* ulist = c->GetUsers(); - for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++) - { - LocalUser* u = IS_LOCAL(i->first); - if (u && !u->quitting && u->already_sent != LocalUser::already_sent_id) - { - u->already_sent = LocalUser::already_sent_id; - u->Write(line); - } - } - } -} -void User::WriteCommonQuit(const std::string &normal_text, const std::string &oper_text) -{ - char tb1[MAXBUF]; - char tb2[MAXBUF]; - - if (this->registered != REG_ALL) - return; - - already_sent_t uniq_id = ++LocalUser::already_sent_id; - - snprintf(tb1,MAXBUF,":%s QUIT :%s",this->GetFullHost().c_str(),normal_text.c_str()); - snprintf(tb2,MAXBUF,":%s QUIT :%s",this->GetFullHost().c_str(),oper_text.c_str()); - std::string out1 = tb1; - std::string out2 = tb2; - - UserChanList include_c(chans); - std::map<User*,bool> exceptions; - - FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions)); - - for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i) + // Now consider the real neighbors + for (IncludeChanList::const_iterator i = include_chans.begin(); i != include_chans.end(); ++i) { - LocalUser* u = IS_LOCAL(i->first); - if (u && !u->quitting) + Channel* chan = (*i)->chan; + const Channel::MemberMap& userlist = chan->GetUsers(); + for (Channel::MemberMap::const_iterator j = userlist.begin(); j != userlist.end(); ++j) { - u->already_sent = uniq_id; - if (i->second) - u->Write(IS_OPER(u) ? out2 : out1); - } - } - for (UCListIter v = include_c.begin(); v != include_c.end(); ++v) - { - const UserMembList* ulist = (*v)->GetUsers(); - for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++) - { - LocalUser* u = IS_LOCAL(i->first); - if (u && !u->quitting && (u->already_sent != uniq_id)) + LocalUser* curr = IS_LOCAL(j->first); + // User not yet visited? + if ((curr) && (curr->already_sent != newid)) { - u->already_sent = uniq_id; - u->Write(IS_OPER(u) ? out2 : out1); + // Mark as visited and execute function + curr->already_sent = newid; + handler.Execute(curr); } } } } -void LocalUser::SendText(const std::string& line) +void User::WriteRemoteNumeric(const Numeric::Numeric& numeric) { - Write(line); -} - -void RemoteUser::SendText(const std::string& line) -{ - ServerInstance->PI->PushToClient(this, line); -} - -void FakeUser::SendText(const std::string& line) -{ -} - -void User::SendText(const char *text, ...) -{ - va_list argsPtr; - char line[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(line, MAXBUF, text, argsPtr); - va_end(argsPtr); - - SendText(std::string(line)); -} - -void User::SendText(const std::string &LinePrefix, std::stringstream &TextStream) -{ - std::string line; - std::string Word; - while (TextStream >> Word) - { - size_t lineLength = LinePrefix.length() + line.length() + Word.length() + 13; - if (lineLength > MAXBUF) - { - SendText(LinePrefix + line); - line.clear(); - } - line += " " + Word; - } - SendText(LinePrefix + line); + WriteNumeric(numeric); } /* return 0 or 1 depending if users u and u2 share one or more common channels @@ -1350,221 +949,103 @@ void User::SendText(const std::string &LinePrefix, std::stringstream &TextStream */ bool User::SharesChannelWith(User *other) { - if ((!other) || (this->registered != REG_ALL) || (other->registered != REG_ALL)) - return false; - /* Outer loop */ - for (UCListIter i = this->chans.begin(); i != this->chans.end(); i++) + for (User::ChanList::iterator i = this->chans.begin(); i != this->chans.end(); ++i) { /* Eliminate the inner loop (which used to be ~equal in size to the outer loop) * by replacing it with a map::find which *should* be more efficient */ - if ((*i)->HasUser(other)) + if ((*i)->chan->HasUser(other)) return true; } return false; } -bool User::ChangeName(const char* gecos) +bool User::ChangeRealName(const std::string& real) { - if (!this->fullname.compare(gecos)) + if (!this->realname.compare(real)) return true; if (IS_LOCAL(this)) { ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnChangeLocalUserGECOS, MOD_RESULT, (IS_LOCAL(this),gecos)); + FIRST_MOD_RESULT(OnPreChangeRealName, MOD_RESULT, (IS_LOCAL(this), real)); if (MOD_RESULT == MOD_RES_DENY) return false; - FOREACH_MOD(I_OnChangeName,OnChangeName(this,gecos)); + FOREACH_MOD(OnChangeRealName, (this, real)); } - this->fullname.assign(gecos, 0, ServerInstance->Config->Limits.MaxGecos); + this->realname.assign(real, 0, ServerInstance->Config->Limits.MaxReal); return true; } -void User::DoHostCycle(const std::string &quitline) -{ - char buffer[MAXBUF]; - - if (!ServerInstance->Config->CycleHosts) - return; - - already_sent_t silent_id = ++LocalUser::already_sent_id; - already_sent_t seen_id = ++LocalUser::already_sent_id; - - UserChanList include_c(chans); - std::map<User*,bool> exceptions; - - FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions)); - - // Users shouldn't see themselves quitting when host cycling - exceptions.erase(this); - for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i) - { - LocalUser* u = IS_LOCAL(i->first); - if (u && !u->quitting) - { - if (i->second) - { - u->already_sent = seen_id; - u->Write(quitline); - } - else - { - u->already_sent = silent_id; - } - } - } - for (UCListIter v = include_c.begin(); v != include_c.end(); ++v) - { - Channel* c = *v; - snprintf(buffer, MAXBUF, ":%s JOIN %s", GetFullHost().c_str(), c->name.c_str()); - std::string joinline(buffer); - Membership* memb = c->GetUser(this); - std::string modeline = memb->modes; - if (modeline.length() > 0) - { - for(unsigned int i=0; i < memb->modes.length(); i++) - modeline.append(" ").append(nick); - snprintf(buffer, MAXBUF, ":%s MODE %s +%s", - ServerInstance->Config->CycleHostsFromUser ? GetFullHost().c_str() : ServerInstance->Config->ServerName.c_str(), - c->name.c_str(), modeline.c_str()); - modeline = buffer; - } - - const UserMembList *ulist = c->GetUsers(); - for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++) - { - LocalUser* u = IS_LOCAL(i->first); - if (u == NULL || u == this) - continue; - if (u->already_sent == silent_id) - continue; - - if (u->already_sent != seen_id) - { - u->Write(quitline); - u->already_sent = seen_id; - } - u->Write(joinline); - if (modeline.length() > 0) - u->Write(modeline); - } - } -} - -bool User::ChangeDisplayedHost(const char* shost) +bool User::ChangeDisplayedHost(const std::string& shost) { - if (dhost == shost) + if (GetDisplayedHost() == shost) return true; - if (IS_LOCAL(this)) + LocalUser* luser = IS_LOCAL(this); + if (luser) { ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnChangeLocalUserHost, MOD_RESULT, (IS_LOCAL(this),shost)); + FIRST_MOD_RESULT(OnPreChangeHost, MOD_RESULT, (luser, shost)); if (MOD_RESULT == MOD_RES_DENY) return false; } - FOREACH_MOD(I_OnChangeHost, OnChangeHost(this,shost)); - - std::string quitstr = ":" + GetFullHost() + " QUIT :Changing host"; + FOREACH_MOD(OnChangeHost, (this,shost)); - /* Fix by Om: User::dhost is 65 long, this was truncating some long hosts */ - this->dhost.assign(shost, 0, 64); + if (realhost == shost) + this->displayhost.clear(); + else + this->displayhost.assign(shost, 0, ServerInstance->Config->Limits.MaxHost); this->InvalidateCache(); - this->DoHostCycle(quitstr); - if (IS_LOCAL(this)) - this->WriteNumeric(RPL_YOURDISPLAYEDHOST, "%s %s :is now your displayed host",this->nick.c_str(),this->dhost.c_str()); + this->WriteNumeric(RPL_YOURDISPLAYEDHOST, this->GetDisplayedHost(), "is now your displayed host"); return true; } -bool User::ChangeIdent(const char* newident) +void User::ChangeRealHost(const std::string& host, bool resetdisplay) { - if (this->ident == newident) - return true; - - FOREACH_MOD(I_OnChangeIdent, OnChangeIdent(this,newident)); - - std::string quitstr = ":" + GetFullHost() + " QUIT :Changing ident"; - - this->ident.assign(newident, 0, ServerInstance->Config->Limits.IdentMax); + // If the real host is the new host and we are not resetting the + // display host then we have nothing to do. + const bool changehost = (realhost != host); + if (!changehost && !resetdisplay) + return; + + // If the displayhost is not set and we are not resetting it then + // we need to copy it to the displayhost field. + if (displayhost.empty() && !resetdisplay) + displayhost = realhost; + + // If the displayhost is the new host or we are resetting it then + // we clear its contents to save memory. + else if (displayhost == host || resetdisplay) + displayhost.clear(); + + // If we are just resetting the display host then we don't need to + // do anything else. + if (!changehost) + return; + realhost = host; this->InvalidateCache(); - - this->DoHostCycle(quitstr); - - return true; } -void User::SendAll(const char* command, const char* text, ...) +bool User::ChangeIdent(const std::string& newident) { - char textbuffer[MAXBUF]; - char formatbuffer[MAXBUF]; - va_list argsPtr; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - snprintf(formatbuffer,MAXBUF,":%s %s $* :%s", this->GetFullHost().c_str(), command, textbuffer); - std::string fmt = formatbuffer; - - for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); i++) - { - if ((*i)->registered == REG_ALL) - (*i)->Write(fmt); - } -} - - -std::string User::ChannelList(User* source, bool spy) -{ - std::string list; - - for (UCListIter i = this->chans.begin(); i != this->chans.end(); i++) - { - Channel* c = *i; - /* If the target is the sender, neither +p nor +s is set, or - * the channel contains the user, it is not a spy channel - */ - if (spy != (source == this || !(c->IsModeSet('p') || c->IsModeSet('s')) || c->HasUser(source))) - list.append(c->GetPrefixChar(this)).append(c->name).append(" "); - } - - return list; -} - -void User::SplitChanList(User* dest, const std::string &cl) -{ - std::string line; - std::ostringstream prefix; - std::string::size_type start, pos; - - prefix << this->nick << " " << dest->nick << " :"; - line = prefix.str(); - int namelen = ServerInstance->Config->ServerName.length() + 6; + if (this->ident == newident) + return true; - for (start = 0; (pos = cl.find(' ', start)) != std::string::npos; start = pos+1) - { - if (line.length() + namelen + pos - start > 510) - { - ServerInstance->SendWhoisLine(this, dest, 319, "%s", line.c_str()); - line = prefix.str(); - } + FOREACH_MOD(OnChangeIdent, (this,newident)); - line.append(cl.substr(start, pos - start + 1)); - } + this->ident.assign(newident, 0, ServerInstance->Config->Limits.IdentMax); + this->InvalidateCache(); - if (line.length() != prefix.str().length()) - { - ServerInstance->SendWhoisLine(this, dest, 319, "%s", line.c_str()); - } + return true; } /* @@ -1578,27 +1059,27 @@ void LocalUser::SetClass(const std::string &explicit_name) { ConnectClass *found = NULL; - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Setting connect class for UID %s", this->uuid.c_str()); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Setting connect class for UID %s", this->uuid.c_str()); if (!explicit_name.empty()) { - for (ClassVector::iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++) + for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i) { ConnectClass* c = *i; if (explicit_name == c->name) { - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Explicitly set to %s", explicit_name.c_str()); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Explicitly set to %s", explicit_name.c_str()); found = c; } } } else { - for (ClassVector::iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++) + for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i) { ConnectClass* c = *i; - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Checking %s", c->GetName().c_str()); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Checking %s", c->GetName().c_str()); ModResult MOD_RESULT; FIRST_MOD_RESULT(OnSetConnectClass, MOD_RESULT, (this,c)); @@ -1606,7 +1087,7 @@ void LocalUser::SetClass(const std::string &explicit_name) continue; if (MOD_RESULT == MOD_RES_ALLOW) { - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Class forced by module to %s", c->GetName().c_str()); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class forced by module to %s", c->GetName().c_str()); found = c; break; } @@ -1620,9 +1101,9 @@ void LocalUser::SetClass(const std::string &explicit_name) /* check if host matches.. */ if (!InspIRCd::MatchCIDR(this->GetIPString(), c->GetHost(), NULL) && - !InspIRCd::MatchCIDR(this->host, c->GetHost(), NULL)) + !InspIRCd::MatchCIDR(this->GetRealHost(), c->GetHost(), NULL)) { - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "No host match (for %s)", c->GetHost().c_str()); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "No host match (for %s)", c->GetHost().c_str()); continue; } @@ -1632,26 +1113,26 @@ void LocalUser::SetClass(const std::string &explicit_name) */ if (c->limit && (c->GetReferenceCount() >= c->limit)) { - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "OOPS: Connect class limit (%lu) hit, denying", c->limit); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "OOPS: Connect class limit (%lu) hit, denying", c->limit); continue; } /* if it requires a port ... */ - int port = c->config->getInt("port"); - if (port) + if (!c->ports.empty()) { - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Requires port (%d)", port); - /* and our port doesn't match, fail. */ - if (this->GetServerPort() != port) + if (!c->ports.count(this->GetServerPort())) + { + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Requires a different port, skipping"); continue; + } } if (regdone && !c->config->getString("password").empty()) { - if (ServerInstance->PassCompare(this, c->config->getString("password"), password, c->config->getString("hash"))) + if (!ServerInstance->PassCompare(this, c->config->getString("password"), password, c->config->getString("hash"))) { - ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Bad password, skipping"); + ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Bad password, skipping"); continue; } } @@ -1671,62 +1152,82 @@ void LocalUser::SetClass(const std::string &explicit_name) } } -/* looks up a users password for their connection class (<ALLOW>/<DENY> tags) - * NOTE: If the <ALLOW> or <DENY> tag specifies an ip, and this user resolves, - * then their ip will be taken as 'priority' anyway, so for example, - * <connect allow="127.0.0.1"> will match joe!bloggs@localhost - */ -ConnectClass* LocalUser::GetClass() -{ - return MyClass; -} - -ConnectClass* User::GetClass() -{ - return NULL; -} - void User::PurgeEmptyChannels() { // firstly decrement the count on each channel - for (UCListIter f = this->chans.begin(); f != this->chans.end(); f++) + for (User::ChanList::iterator i = this->chans.begin(); i != this->chans.end(); ) { - Channel* c = *f; + Channel* c = (*i)->chan; + ++i; c->DelUser(this); } this->UnOper(); } +void User::WriteNotice(const std::string& text) +{ + LocalUser* const localuser = IS_LOCAL(this); + if (!localuser) + return; + + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, text, MSG_NOTICE); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); +} + const std::string& FakeUser::GetFullHost() { - if (!ServerInstance->Config->HideWhoisServer.empty()) - return ServerInstance->Config->HideWhoisServer; - return server; + if (!ServerInstance->Config->HideServer.empty()) + return ServerInstance->Config->HideServer; + return server->GetName(); } const std::string& FakeUser::GetFullRealHost() { - if (!ServerInstance->Config->HideWhoisServer.empty()) - return ServerInstance->Config->HideWhoisServer; - return server; + if (!ServerInstance->Config->HideServer.empty()) + return ServerInstance->Config->HideServer; + return server->GetName(); } ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask) : config(tag), type(t), fakelag(true), name("unnamed"), registration_timeout(0), host(mask), pingtime(0), softsendqmax(0), hardsendqmax(0), recvqmax(0), - penaltythreshold(0), commandrate(0), maxlocal(0), maxglobal(0), maxconnwarn(true), maxchans(0), limit(0) + penaltythreshold(0), commandrate(0), maxlocal(0), maxglobal(0), maxconnwarn(true), maxchans(ServerInstance->Config->MaxChans), + limit(0), resolvehostnames(true) { } ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask, const ConnectClass& parent) - : config(tag), type(t), fakelag(parent.fakelag), name("unnamed"), - registration_timeout(parent.registration_timeout), host(mask), pingtime(parent.pingtime), - softsendqmax(parent.softsendqmax), hardsendqmax(parent.hardsendqmax), recvqmax(parent.recvqmax), - penaltythreshold(parent.penaltythreshold), commandrate(parent.commandrate), - maxlocal(parent.maxlocal), maxglobal(parent.maxglobal), maxconnwarn(parent.maxconnwarn), maxchans(parent.maxchans), - limit(parent.limit) { + Update(&parent); + name = "unnamed"; + type = t; + host = mask; + + // Connect classes can inherit from each other but this is problematic for modules which can't use + // ConnectClass::Update so we build a hybrid tag containing all of the values set on this class as + // well as the parent class. + ConfigItems* items = NULL; + config = ConfigTag::create(tag->tag, tag->src_name, tag->src_line, items); + + const ConfigItems& parentkeys = parent.config->getItems(); + for (ConfigItems::const_iterator piter = parentkeys.begin(); piter != parentkeys.end(); ++piter) + { + // The class name and parent name are not inherited + if (stdalgo::string::equalsci(piter->first, "name") || stdalgo::string::equalsci(piter->first, "parent")) + continue; + + // Store the item in the config tag. If this item also + // exists in the child it will be overwritten. + (*items)[piter->first] = piter->second; + } + + const ConfigItems& childkeys = tag->getItems(); + for (ConfigItems::const_iterator citer = childkeys.begin(); citer != childkeys.end(); ++citer) + { + // This will overwrite the parent value if present. + (*items)[citer->first] = citer->second; + } } void ConnectClass::Update(const ConnectClass* src) @@ -1748,4 +1249,6 @@ void ConnectClass::Update(const ConnectClass* src) maxconnwarn = src->maxconnwarn; maxchans = src->maxchans; limit = src->limit; + resolvehostnames = src->resolvehostnames; + ports = src->ports; } diff --git a/src/version.sh b/src/version.sh index b082ad6e1..4423ad84a 100755 --- a/src/version.sh +++ b/src/version.sh @@ -1,2 +1,2 @@ #!/bin/sh -echo "InspIRCd-2.0.26" +echo "InspIRCd-3.0.0-a9" diff --git a/src/whois.cpp b/src/whois.cpp deleted file mode 100644 index bec9c7ea9..000000000 --- a/src/whois.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" - -void InspIRCd::DoWhois(User* user, User* dest,unsigned long signon, unsigned long idle, const char* nick) -{ - this->SendWhoisLine(user, dest, 311, "%s %s %s %s * :%s",user->nick.c_str(), dest->nick.c_str(), dest->ident.c_str(), dest->dhost.c_str(), dest->fullname.c_str()); - if (user == dest || user->HasPrivPermission("users/auspex")) - { - this->SendWhoisLine(user, dest, 378, "%s %s :is connecting from %s@%s %s", user->nick.c_str(), dest->nick.c_str(), dest->ident.c_str(), dest->host.c_str(), dest->GetIPString()); - } - - std::string cl = dest->ChannelList(user, false); - const ServerConfig::OperSpyWhoisState state = user->HasPrivPermission("users/auspex") ? ServerInstance->Config->OperSpyWhois : ServerConfig::SPYWHOIS_NONE; - - if (state == ServerConfig::SPYWHOIS_SINGLEMSG) - cl.append(dest->ChannelList(user, true)); - - user->SplitChanList(dest,cl); - - if (state == ServerConfig::SPYWHOIS_SPLITMSG) - { - std::string scl = dest->ChannelList(user, true); - if (scl.length()) - { - SendWhoisLine(user, dest, 336, "%s %s :is on private/secret channels:",user->nick.c_str(), dest->nick.c_str()); - user->SplitChanList(dest,scl); - } - } - if (user != dest && !this->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex")) - { - this->SendWhoisLine(user, dest, 312, "%s %s %s :%s",user->nick.c_str(), dest->nick.c_str(), this->Config->HideWhoisServer.c_str(), this->Config->Network.c_str()); - } - else - { - std::string serverdesc = GetServerDescription(dest->server); - this->SendWhoisLine(user, dest, 312, "%s %s %s :%s",user->nick.c_str(), dest->nick.c_str(), dest->server.c_str(), serverdesc.c_str()); - } - - if (IS_AWAY(dest)) - { - this->SendWhoisLine(user, dest, 301, "%s %s :%s",user->nick.c_str(), dest->nick.c_str(), dest->awaymsg.c_str()); - } - - if (IS_OPER(dest)) - { - if (this->Config->GenericOper) - this->SendWhoisLine(user, dest, 313, "%s %s :is an IRC operator",user->nick.c_str(), dest->nick.c_str()); - else - this->SendWhoisLine(user, dest, 313, "%s %s :is %s %s on %s",user->nick.c_str(), dest->nick.c_str(), (strchr("AEIOUaeiou",dest->oper->name[0]) ? "an" : "a"),dest->oper->NameStr(), this->Config->Network.c_str()); - } - - if (user == dest || user->HasPrivPermission("users/auspex")) - { - if (dest->IsModeSet('s') != 0) - { - this->SendWhoisLine(user, dest, 379, "%s %s :is using modes +%s +%s", user->nick.c_str(), dest->nick.c_str(), dest->FormatModes(), dest->FormatNoticeMasks()); - } - else - { - this->SendWhoisLine(user, dest, 379, "%s %s :is using modes +%s", user->nick.c_str(), dest->nick.c_str(), dest->FormatModes()); - } - } - - FOREACH_MOD(I_OnWhois,OnWhois(user,dest)); - - /* - * We only send these if we've been provided them. That is, if hidewhois is turned off, and user is local, or - * if remote whois is queried, too. This is to keep the user hidden, and also since you can't reliably tell remote time. -- w00t - */ - if ((idle) || (signon)) - { - this->SendWhoisLine(user, dest, 317, "%s %s %lu %lu :seconds idle, signon time",user->nick.c_str(), dest->nick.c_str(), idle, signon); - } - - this->SendWhoisLine(user, dest, 318, "%s %s :End of /WHOIS list.",user->nick.c_str(), dest->nick.c_str()); -} - - - diff --git a/src/wildcard.cpp b/src/wildcard.cpp index eb9151293..4a313af76 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -19,15 +19,12 @@ */ -/* $Core */ - #include "inspircd.h" -#include "hashcomp.h" -#include "inspstring.h" -static bool match_internal(const unsigned char *str, const unsigned char *mask, unsigned const char *map) +static bool MatchInternal(const unsigned char* str, const unsigned char* mask, unsigned const char* map) { - unsigned char *cp = NULL, *mp = NULL; + unsigned char* cp = NULL; + unsigned char* mp = NULL; unsigned char* string = (unsigned char*)str; unsigned char* wild = (unsigned char*)mask; @@ -74,46 +71,53 @@ static bool match_internal(const unsigned char *str, const unsigned char *mask, return !*wild; } -/******************************************************************** - * Below here is all wrappers around match_internal - ********************************************************************/ +// Below here is all wrappers around MatchInternal -CoreExport bool InspIRCd::Match(const std::string &str, const std::string &mask, unsigned const char *map) +bool InspIRCd::Match(const std::string& str, const std::string& mask, unsigned const char* map) { if (!map) map = national_case_insensitive_map; - return match_internal((const unsigned char *)str.c_str(), (const unsigned char *)mask.c_str(), map); + return MatchInternal((const unsigned char*)str.c_str(), (const unsigned char*)mask.c_str(), map); } -CoreExport bool InspIRCd::Match(const char *str, const char *mask, unsigned const char *map) +bool InspIRCd::Match(const char* str, const char* mask, unsigned const char* map) { if (!map) map = national_case_insensitive_map; - return match_internal((const unsigned char *)str, (const unsigned char *)mask, map); + + return MatchInternal((const unsigned char*)str, (const unsigned char*)mask, map); } -CoreExport bool InspIRCd::MatchCIDR(const std::string &str, const std::string &mask, unsigned const char *map) +bool InspIRCd::MatchCIDR(const std::string& str, const std::string& mask, unsigned const char* map) { if (irc::sockets::MatchCIDR(str, mask, true)) return true; - if (!map) - map = national_case_insensitive_map; - // Fall back to regular match return InspIRCd::Match(str, mask, map); } -CoreExport bool InspIRCd::MatchCIDR(const char *str, const char *mask, unsigned const char *map) +bool InspIRCd::MatchCIDR(const char* str, const char* mask, unsigned const char* map) { if (irc::sockets::MatchCIDR(str, mask, true)) return true; - if (!map) - map = national_case_insensitive_map; - // Fall back to regular match return InspIRCd::Match(str, mask, map); } +bool InspIRCd::MatchMask(const std::string& masks, const std::string& hostname, const std::string& ipaddr) +{ + irc::spacesepstream masklist(masks); + std::string mask; + while (masklist.GetToken(mask)) + { + if (InspIRCd::Match(hostname, mask, ascii_case_insensitive_map) || + InspIRCd::MatchCIDR(ipaddr, mask, ascii_case_insensitive_map)) + { + return true; + } + } + return false; +} diff --git a/src/xline.cpp b/src/xline.cpp index 586c7342a..d07fe0dc5 100644 --- a/src/xline.cpp +++ b/src/xline.cpp @@ -23,7 +23,7 @@ #include "inspircd.h" #include "xline.h" -#include "bancache.h" +#include "modules/stats.h" /** An XLineFactory specialized to generate GLine* pointers */ @@ -34,7 +34,7 @@ class GLineFactory : public XLineFactory /** Generate a GLine */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { IdentHostPair ih = ServerInstance->XLines->IdentSplit(xline_specific_mask); return new GLine(set_time, duration, source, reason, ih.first, ih.second); @@ -50,7 +50,7 @@ class ELineFactory : public XLineFactory /** Generate an ELine */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { IdentHostPair ih = ServerInstance->XLines->IdentSplit(xline_specific_mask); return new ELine(set_time, duration, source, reason, ih.first, ih.second); @@ -66,7 +66,7 @@ class KLineFactory : public XLineFactory /** Generate a KLine */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { IdentHostPair ih = ServerInstance->XLines->IdentSplit(xline_specific_mask); return new KLine(set_time, duration, source, reason, ih.first, ih.second); @@ -82,7 +82,7 @@ class QLineFactory : public XLineFactory /** Generate a QLine */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { return new QLine(set_time, duration, source, reason, xline_specific_mask); } @@ -97,7 +97,7 @@ class ZLineFactory : public XLineFactory /** Generate a ZLine */ - XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) + XLine* Generate(time_t set_time, long duration, std::string source, std::string reason, std::string xline_specific_mask) CXX11_OVERRIDE { return new ZLine(set_time, duration, source, reason, xline_specific_mask); } @@ -156,9 +156,10 @@ void XLineManager::CheckELines() if (ELines.empty()) return; - for (LocalUserList::const_iterator u2 = ServerInstance->Users->local_users.begin(); u2 != ServerInstance->Users->local_users.end(); u2++) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator u2 = list.begin(); u2 != list.end(); u2++) { - User* u = (User*)(*u2); + LocalUser* u = *u2; u->exempt = false; /* This uses safe iteration to ensure that if a line expires here, it doenst trash the iterator */ @@ -278,7 +279,7 @@ bool XLineManager::AddLine(XLine* line, User* user) if (!xlf) return false; - ServerInstance->BanCache->RemoveEntries(line->type, false); // XXX perhaps remove ELines here? + ServerInstance->BanCache.RemoveEntries(line->type, false); // XXX perhaps remove ELines here? if (xlf->AutoApplyToUserList(line)) pending_lines.push_back(line); @@ -286,7 +287,7 @@ bool XLineManager::AddLine(XLine* line, User* user) lookup_lines[line->type][line->Displayable()] = line; line->OnAdd(); - FOREACH_MOD(I_OnAddLine,OnAddLine(user, line)); + FOREACH_MOD(OnAddLine, (user, line)); return true; } @@ -308,15 +309,13 @@ bool XLineManager::DelLine(const char* hostmask, const std::string &type, User* if (simulate) return true; - ServerInstance->BanCache->RemoveEntries(y->second->type, true); + ServerInstance->BanCache.RemoveEntries(y->second->type, true); - FOREACH_MOD(I_OnDelLine,OnDelLine(user, y->second)); + FOREACH_MOD(OnDelLine, (user, y->second)); y->second->Unset(); - std::vector<XLine*>::iterator pptr = std::find(pending_lines.begin(), pending_lines.end(), y->second); - if (pptr != pending_lines.end()) - pending_lines.erase(pptr); + stdalgo::erase(pending_lines, y->second); delete y->second; x->second.erase(y); @@ -404,7 +403,7 @@ XLine* XLineManager::MatchesLine(const std::string &type, const std::string &pat // removes lines that have expired void XLineManager::ExpireLine(ContainerIter container, LookupIter item) { - FOREACH_MOD(I_OnExpireLine, OnExpireLine(item->second)); + FOREACH_MOD(OnExpireLine, (item->second)); item->second->DisplayExpiry(); item->second->Unset(); @@ -413,9 +412,7 @@ void XLineManager::ExpireLine(ContainerIter container, LookupIter item) * is pending, cleared when it is no longer pending, so we skip over this loop if its not pending? * -- Brain */ - std::vector<XLine*>::iterator pptr = std::find(pending_lines.begin(), pending_lines.end(), item->second); - if (pptr != pending_lines.end()) - pending_lines.erase(pptr); + stdalgo::erase(pending_lines, item->second); delete item->second; container->second.erase(item); @@ -425,10 +422,10 @@ void XLineManager::ExpireLine(ContainerIter container, LookupIter item) // applies lines, removing clients and changing nicks etc as applicable void XLineManager::ApplyLines() { - LocalUserList::reverse_iterator u2 = ServerInstance->Users->local_users.rbegin(); - while (u2 != ServerInstance->Users->local_users.rend()) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator j = list.begin(); j != list.end(); ++j) { - User* u = *u2++; + LocalUser* u = *j; // Don't ban people who are exempt. if (u->exempt) @@ -445,7 +442,7 @@ void XLineManager::ApplyLines() pending_lines.clear(); } -void XLineManager::InvokeStats(const std::string &type, int numeric, User* user, string_list &results) +void XLineManager::InvokeStats(const std::string& type, unsigned int numeric, Stats::Context& stats) { ContainerIter n = lookup_lines.find(type); @@ -466,7 +463,7 @@ void XLineManager::InvokeStats(const std::string &type, int numeric, User* user, ExpireLine(n, i); } else - results.push_back(ServerInstance->Config->ServerName+" "+ConvToStr(numeric)+" "+user->nick+" :"+i->second->Displayable()+" "+ + stats.AddRow(numeric, i->second->Displayable()+" "+ ConvToStr(i->second->set_time)+" "+ConvToStr(i->second->duration)+" "+i->second->source+" :"+i->second->reason); i = safei; } @@ -521,40 +518,38 @@ void XLine::Apply(User* u) bool XLine::IsBurstable() { - return true; + return !from_config; } void XLine::DefaultApply(User* u, const std::string &line, bool bancache) { - char sreason[MAXBUF]; - snprintf(sreason, MAXBUF, "%s-Lined: %s", line.c_str(), this->reason.c_str()); - if (!ServerInstance->Config->MoronBanner.empty()) - u->WriteServ("NOTICE %s :*** %s", u->nick.c_str(), ServerInstance->Config->MoronBanner.c_str()); + const std::string banReason = line + "-Lined: " + reason; + + if (!ServerInstance->Config->XLineMessage.empty()) + u->WriteNumeric(ERR_YOUREBANNEDCREEP, ServerInstance->Config->XLineMessage); if (ServerInstance->Config->HideBans) - ServerInstance->Users->QuitUser(u, line + "-Lined", sreason); + ServerInstance->Users->QuitUser(u, line + "-Lined", &banReason); else - ServerInstance->Users->QuitUser(u, sreason); + ServerInstance->Users->QuitUser(u, banReason); if (bancache) { - ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCache: Adding positive hit (" + line + ") for " + u->GetIPString()); - if (this->duration > 0) - ServerInstance->BanCache->AddHit(u->GetIPString(), this->type, line + "-Lined: " + this->reason, this->duration); - else - ServerInstance->BanCache->AddHit(u->GetIPString(), this->type, line + "-Lined: " + this->reason); + ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Adding positive hit (" + line + ") for " + u->GetIPString()); + ServerInstance->BanCache.AddHit(u->GetIPString(), this->type, banReason, this->duration); } } bool KLine::Matches(User *u) { - if (u->exempt) + LocalUser* lu = IS_LOCAL(u); + if (lu && lu->exempt) return false; if (InspIRCd::Match(u->ident, this->identmask, ascii_case_insensitive_map)) { - if (InspIRCd::MatchCIDR(u->host, this->hostmask, ascii_case_insensitive_map) || + if (InspIRCd::MatchCIDR(u->GetRealHost(), this->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(u->GetIPString(), this->hostmask, ascii_case_insensitive_map)) { return true; @@ -571,12 +566,13 @@ void KLine::Apply(User* u) bool GLine::Matches(User *u) { - if (u->exempt) + LocalUser* lu = IS_LOCAL(u); + if (lu && lu->exempt) return false; if (InspIRCd::Match(u->ident, this->identmask, ascii_case_insensitive_map)) { - if (InspIRCd::MatchCIDR(u->host, this->hostmask, ascii_case_insensitive_map) || + if (InspIRCd::MatchCIDR(u->GetRealHost(), this->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(u->GetIPString(), this->hostmask, ascii_case_insensitive_map)) { return true; @@ -595,7 +591,7 @@ bool ELine::Matches(User *u) { if (InspIRCd::Match(u->ident, this->identmask, ascii_case_insensitive_map)) { - if (InspIRCd::MatchCIDR(u->host, this->hostmask, ascii_case_insensitive_map) || + if (InspIRCd::MatchCIDR(u->GetRealHost(), this->hostmask, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(u->GetIPString(), this->hostmask, ascii_case_insensitive_map)) { return true; @@ -607,7 +603,8 @@ bool ELine::Matches(User *u) bool ZLine::Matches(User *u) { - if (u->exempt) + LocalUser* lu = IS_LOCAL(u); + if (lu && lu->exempt) return false; if (InspIRCd::MatchCIDR(u->GetIPString(), this->ipaddr)) @@ -633,7 +630,7 @@ bool QLine::Matches(User *u) void QLine::Apply(User* u) { /* Force to uuid on apply of qline, no need to disconnect any more :) */ - u->ForceNickChange(u->uuid.c_str()); + u->ChangeNick(u->uuid); } @@ -671,67 +668,45 @@ bool GLine::Matches(const std::string &str) void ELine::OnAdd() { /* When adding one eline, only check the one eline */ - for (LocalUserList::const_iterator u2 = ServerInstance->Users->local_users.begin(); u2 != ServerInstance->Users->local_users.end(); u2++) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator u2 = list.begin(); u2 != list.end(); u2++) { - User* u = (User*)(*u2); + LocalUser* u = *u2; if (this->Matches(u)) u->exempt = true; } } -void ELine::DisplayExpiry() +void XLine::DisplayExpiry() { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired E-Line %s@%s (set by %s %ld seconds ago)", - identmask.c_str(),hostmask.c_str(),source.c_str(),(long)(ServerInstance->Time() - this->set_time)); + bool onechar = (type.length() == 1); + ServerInstance->SNO->WriteToSnoMask('x', "Removing expired %s%s %s (set by %s %ld seconds ago)", + type.c_str(), (onechar ? "-Line" : ""), Displayable().c_str(), source.c_str(), (long)(ServerInstance->Time() - set_time)); } -void QLine::DisplayExpiry() +const std::string& ELine::Displayable() { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired Q-Line %s (set by %s %ld seconds ago)", - nick.c_str(),source.c_str(),(long)(ServerInstance->Time() - this->set_time)); + return matchtext; } -void ZLine::DisplayExpiry() +const std::string& KLine::Displayable() { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired Z-Line %s (set by %s %ld seconds ago)", - ipaddr.c_str(),source.c_str(),(long)(ServerInstance->Time() - this->set_time)); + return matchtext; } -void KLine::DisplayExpiry() +const std::string& GLine::Displayable() { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired K-Line %s@%s (set by %s %ld seconds ago)", - identmask.c_str(),hostmask.c_str(),source.c_str(),(long)(ServerInstance->Time() - this->set_time)); + return matchtext; } -void GLine::DisplayExpiry() +const std::string& ZLine::Displayable() { - ServerInstance->SNO->WriteToSnoMask('x',"Removing expired G-Line %s@%s (set by %s %ld seconds ago)", - identmask.c_str(),hostmask.c_str(),source.c_str(),(long)(ServerInstance->Time() - this->set_time)); + return ipaddr; } -const char* ELine::Displayable() +const std::string& QLine::Displayable() { - return matchtext.c_str(); -} - -const char* KLine::Displayable() -{ - return matchtext.c_str(); -} - -const char* GLine::Displayable() -{ - return matchtext.c_str(); -} - -const char* ZLine::Displayable() -{ - return ipaddr.c_str(); -} - -const char* QLine::Displayable() -{ - return nick.c_str(); + return nick; } bool KLine::IsBurstable() @@ -772,3 +747,24 @@ XLineFactory* XLineManager::GetFactory(const std::string &type) return n->second; } + +void XLineManager::ClearConfigLines() +{ + // Nothing to do. + if (lookup_lines.empty()) + return; + + ServerInstance->SNO->WriteToSnoMask('x', "Server rehashing; expiring lines defined in the server config ..."); + for (ContainerIter type = lookup_lines.begin(); type != lookup_lines.end(); ++type) + { + for (LookupIter xline = type->second.begin(); xline != type->second.end(); ) + { + // We cache this to avoid iterator invalidation. + LookupIter cachedxline = xline++; + if (cachedxline->second->from_config) + { + ExpireLine(type, cachedxline); + } + } + } +} |