diff options
Diffstat (limited to 'src')
394 files changed, 28507 insertions, 28466 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..0ff3fbe4c 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; @@ -174,35 +192,35 @@ void Extensible::doUnhookExtensions(const std::vector<reference<ExtensionItem> > } } -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); } 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)); @@ -265,7 +298,8 @@ void LocalIntExt::free(void*) { } -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) { } @@ -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 9f1eafd0c..bc23c680a 100644 --- a/src/channels.cpp +++ b/src/channels.cpp @@ -23,172 +23,106 @@ */ -/* $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"); + ChanModeReference inviteonlymode(NULL, "inviteonly"); + ChanModeReference keymode(NULL, "key"); + ChanModeReference limitmode(NULL, "limit"); } -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; + this->WriteChannel(u, "TOPIC %s :%s", this->name.c_str(), this->topic.c_str()); } -} - -void Channel::SetModeParam(ModeHandler* mode, const std::string& parameter) -{ - SetModeParam(mode->GetModeChar(), parameter); -} -std::string Channel::GetModeParameter(char mode) -{ - CustomModeList::iterator n = custom_mode_params.find(mode); - if (n != custom_mode_params.end()) - return n->second; - return ""; -} + // 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(ModeHandler* mode) -{ - CustomModeList::iterator n = custom_mode_params.find(mode->GetModeChar()); - if (n != custom_mode_params.end()) - return n->second; - return ""; + FOREACH_MOD(OnPostTopicChange, (u, this, this->topic)); } -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)); - - return CMD_SUCCESS; -} + std::pair<MemberMap::iterator, bool> ret = userlist.insert(std::make_pair(user, insp::aligned_storage<Membership>())); + if (!ret.second) + return NULL; -long Channel::GetUserCounter() -{ - return userlist.size(); + Membership* memb = new(ret.first->second) Membership(user, this); + return memb; } -Membership* Channel::AddUser(User* user) +void Channel::DelUser(User* user) { - Membership* memb = new Membership(user, this); - userlist[user] = memb; - return memb; + MemberMap::iterator it = userlist.find(user); + if (it != userlist.end()) + DelUser(it); } -void Channel::DelUser(User* user) +void Channel::CheckDestroy() { - 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 +135,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 +148,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 +160,184 @@ 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); + + 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 (!Ptr) + 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 no module returned MOD_RES_DENY or MOD_RES_ALLOW (which is the case + // most of the time) then proceed to check channel modes +k, +i, +l and bans, + // in this order. + // If a module explicitly allowed the join (by returning MOD_RES_ALLOW), + // then this entire section is skipped + if (MOD_RESULT == MOD_RES_PASSTHRU) + { + std::string ckey = chan->GetModeParameter(keymode); if (!ckey.empty()) { - FIRST_MOD_RESULT(OnCheckKey, MOD_RESULT, (user, Ptr, key ? key : "")); - if (!MOD_RESULT.check((key && ckey == key) || can_bypass)) + FIRST_MOD_RESULT(OnCheckKey, MOD_RESULT, (user, chan, key)); + if (!MOD_RESULT.check(InspIRCd::TimingSafeCompare(ckey, key))) { // 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()); + user->WriteNumeric(ERR_BADCHANNELKEY, chan->name, "Cannot join channel (Incorrect channel key)"); return NULL; } } - if (Ptr->IsModeSet('i')) + if (chan->IsModeSet(inviteonlymode)) { - FIRST_MOD_RESULT(OnCheckInvite, MOD_RESULT, (user, Ptr)); - if (!MOD_RESULT.check(invited)) + FIRST_MOD_RESULT(OnCheckInvite, MOD_RESULT, (user, chan)); + if (MOD_RESULT != MOD_RES_ALLOW) { - user->WriteNumeric(ERR_INVITEONLYCHAN, "%s %s :Cannot join channel (Invite only)",user->nick.c_str(), Ptr->name.c_str()); + user->WriteNumeric(ERR_INVITEONLYCHAN, chan->name, "Cannot join channel (Invite only)"); return NULL; } } - std::string limit = Ptr->GetModeParameter('l'); + std::string limit = chan->GetModeParameter(limitmode); if (!limit.empty()) { - FIRST_MOD_RESULT(OnCheckLimit, MOD_RESULT, (user, Ptr)); - if (!MOD_RESULT.check((Ptr->GetUserCounter() < atol(limit.c_str()) || can_bypass))) + FIRST_MOD_RESULT(OnCheckLimit, MOD_RESULT, (user, chan)); + if (!MOD_RESULT.check((chan->GetUserCounter() < atol(limit.c_str())))) { - user->WriteNumeric(ERR_CHANNELISFULL, "%s %s :Cannot join channel (Channel is full)",user->nick.c_str(), Ptr->name.c_str()); + user->WriteNumeric(ERR_CHANNELISFULL, chan->name, "Cannot join channel (Channel is full)"); return NULL; } } - if (Ptr->IsBanned(user) && !can_bypass) + 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()); + this->WriteAllExcept(user, false, 0, except_list, "JOIN :%s", this->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())) + if ((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()); + this->WriteAllExcept(user, !ServerInstance->Config->CycleHostsFromUser, 0, except_list, "MODE %s +%s", this->name.c_str(), ms.c_str()); } - 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 +348,15 @@ 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); + 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,12 +376,11 @@ 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); + std::string suffix(mask, at + 1); if (InspIRCd::Match(user->host, suffix, NULL) || InspIRCd::Match(user->dhost, suffix, NULL) || InspIRCd::MatchCIDR(user->GetIPString(), suffix, NULL)) @@ -474,12 +395,14 @@ 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); + const ListModeBase::ModeList* bans = banlm->GetList(this); + if (bans) { - if (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 (CheckBan(user, it->mask)) return MOD_RES_DENY; } } @@ -487,150 +410,76 @@ 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) +bool Channel::PartUser(User* user, std::string& reason) { - if (!user) - return; + MemberMap::iterator membiter = userlist.find(user); - Membership* memb = GetUser(user); + if (membiter == userlist.end()) + return false; - if (memb) - { - CUList except_list; - FOREACH_MOD(I_OnUserPart,OnUserPart(memb, reason, except_list)); + Membership* memb = membiter->second; + CUList except_list; + FOREACH_MOD(OnUserPart, (memb, reason, except_list)); - WriteAllExcept(user, false, 0, except_list, "PART %s%s%s", this->name.c_str(), reason.empty() ? "" : " :", reason.c_str()); + 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); - } + // 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->DelUser(user); + return true; } -void Channel::KickUser(User *src, User *user, const char* reason) +void Channel::KickUser(User* src, const MemberMap::iterator& victimiter, const std::string& 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); + Membership* memb = victimiter->second; + CUList except_list; + FOREACH_MOD(OnUserKick, (src, memb, reason, except_list)); - user->chans.erase(this); - this->RemoveAllPrefixes(user); - } + User* victim = memb->user; + WriteAllExcept(src, false, 0, except_list, "KICK %s %s :%s", name.c_str(), victim->nick.c_str(), reason.c_str()); - this->DelUser(user); + victim->chans.erase(memb); + this->DelUser(victimiter); } 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)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteChannel(user, 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; + const std::string message = ":" + user->GetFullHost() + " " + text; - for (UserMembIter i = userlist.begin(); i != userlist.end(); i++) + for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++) { if (IS_LOCAL(i->first)) - i->first->Write(out); + i->first->Write(message); } } 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)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteChannelWithServ(ServName, 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; + const std::string message = ":" + (ServName.empty() ? ServerInstance->Config->ServerName : ServName) + " " + text; - for (UserMembIter i = userlist.begin(); i != userlist.end(); i++) + for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++) { if (IS_LOCAL(i->first)) - i->first->Write(out); + i->first->Write(message); } } @@ -638,43 +487,23 @@ void Channel::WriteChannelWithServ(const std::string& ServName, const std::strin * for the sender (for privmsg etc) */ void Channel::WriteAllExceptSender(User* user, bool serversource, char status, 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->WriteAllExceptSender(user, serversource, status, std::string(textbuffer)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteAllExceptSender(user, serversource, status, textbuffer); } void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...) { - char textbuffer[MAXBUF]; - va_list argsPtr; - - if (!text) - return; - - int offset = snprintf(textbuffer,MAXBUF,":%s ", serversource ? ServerInstance->Config->ServerName.c_str() : user->GetFullHost().c_str()); - - va_start(argsPtr, text); - vsnprintf(textbuffer + offset, MAXBUF - offset, text, argsPtr); - va_end(argsPtr); - - this->RawWriteAllExcept(user, serversource, status, except_list, std::string(textbuffer)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + textbuffer = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + textbuffer; + this->RawWriteAllExcept(user, serversource, status, except_list, textbuffer); } void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &text) { - char tb[MAXBUF]; - - snprintf(tb,MAXBUF,":%s %s", serversource ? ServerInstance->Config->ServerName.c_str() : user->GetFullHost().c_str(), text.c_str()); - - this->RawWriteAllExcept(user, serversource, status, except_list, std::string(tb)); + const std::string message = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + text; + this->RawWriteAllExcept(user, serversource, status, except_list, message); } void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &out) @@ -682,11 +511,11 @@ void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CULi 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())) { @@ -706,188 +535,64 @@ void Channel::WriteAllExceptSender(User* user, bool serversource, char status, c 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; + scratch += sparam; + return scratch.c_str(); } -/* 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) +void Channel::WriteNotice(const std::string& text) { - 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; -} - -void Channel::ResetMaxBans() -{ - this->maxbans = 0; + std::string rawmsg = "NOTICE "; + rawmsg.append(this->name).append(" :").append(text); + WriteChannelWithServ(ServerInstance->Config->ServerName, rawmsg); } /* 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 +604,50 @@ 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) -{ - if ((timeout != 0) && (ServerInstance->Time() >= timeout)) - // Expired, don't bother - 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; - } -} 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/command_parse.cpp b/src/command_parse.cpp index 76dfc06ce..9ffd20865 100644 --- a/src/command_parse.cpp +++ b/src/command_parse.cpp @@ -23,117 +23,93 @@ #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 std::vector<std::string>& 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(); - 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 + + CmdResult result = handler->Handle(new_parameters, user); + if (localuser) { - return true; + // Run the OnPostCommand hook with the last parameter (original line) being empty + // to indicate that the command had more targets in its original form. + item.clear(); + FOREACH_MOD(OnPostCommand, (handler, new_parameters, localuser, result, item)); } } } - 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 +118,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 std::vector<std::string>& parameters, User* user, Command** cmd) { - Commandtable::iterator n = cmdlist.find(commandname); + CommandMap::iterator n = cmdlist.find(commandname); if (n != cmdlist.end()) { @@ -174,6 +150,8 @@ CmdResult CommandParser::CallHandler(const std::string &commandname, const std:: if (bOkay) { + if (cmd) + *cmd = n->second; return n->second->Handle(parameters,user); } } @@ -181,7 +159,7 @@ CmdResult CommandParser::CallHandler(const std::string &commandname, const std:: return CMD_INVALID; } -bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) +void CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) { std::vector<std::string> command_p; irc::tokenstream tokens(cmd); @@ -196,23 +174,22 @@ bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) if (command[0] == ':') tokens.GetToken(command); - while (tokens.GetToken(token) && (command_p.size() <= MAXPARAMETERS)) + while (tokens.GetToken(token)) 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 @@ -222,13 +199,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)); if (MOD_RESULT == MOD_RES_DENY) - return true; + return; /* * This double lookup is in case a module (abbreviation) wishes to change a command. @@ -237,17 +213,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): @@ -256,32 +234,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 std::vector<std::string>::iterator lastkeep = command_p.begin() + (handler->max_params - 1); + // Iterator to the first excess parameter + const std::vector<std::string>::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 (std::vector<std::string>::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()); } /* @@ -291,104 +258,140 @@ bool CommandParser::ProcessCommand(LocalUser *user, std::string &cmd) ModResult MOD_RESULT; FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false, cmd)); 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; + 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)); 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(command_p, user); - FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, command_p, user, result,cmd)); - return do_more; + FOREACH_MOD(OnPostCommand, (handler, command_p, user, result, cmd)); } } 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, int index) +{ +} + +RouteDescriptor CommandBase::GetRouting(User* user, const std::vector<std::string>& 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(std::string &buffer,LocalUser *user) +{ + if (buffer.empty()) + return; - 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); + ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), buffer.c_str()); + ProcessCommand(user,buffer); } bool CommandParser::AddCommand(Command *f) @@ -406,88 +409,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 std::vector<std::string>& 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..c72a5dc73 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -21,103 +21,7 @@ */ -/* $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) -{ - 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++) - { - if ((InspIRCd::MatchCIDR(u->second->MakeHost(), mask, ascii_case_insensitive_map)) || - (InspIRCd::MatchCIDR(u->second->MakeHostIP(), mask, 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 G/K/E line mask of %s, which covers %.2f%% of the network!",user->nick.c_str(),mask.c_str(),percent); - return true; - } - 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) { @@ -127,7 +31,7 @@ CmdResult SplitCommand::Handle(const std::vector<std::string>& parms, User* 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 in command (uuid=%s)!", u->uuid.c_str()); return CMD_INVALID; } @@ -145,4 +49,3 @@ CmdResult SplitCommand::HandleServer(const std::vector<std::string>&, FakeUser*) { 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_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_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_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_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_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..86268a132 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) { @@ -197,21 +219,21 @@ struct Parser } else if (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") { - 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") { - 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"); @@ -223,9 +245,9 @@ struct Parser { std::string format = tag->getString("format"); if (format == "xml") - flags |= FLAG_USE_XML; + flags &= ~FLAG_USE_COMPAT; else if (format == "compat") - flags &= ~FLAG_USE_XML; + 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) +bool ParseStack::ParseFile(const std::string& path, int flags, const std::string& mandatory_tag, bool isexec) { - 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)"); - } - } + 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(fopen(name.c_str(), "r")); + FileWrapper file((isexec ? popen(path.c_str(), "r") : fopen(path.c_str(), "r")), isexec); if (!file) - throw CoreException("Could not read \"" + 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; } -bool ParseStack::ParseExec(const std::string& name, int flags, const std::string& mandatory_tag) -{ - 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)"); - } - } - - /* It's not already included, add it to the list of files we've loaded */ - - FileWrapper file(popen(name.c_str(), "r"), true); - if (!file) - throw CoreException("Could not open executable \"" + 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; -} - -#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,23 @@ 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, 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) +long ConfigTag::getInt(const std::string &key, long def, long min, long max) { std::string result; if(!readString(key, result)) @@ -446,18 +431,41 @@ long ConfigTag::getInt(const std::string &key, long def) switch (toupper(*res_tail)) { case 'K': - res= res* 1024; + res = res * 1024; break; case 'M': - res= res* 1024 * 1024; + res = res * 1024 * 1024; break; case 'G': - res= res* 1024 * 1024 * 1024; + res = res * 1024 * 1024 * 1024; break; } + + CheckRange(key, res, def, min, max); return res; } +void ConfigTag::CheckRange(const std::string& key, long& res, long def, long min, long max) +{ + if (res < min || res > max) + { + ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: <%s:%s> value of %ld is not between %ld and %ld; set to %ld.", + tag.c_str(), key.c_str(), res, min, max, def); + res = def; + } +} + +long ConfigTag::getDuration(const std::string& key, long def, long min, long max) +{ + std::string duration; + if (!readString(key, duration)) + return def; + + long ret = InspIRCd::Duration(duration); + CheckRange(key, ret, def, min, max); + return ret; +} + double ConfigTag::getFloat(const std::string &key, double def) { std::string result; @@ -477,7 +485,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 +495,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; diff --git a/src/configreader.cpp b/src/configreader.cpp index 301db14e8..d198a1d84 100644 --- a/src/configreader.cpp +++ b/src/configreader.cpp @@ -23,35 +23,56 @@ #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 + +ServerLimits::ServerLimits(ConfigTag* tag) + : NickMax(tag->getInt("maxnick", 30)) + , ChanMax(tag->getInt("maxchan", 64)) + , MaxModes(tag->getInt("maxmodes", 20)) + , IdentMax(tag->getInt("maxident", 10)) + , MaxQuit(tag->getInt("maxquit", 255)) + , MaxTopic(tag->getInt("maxtopic", 307)) + , MaxKick(tag->getInt("maxkick", 255)) + , MaxGecos(tag->getInt("maxgecos", 128)) + , MaxAway(tag->getInt("maxaway", 200)) + , MaxLine(tag->getInt("maxline", 512)) + , MaxHost(tag->getInt("maxhost", 64)) +{ +} + +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)) +{ +} + +static ConfigTag* CreateEmptyTag() +{ + ConfigItems* items; + return ConfigTag::create("empty", "<auto>", 0, items); +} ServerConfig::ServerConfig() - : NoSnoticeStack(false) + : EmptyTag(CreateEmptyTag()) + , Limits(EmptyTag) + , Paths(EmptyTag) + , NoSnoticeStack(false) { - WhoWasGroupSize = WhoWasMaxGroups = WhoWasMaxKeep = 0; - RawLog = NoUserDns = HideBans = HideSplits = UndernetMsgPrefix = false; - WildcardIPv6 = CycleHosts = InvBypassModes = true; - dns_timeout = 5; + RawLog = HideBans = HideSplits = false; + WildcardIPv6 = true; 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() @@ -59,60 +80,6 @@ ServerConfig::~ServerConfig() 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) -{ - 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) -{ - 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; -} - - -static void ValidIP(const std::string& ip, const std::string& key) -{ - 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) { int num_dots = 0; @@ -139,79 +106,20 @@ bool ServerConfig::ApplyDisabledCommands(const std::string& data) std::string thiscmd; /* Enable everything first */ - for (Commandtable::iterator x = ServerInstance->Parser->cmdlist.begin(); x != ServerInstance->Parser->cmdlist.end(); x++) + 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); - } + Command* handler = ServerInstance->Parser.GetHandler(thiscmd); + if (handler) + handler->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) - { - 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()) - { - 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",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",DEFAULT,"/etc/resolv.conf contains no viable nameserver entries! Defaulting to nameserver '127.0.0.1'!"); -#endif - server = "127.0.0.1"; -} - static void ReadXLine(ServerConfig* conf, const std::string& tag, const std::string& key, XLineFactory* make) { ConfigTagList tags = conf->ConfTags(tag); @@ -250,13 +158,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; + OperTypes[name] = ifo; ifo->name = name; ifo->type_block = tag; @@ -281,8 +187,8 @@ 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()); @@ -305,7 +211,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; } @@ -322,9 +228,9 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) 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; } @@ -404,8 +310,8 @@ 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)) { @@ -428,6 +334,15 @@ void ServerConfig::CrossCheckConnectBlocks(ServerConfig* current) me->maxchans = tag->getInt("maxchans", me->maxchans); me->maxconnwarn = tag->getBool("maxconnwarn", me->maxconnwarn); me->limit = tag->getInt("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,88 +360,81 @@ 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"); + ServerName = server->getString("name", "irc.example.com"); ValidHost(ServerName, "<server:name>"); - if (!sid.empty() && !ServerInstance->IsSID(sid)) + + 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"); - } - 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()); + 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"); + + } + SoftLimit = ConfValue("performance")->getInt("softlimit", (SocketEngine::GetMaxFds() > 0 ? SocketEngine::GetMaxFds() : LONG_MAX), 10); + CCOnConnect = ConfValue("performance")->getBool("clonesonconnect", true); 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); + 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); DisabledCommands = ConfValue("disabled")->getString("commands", ""); 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"); @@ -534,52 +442,29 @@ void ServerConfig::Fill() 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->getInt("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); + OperMaxChans = ConfValue("channels")->getInt("opers"); 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); + 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>"); + if (Network.find(' ') != std::string::npos) + throw CoreException(Network + " is not a valid network name. A network name must not contain spaces."); 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,26 +475,7 @@ 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); } ReadXLine(this, "badip", "ipmask", ServerInstance->XLines->GetFactory("Z")); @@ -617,30 +483,25 @@ void ServerConfig::Fill() ReadXLine(this, "badhost", "host", ServerInstance->XLines->GetFactory("K")); ReadXLine(this, "exception", "host", ServerInstance->XLines->GetFactory("E")); - memset(DisabledUModes, 0, sizeof(DisabledUModes)); + 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") @@ -675,12 +536,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 +548,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 +557,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 (int 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 +588,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 +598,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 +610,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 +625,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 +640,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); } @@ -783,15 +654,11 @@ void ServerConfig::Apply(ServerConfig* old, const std::string &useruid) ConfigTag *tag = (*it)->config; // Make sure our connection class allows motd colors if(!tag->getBool("allowmotdcolors")) - continue; + 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 +680,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 +696,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 +704,50 @@ 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())) + 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 +756,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 +766,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 +799,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 +811,23 @@ 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(); + ChanModeReference ban(NULL, "ban"); + static_cast<ListModeBase*>(*ban)->DoRehash(); Config->ApplyDisabledCommands(Config->DisabledCommands); 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) + i->second->ReadConfig(status); + + // 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..a1319ebc0 --- /dev/null +++ b/src/coremods/core_channel/cmd_invite.cpp @@ -0,0 +1,174 @@ +/* + * 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 (const std::vector<std::string>& parameters, User *user) +{ + 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) || (!u) || (u->registered != REG_ALL)) + { + user->WriteNumeric(Numerics::NoSuchNick(c ? parameters[0] : parameters[1])); + 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; + } + } + } + + if (IS_LOCAL(u)) + { + invapi.Create(IS_LOCAL(u), c, timeout); + u->WriteFrom(user,"INVITE %s :%s",u->nick.c_str(),c->name.c_str()); + } + + 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 (ServerInstance->Config->AnnounceInvites) + { + case ServerConfig::INVITE_ANNOUNCE_OPS: + { + prefix = '@'; + minrank = OP_VALUE; + break; + } + case ServerConfig::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 (ServerInstance->Config->AnnounceInvites != ServerConfig::INVITE_ANNOUNCE_NONE) + c->WriteAllExcept(user, true, prefix, excepts, "NOTICE %s :*** %s invited %s into the channel", c->name.c_str(), user->nick.c_str(), u->nick.c_str()); + } + 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 std::vector<std::string>& 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..06e203ead --- /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(const std::vector<std::string>& parameters, LocalUser *user) +{ + 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_NOSUCHCHANNEL, 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..7f8a8a329 --- /dev/null +++ b/src/coremods/core_channel/cmd_kick.cpp @@ -0,0 +1,128 @@ +/* + * 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 (const std::vector<std::string>& parameters, User *user) +{ + 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 ((!u) || (!c) || (u->registered != REG_ALL)) + { + user->WriteNumeric(Numerics::NoSuchNick(c ? parameters[1] : parameters[0])); + 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() > req) + req = mh->GetLevelRequired(); + } + + 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 std::vector<std::string>& 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..21fe43cca --- /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(const std::vector<std::string>& parameters, LocalUser* user) +{ + Channel* c; + + if (!parameters.size()) + { + 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::NoSuchNick(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..ec6ed9744 --- /dev/null +++ b/src/coremods/core_channel/cmd_topic.cpp @@ -0,0 +1,103 @@ +/* + * 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(const std::vector<std::string>& parameters, LocalUser* user) +{ + Channel* c = ServerInstance->FindChan(parameters[0]); + if (!c) + { + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + return CMD_FAILURE; + } + + if (parameters.size() == 1) + { + if ((c->IsModeSet(secretmode)) && (!c->HasUser(user))) + { + user->WriteNumeric(Numerics::NoSuchNick(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; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, MOD_RESULT, (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/coremods/core_channel/core_channel.cpp b/src/coremods/core_channel/core_channel.cpp new file mode 100644 index 000000000..3af809645 --- /dev/null +++ b/src/coremods/core_channel/core_channel.cpp @@ -0,0 +1,188 @@ +/* + * 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" + +class CoreModChannel : public Module, public CheckExemption::EventListener +{ + Invite::APIImpl invapi; + CommandInvite cmdinvite; + CommandJoin cmdjoin; + CommandKick cmdkick; + CommandNames cmdnames; + CommandTopic cmdtopic; + 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) + { + } + + 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); + } + + 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); + } + + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE + { + // 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)); + } + } + + 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->topicset) + 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); + } + + 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..19a984827 --- /dev/null +++ b/src/coremods/core_channel/core_channel.h @@ -0,0 +1,137 @@ +/* + * 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" +#include "modules/exemption.h" + +namespace Topic +{ + void ShowTopic(LocalUser* user, Channel* chan); +} + +namespace Invite +{ + class APIImpl; +} + +/** Handle /INVITE. + */ +class CommandInvite : public Command +{ + Invite::APIImpl& invapi; + + public: + /** 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(const std::vector<std::string>& parameters, User*user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +/** 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(const std::vector<std::string>& parameters, LocalUser* user); +}; + +/** 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(const std::vector<std::string>& parameters, LocalUser* user); +}; + +/** 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(const std::vector<std::string>& parameters, LocalUser* user); + + /** 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(const std::vector<std::string>& parameters, User *user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; 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..2a99ec2df --- /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(store); + } + + void free(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..1040bb036 --- /dev/null +++ b/src/coremods/core_dns.cpp @@ -0,0 +1,855 @@ +/* + * 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 +{ + static bool IsValidName(const std::string& name) + { + return (name.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == std::string::npos); + } + + 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 (!IsValidName(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.sa, this->myserver.sa_size()) != 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.sa.sa_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.sa.sa_family == AF_INET) + sourceaddr = "0.0.0.0"; + else if (myserver.sa.sa_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.sa.sa_family != myserver.sa.sa_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->getInt("sourceport", 0, 0, 65535); + + 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..d150b8551 --- /dev/null +++ b/src/coremods/core_hostname_lookup.cpp @@ -0,0 +1,236 @@ +/* + * 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) + { + 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.sa.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->sa.sa_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->host.assign(*hostname, 0, ServerInstance->Config->Limits.MaxHost); + bound_user->dhost = bound_user->host; + + /* Invalidate cache */ + bound_user->InvalidateCache(); + } + 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) + { + 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 OnUserInit(LocalUser *user) CXX11_OVERRIDE + { + if (!DNS || !user->MyClass->resolvehostnames) + { + user->WriteNotice("*** Skipping host resolution (disabled by server administrator)"); + 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..f79ebc036 --- /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 (const std::vector<std::string>& parameters, User *user) +{ + 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..a7a622f5f --- /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 (const std::vector<std::string>&, User *user) +{ + 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..3bf9db893 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 Shutter", " ", "\2Former Contributors\2:", " dmb Zaba skenmy GreenReaper", @@ -87,9 +65,10 @@ static const char* const lines[] = { " Stskeeps ThaPrince BuildSmart Thunderhacker", " Skip LeaChim Majic MacGyver", " Namegduf Ankit Phoenix Taros", + " jackmcbarn ChrisTX Shawn", " ", "\2Thanks To\2:", - " searchirc.com irc-junkie.org Brik fraggeln", + " Asmo Brik fraggeln", " ", " Best experienced with: \2An IRC client\2", NULL @@ -104,10 +83,8 @@ CmdResult CommandInfo::Handle (const std::vector<std::string>& parameters, User 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++]); + FOREACH_MOD(OnInfo, (user)); + 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..04f470bcd --- /dev/null +++ b/src/coremods/core_info/cmd_modules.cpp @@ -0,0 +1,89 @@ +/* + * 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 (const std::vector<std::string>& parameters, User *user) +{ + // 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("vcC"); + int pos = 0; + for (int mult = 2; mult <= VF_OPTCOMMON; mult *= 2, ++pos) + if (!(V.Flags & mult)) + flags[pos] = '-'; + +#ifdef INSPIRCD_STATIC + user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, INSPIRCD_VERSION, flags, V.description); +#else + std::string srcrev = m->ModuleDLLManager->GetVersion(); + user->WriteRemoteNumeric(RPL_MODLIST, m->ModuleSourceFile, srcrev.empty() ? "*" : srcrev, flags, V.description); +#endif + } + 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..cfb083eed --- /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 (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) && (!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..6755e5837 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 (const std::vector<std::string>& parameters, User *user) { - 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..9ec0108b1 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 (const std::vector<std::string>&, User *user) { - 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..bd519076d --- /dev/null +++ b/src/coremods/core_info/core_info.cpp @@ -0,0 +1,61 @@ +/* + * 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" + +RouteDescriptor ServerTargetCommand::GetRouting(User* user, const std::vector<std::string>& 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; + + public: + CoreModInfo() + : cmdadmin(this), cmdcommands(this), cmdinfo(this), cmdmodules(this), cmdmotd(this), cmdtime(this), cmdversion(this) + { + } + + 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"); + } + + 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..ecfeb4f36 --- /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 std::vector<std::string>& parameters); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; diff --git a/src/coremods/core_ison.cpp b/src/coremods/core_ison.cpp new file mode 100644 index 000000000..f1733ba88 --- /dev/null +++ b/src/coremods/core_ison.cpp @@ -0,0 +1,81 @@ +/* + * 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(const std::vector<std::string>& parameters, LocalUser* user); +}; + +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(const std::vector<std::string>& parameters, LocalUser* user) +{ + 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; +} + + +COMMAND_INIT(CommandIson) diff --git a/src/commands/cmd_list.cpp b/src/coremods/core_list.cpp index eb28fb89c..6a62d122f 100644 --- a/src/commands/cmd_list.cpp +++ b/src/coremods/core_list.cpp @@ -20,20 +20,26 @@ #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. +/** Handle /LIST. */ class CommandList : public Command { + ChanModeReference secretmode; + ChanModeReference privatemode; + public: /** Constructor for list. */ - CommandList ( Module* parent) : Command(parent,"LIST", 0, 0) { Penalty = 5; } + CommandList(Module* parent) + : Command(parent,"LIST", 0, 0) + , secretmode(creator, "secret") + , privatemode(creator, "private") + { + Penalty = 5; + } + /** 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. */ @@ -47,7 +53,7 @@ CmdResult CommandList::Handle (const std::vector<std::string>& parameters, User { int minusers = 0, maxusers = 0; - user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str()); + user->WriteNumeric(RPL_LISTSTART, "Channel", "Users Name"); if ((parameters.size() == 1) && (!parameters[0].empty())) { @@ -61,10 +67,16 @@ CmdResult CommandList::Handle (const std::vector<std::string>& parameters, User } } - for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); i++) + const bool has_privs = user->HasPrivPermission("channels/auspex"); + const bool match_name_topic = ((!parameters.empty()) && (!parameters[0].empty()) && (parameters[0][0] != '<') && (parameters[0][0] != '>')); + + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) { + Channel* const chan = i->second; + // attempt to match a glob pattern - long users = i->second->GetUserCounter(); + long users = chan->GetUserCounter(); bool too_few = (minusers && (users <= minusers)); bool too_many = (maxusers && (users >= maxusers)); @@ -72,31 +84,31 @@ CmdResult CommandList::Handle (const std::vector<std::string>& parameters, User if (too_many || too_few) continue; - if (parameters.size() && !parameters[0].empty() && (parameters[0][0] != '<' && parameters[0][0] != '>')) + if (match_name_topic) { - if (!InspIRCd::Match(i->second->name, parameters[0]) && !InspIRCd::Match(i->second->topic, parameters[0])) + if (!InspIRCd::Match(chan->name, parameters[0]) && !InspIRCd::Match(chan->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")); + 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 || !i->second->IsModeSet('s')) + if ((n) || (!chan->IsModeSet(secretmode))) { - if (!n && i->second->IsModeSet('p')) + if ((!n) && (chan->IsModeSet(privatemode))) { - /* Channel is +p and user is outside/not privileged */ - user->WriteNumeric(322, "%s * %ld :",user->nick.c_str(), users); + // 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(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(RPL_LIST, chan->name, users, InspIRCd::Format("[+%s] %s", chan->ChanModes(n), chan->topic.c_str())); } } } - user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str()); + user->WriteNumeric(RPL_LISTEND, "End of channel list."); return CMD_SUCCESS; } diff --git a/src/coremods/core_loadmodule.cpp b/src/coremods/core_loadmodule.cpp new file mode 100644 index 000000000..fde149587 --- /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(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(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(const std::vector<std::string>& parameters, User* user); +}; + +CmdResult CommandUnloadmodule::Handle(const std::vector<std::string>& parameters, User* user) +{ + 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..7f1903dd0 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,8 +57,7 @@ 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. */ @@ -73,11 +69,11 @@ class CommandLusers : public Command CmdResult CommandLusers::Handle (const std::vector<std::string>&, User *user) { 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) { 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/commands/cmd_die.cpp b/src/coremods/core_oper/cmd_die.cpp index 1d6640213..5fe643520 100644 --- a/src/commands/cmd_die.cpp +++ b/src/coremods/core_oper/cmd_die.cpp @@ -19,50 +19,63 @@ #include "inspircd.h" +#include "exitcodes.h" +#include "core_oper.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 +CommandDie::CommandDie(Module* parent) + : Command(parent, "DIE", 1) { - 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); -}; + flags_needed = 'o'; + syntax = "<server>"; +} -#include "exitcodes.h" +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) +{ + const std::string unregline = "ERROR :" + message; + 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->Write(unregline); + } + } +} /** 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)) + if (DieRestart::CheckPass(user, parameters[0], "diepass")) { { std::string diebuf = "*** DIE command from " + user->GetFullHost() + ". Terminating."; - ServerInstance->Logs->Log("COMMAND",SPARSE, diebuf); - ServerInstance->SendError(diebuf); + ServerInstance->Logs->Log(MODNAME, LOG_SPARSE, diebuf); + DieRestart::SendError(diebuf); } + QuitAll(); ServerInstance->Exit(EXIT_STATUS_DIE); } else { - ServerInstance->Logs->Log("COMMAND",SPARSE, "Failed /DIE command from %s", user->GetFullRealHost().c_str()); + 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; } - -COMMAND_INIT(CommandDie) diff --git a/src/coremods/core_oper/cmd_kill.cpp b/src/coremods/core_oper/cmd_kill.cpp new file mode 100644 index 000000000..8e8c4fadc --- /dev/null +++ b/src/coremods/core_oper/cmd_kill.cpp @@ -0,0 +1,145 @@ +/* + * 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) +{ + flags_needed = 'o'; + syntax = "<nickname> <reason>"; + TRANSLATE2(TR_CUSTOM, TR_CUSTOM); +} + + +/** Handle /KILL + */ +CmdResult CommandKill::Handle (const std::vector<std::string>& parameters, User *user) +{ + /* 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(user) || IS_LOCAL(target)) + ServerInstance->Logs->Log("KILL", LOG_DEFAULT, "%s KILL: %s :%s!%s!%s (%s)", + IS_LOCAL(user) && IS_LOCAL(target) ? "LOCAL" : "REMOTE", + target->nick.c_str(), + ServerInstance->Config->ServerName.c_str(), user->dhost.c_str(), user->nick.c_str(), + parameters[1].c_str()); + + if (IS_LOCAL(target)) + { + target->Write(":%s KILL %s :%s", + ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(), + target->nick.c_str(), + parameters[1].c_str()); + + 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 std::vector<std::string>& 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, 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..9c06583a8 --- /dev/null +++ b/src/coremods/core_oper/cmd_oper.cpp @@ -0,0 +1,72 @@ +/* + * 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(const std::vector<std::string>& parameters, LocalUser *user) +{ + bool match_login = false; + bool match_pass = false; + bool match_hosts = false; + + const std::string userHost = user->ident + "@" + user->host; + 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()); + ServerInstance->Logs->Log("OPER", LOG_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; +} diff --git a/src/commands/cmd_rehash.cpp b/src/coremods/core_oper/cmd_rehash.cpp index abf0b7876..5ce38eb2c 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) { 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 bdbcfed35..f76fd098d 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) { - 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 execv() 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..338a369f5 --- /dev/null +++ b/src/coremods/core_oper/core_oper.h @@ -0,0 +1,129 @@ +/* + * 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(const std::vector<std::string>& parameters, User* user); +}; + +/** Handle /KILL. + */ +class CommandKill : public Command +{ + std::string lastuuid; + std::string killreason; + + public: + /** Constructor for kill. + */ + CommandKill(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(const std::vector<std::string>& parameters, User* user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + + void EncodeParameter(std::string& param, int index); +}; + +/** Handle /OPER. + */ +class CommandOper : public SplitCommand +{ + public: + /** Constructor for oper. + */ + CommandOper(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(const std::vector<std::string>& parameters, LocalUser* user); +}; + +/** Handle /REHASH. + */ +class CommandRehash : public Command +{ + public: + /** Constructor for rehash. + */ + CommandRehash(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(const std::vector<std::string>& parameters, User *user); +}; + +/** Handle /RESTART + */ +class CommandRestart : public Command +{ + public: + /** Constructor for restart. + */ + CommandRestart(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(const std::vector<std::string>& parameters, User* user); +}; diff --git a/src/coremods/core_privmsg.cpp b/src/coremods/core_privmsg.cpp new file mode 100644 index 000000000..449097a75 --- /dev/null +++ b/src/coremods/core_privmsg.cpp @@ -0,0 +1,289 @@ +/* + * 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" + +namespace +{ + const char* MessageTypeString[] = { "PRIVMSG", "NOTICE" }; +} + +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) + */ + static void SendAll(User* user, const std::string& msg, MessageType mt); + + public: + MessageCommandBase(Module* parent, MessageType mt) + : Command(parent, MessageTypeString[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(const std::vector<std::string>& parameters, User* user, MessageType mt); + + 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]); + } +}; + +void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt) +{ + const std::string message = ":" + user->GetFullHost() + " " + MessageTypeString[mt] + " $* :" + msg; + 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)->Write(message); + } +} + +CmdResult MessageCommandBase::HandleMessage(const std::vector<std::string>& parameters, User* user, MessageType mt) +{ + User *dest; + Channel *chan; + CUList except_list; + + LocalUser* localuser = IS_LOCAL(user); + if (localuser) + localuser->idle_lastmsg = ServerInstance->Time(); + + if (CommandParser::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, mt)); + 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(OnText, (user, (void*)parameters[0].c_str(), TYPE_SERVER, text, 0, except_list)); + if (InspIRCd::Match(ServerInstance->Config->ServerName, servermask, NULL)) + { + SendAll(user, text, mt); + } + FOREACH_MOD(OnUserMessage, (user, (void*)parameters[0].c_str(), TYPE_SERVER, text, 0, except_list, mt)); + 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 (localuser && 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) + { + if (chan->IsBanned(user)) + { + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're banned)"); + 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, mt)); + 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(ERR_NOTEXTTOSEND, "No text to send"); + return CMD_FAILURE; + } + + FOREACH_MOD(OnText, (user,chan,TYPE_CHANNEL,text,status,except_list)); + + if (status) + { + chan->WriteAllExcept(user, false, status, except_list, "%s %c%s :%s", MessageTypeString[mt], status, chan->name.c_str(), text); + } + else + { + chan->WriteAllExcept(user, false, status, except_list, "%s %s :%s", MessageTypeString[mt], chan->name.c_str(), text); + } + + FOREACH_MOD(OnUserMessage, (user,chan, TYPE_CHANNEL, text, status, except_list, mt)); + } + else + { + /* no such nick/channel */ + user->WriteNumeric(Numerics::NoSuchNick(target)); + return CMD_FAILURE; + } + return CMD_SUCCESS; + } + + const char* destnick = parameters[0].c_str(); + + if (localuser) + { + 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); + } + + ModResult MOD_RESULT; + + std::string temp = parameters[1]; + FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, dest, TYPE_USER, temp, 0, except_list, mt)); + if (MOD_RESULT == MOD_RES_DENY) + return CMD_FAILURE; + + const char* text = temp.c_str(); + + FOREACH_MOD(OnText, (user, dest, TYPE_USER, text, 0, except_list)); + + if (IS_LOCAL(dest)) + { + // direct write, same server + dest->WriteFrom(user, "%s %s :%s", MessageTypeString[mt], dest->nick.c_str(), text); + } + + FOREACH_MOD(OnUserMessage, (user, dest, TYPE_USER, text, 0, except_list, mt)); + } + 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(const std::vector<std::string>& parameters, User* user) + { + return HandleMessage(parameters, user, MT); + } +}; + +class ModuleCoreMessage : public Module +{ + CommandMessage<MSG_PRIVMSG> CommandPrivmsg; + CommandMessage<MSG_NOTICE> CommandNotice; + + public: + ModuleCoreMessage() + : CommandPrivmsg(this), CommandNotice(this) + { + } + + 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..ffd4f9bf5 --- /dev/null +++ b/src/coremods/core_reloadmodule.cpp @@ -0,0 +1,635 @@ +/* + * 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; + +class CommandReloadmodule : public Command +{ + Events::ModuleEventProvider evprov; + public: + /** Constructor for reloadmodule. + */ + CommandReloadmodule(Module* parent) + : Command(parent, "RELOADMODULE", 1) + , evprov(parent, "event/reloadmodule") + { + reloadevprov = &evprov; + 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(const std::vector<std::string>& parameters, User *user); +}; + +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; + }; + + ProviderInfo(ModeHandler* mode) + : itemname(mode->name) + , mh(mode) + { + } + + ProviderInfo(ExtensionItem* ei) + : itemname(ei->name) + , extitem(ei) + { + } + }; + + 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 + typedef OwnedModesExts UserData; + + /** 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 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); + + 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); + + 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 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); + + // 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()) + { + userdatalist.push_back(UserData(user->uuid)); + userdatalist.back().swap(currdata); + } + } +} + +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::Restore(Module* newmod) +{ + this->mod = newmod; + + // Find the new extension items + LinkExtensions(); + LinkModes(MODETYPE_USER); + LinkModes(MODETYPE_CHANNEL); + + // 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); + } +} + +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; + } + + 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 HandlerBase0<void> +{ + 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() + { + 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 (const std::vector<std::string>& parameters, User *user) +{ + 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; + } +} + +COMMAND_INIT(CommandReloadmodule) diff --git a/src/coremods/core_stats.cpp b/src/coremods/core_stats.cpp new file mode 100644 index 000000000..9d1fdf3fe --- /dev/null +++ b/src/coremods/core_stats.cpp @@ -0,0 +1,389 @@ +/* + * 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" + +#ifdef _WIN32 +#include <psapi.h> +#pragma comment(lib, "psapi.lib") // For GetProcessMemoryInfo() +#endif + +/** Handle /STATS. + */ +class CommandStats : public Command +{ + void DoStats(Stats::Context& stats); + 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 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) && (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->dhost : 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->host.c_str()); + stats.AddRow(481, (std::string("Permission Denied - STATS ") + statschar + " requires the servers/auspex priv.")); + return; + } + + ModResult MOD_RESULT; + FIRST_MOD_RESULT(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->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 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->dhost + ") 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 */ + { + 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)); + + 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': + { + ConfigTagList tags = ServerInstance->Config->ConfTags("oper"); + for(ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + 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->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) && (!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; +} + +COMMAND_INIT(CommandStats) diff --git a/src/coremods/core_stub.cpp b/src/coremods/core_stub.cpp new file mode 100644 index 000000000..71b0ddaea --- /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(const std::vector<std::string>& parameters, User* user) + { + /* + * 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(const std::vector<std::string>& parameters, User* user) + { + 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(const std::vector<std::string>& parameters, User* user) + { + 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(const std::vector<std::string>& parameters, User* user) + { + 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/commands/cmd_away.cpp b/src/coremods/core_user/cmd_away.cpp index fa3f7fae9..fb720a5a7 100644 --- a/src/commands/cmd_away.cpp +++ b/src/coremods/core_user/cmd_away.cpp @@ -19,26 +19,13 @@ #include "inspircd.h" +#include "core_user.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 +CommandAway::CommandAway(Module* parent) + : Command(parent, "AWAY", 0, 0) { - 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); -}; + syntax = "[<message>]"; +} /** Handle /AWAY */ @@ -56,7 +43,7 @@ CmdResult CommandAway::Handle (const std::vector<std::string>& parameters, User 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()); + user->WriteNumeric(RPL_NOWAWAY, "You have been marked as being away"); } else { @@ -66,10 +53,13 @@ CmdResult CommandAway::Handle (const std::vector<std::string>& parameters, User return CMD_FAILURE; user->awaymsg.clear(); - user->WriteNumeric(RPL_UNAWAY, "%s :You are no longer marked as being away",user->nick.c_str()); + user->WriteNumeric(RPL_UNAWAY, "You are no longer marked as being away"); } return CMD_SUCCESS; } -COMMAND_INIT(CommandAway) +RouteDescriptor CommandAway::GetRouting(User* user, const std::vector<std::string>& 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..2b2652606 --- /dev/null +++ b/src/coremods/core_user/cmd_mode.cpp @@ -0,0 +1,174 @@ +/* + * 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(const std::vector<std::string>& parameters, User* user) +{ + 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)) + { + 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.GetLastParse().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 std::vector<std::string>& 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..cedafbbf4 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(const std::vector<std::string>& parameters, LocalUser* user) { 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,42 @@ 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) { - 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)) + { + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're banned)"); 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..4da2787d9 --- /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 (const std::vector<std::string>& parameters, User *user) +{ + 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::NoSuchNick(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 std::vector<std::string>& 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..f9a4e1f70 --- /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 (const std::vector<std::string>& parameters, User *user) +{ + 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 std::vector<std::string>& parameters) +{ + return (IS_LOCAL(user) ? ROUTE_LOCALONLY : ROUTE_BROADCAST); +} diff --git a/src/commands/cmd_user.cpp b/src/coremods/core_user/cmd_user.cpp index 09e1e3319..37ac116df 100644 --- a/src/commands/cmd_user.cpp +++ b/src/coremods/core_user/cmd_user.cpp @@ -19,39 +19,28 @@ #include "inspircd.h" +#include "core_user.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 +CommandUser::CommandUser(Module* parent) + : SplitCommand(parent, "USER", 4, 4) { - 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); -}; + works_before_reg = true; + Penalty = 0; + syntax = "<username> <localhost> <remotehost> <GECOS>"; +} 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())) + if (!ServerInstance->IsIdent(parameters[0])) { /* * 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()); + user->WriteNumeric(ERR_NEEDMOREPARAMS, name, "Your username is not valid"); return CMD_FAILURE; } else @@ -61,31 +50,33 @@ CmdResult CommandUser::HandleLocal(const std::vector<std::string>& parameters, L * ~ character, and +1 for null termination, therefore we can safely use up to * IDENTMAX here. */ - user->ChangeIdent(parameters[0].c_str()); + user->ChangeIdent(parameters[0]); user->fullname.assign(parameters[3].empty() ? "No info" : parameters[3], 0, ServerInstance->Config->Limits.MaxGecos); user->registered = (user->registered | REG_USER); } } else { + user->WriteNumeric(ERR_ALREADYREGISTERED, "You may not reregister"); 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 */ + 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; - - /* 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/coremods/core_user/core_user.cpp b/src/coremods/core_user/core_user.cpp new file mode 100644 index 000000000..a2ffc009e --- /dev/null +++ b/src/coremods/core_user/core_user.cpp @@ -0,0 +1,170 @@ +/* + * 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(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, "You may not reregister"); + return CMD_FAILURE; + } + user->password = parameters[0]; + + return CMD_SUCCESS; + } +}; + +/** Handle /PING. + */ +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 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) + { + user->WriteServ("PONG %s :%s", ServerInstance->Config->ServerName.c_str(), parameters[0].c_str()); + 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(const std::vector<std::string>& parameters, 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; + } +}; + +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; + + public: + CoreModUser() + : cmdaway(this), cmdmode(this), cmdnick(this), cmdpart(this), cmdpass(this), cmdping(this) + , cmdpong(this), cmdquit(this), cmduser(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..72ba67869 --- /dev/null +++ b/src/coremods/core_user/core_user.h @@ -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/>. + */ + + +#pragma once + +#include "inspircd.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 +{ + 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(const std::vector<std::string>& parameters, User *user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +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(const std::vector<std::string>& parameters, User* user); + + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +/** 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(const std::vector<std::string>& parameters, LocalUser* user); +}; + +/** 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(const std::vector<std::string>& parameters, User *user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +/** 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(const std::vector<std::string>& parameters, User*user); + + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +/** 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(const std::vector<std::string>& parameters, LocalUser *user); + + /** 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); +}; diff --git a/src/commands/cmd_userhost.cpp b/src/coremods/core_userhost.cpp index 7e1efd637..3100995a8 100644 --- a/src/commands/cmd_userhost.cpp +++ b/src/coremods/core_userhost.cpp @@ -20,22 +20,23 @@ #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. */ @@ -44,52 +45,39 @@ class CommandUserhost : public Command CmdResult CommandUserhost::Handle (const std::vector<std::string>& parameters, User *user) { - 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 == user) || (has_privs)) ? u->host : u->dhost); + retbuf += ' '; } } - user->WriteServ(retbuf); + user->WriteNumeric(RPL_USERHOST, retbuf); return CMD_SUCCESS; } diff --git a/src/commands/cmd_wallops.cpp b/src/coremods/core_wallops.cpp index 198997a95..0210df8ee 100644 --- a/src/commands/cmd_wallops.cpp +++ b/src/coremods/core_wallops.cpp @@ -20,24 +20,34 @@ #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. +/** Handle /WALLOPS. */ class CommandWallops : public Command { + SimpleUserModeHandler wallopsmode; + public: /** Constructor for wallops. */ - CommandWallops ( Module* parent) : Command(parent,"WALLOPS",1,1) { flags_needed = 'o'; syntax = "<any-text>"; } + CommandWallops(Module* parent) + : Command(parent, "WALLOPS", 1, 1) + , wallopsmode(parent, "wallops", 'w') + { + 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 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); + + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) + { + return ROUTE_BROADCAST; + } }; CmdResult CommandWallops::Handle (const std::vector<std::string>& parameters, User *user) @@ -45,14 +55,14 @@ CmdResult CommandWallops::Handle (const std::vector<std::string>& parameters, Us 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++) + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { User* t = *i; - if (t->IsModeSet('w')) - user->WriteTo(t,wallop); + if (t->IsModeSet(wallopsmode)) + t->WriteFrom(user, wallop); } - FOREACH_MOD(I_OnWallops,OnWallops(user,parameters[0])); return CMD_SUCCESS; } diff --git a/src/commands/cmd_who.cpp b/src/coremods/core_who.cpp index 8438f8cdd..677a1eb6d 100644 --- a/src/commands/cmd_who.cpp +++ b/src/coremods/core_who.cpp @@ -20,10 +20,7 @@ #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. +/** Handle /WHO. */ class CommandWho : public Command { @@ -39,17 +36,41 @@ class CommandWho : public Command bool opt_local; bool opt_far; bool opt_time; + ChanModeReference secretmode; + ChanModeReference privatemode; + UserModeReference hidechansmode; + UserModeReference invisiblemode; + + Membership* get_first_visible_channel(User* source, User* u) + { + for (User::ChanList::iterator i = u->chans.begin(); i != u->chans.end(); ++i) + { + Membership* memb = *i; + + /* XXX move the +I check into m_hidechans */ + bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || u->IsModeSet(hidechansmode); + if (source == u || !has_modes || memb->chan->HasUser(source)) + return memb; + } + return NULL; + } public: /** Constructor for who. */ - CommandWho ( Module* parent) : Command(parent,"WHO", 1) { + CommandWho(Module* parent) + : Command(parent, "WHO", 1) + , secretmode(parent, "secret") + , privatemode(parent, "private") + , hidechansmode(parent, "hidechans") + , invisiblemode(parent, "invisible") + { syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [ohurmMiaplf]"; } - void SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string &initial, Channel* ch, User* u, std::vector<std::string> &whoresults); + + void SendWhoLine(User* user, const std::vector<std::string>& parms, Membership* memb, User* u, std::vector<Numeric::Numeric>& whoresults); /** 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. */ @@ -57,21 +78,6 @@ class CommandWho : public Command 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; @@ -140,7 +146,7 @@ bool CommandWho::whomatch(User* cuser, User* user, const char* matchtext) match = InspIRCd::Match(user->awaymsg, matchtext); else if (opt_time) { - long seconds = ServerInstance->Duration(matchtext); + long seconds = InspIRCd::Duration(matchtext); // Okay, so time matching, we want all users connected `seconds' ago if (user->signon >= ServerInstance->Time() - seconds) @@ -162,7 +168,7 @@ bool CommandWho::whomatch(User* cuser, User* user, const char* 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); + match = InspIRCd::Match(user->server->GetName(), matchtext); return match; } @@ -170,9 +176,6 @@ bool CommandWho::whomatch(User* cuser, User* user, const char* matchtext) 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 */ @@ -182,50 +185,58 @@ bool CommandWho::CanView(Channel* chan, User* user) 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')) + else if (!chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode)) 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) +void CommandWho::SendWhoLine(User* user, const std::vector<std::string>& parms, Membership* memb, User* u, std::vector<Numeric::Numeric>& whoresults) { - if (!ch) - ch = get_first_visible_channel(user, u); + if (!memb) + memb = get_first_visible_channel(user, u); - std::string wholine = initial + (ch ? ch->name : "*") + " " + u->ident + " " + - (opt_showrealhost ? u->host : u->dhost) + " "; + Numeric::Numeric wholine(RPL_WHOREPLY); + wholine.push(memb ? memb->chan->name : "*").push(u->ident); + wholine.push(opt_showrealhost ? u->host : u->dhost); if (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex")) - wholine.append(ServerInstance->Config->HideWhoisServer); + wholine.push(ServerInstance->Config->HideWhoisServer); else - wholine.append(u->server); - - wholine.append(" " + u->nick + " "); + wholine.push(u->server->GetName()); + wholine.push(u->nick); + + std::string param; /* away? */ - if (IS_AWAY(u)) + if (u->IsAway()) { - wholine.append("G"); + param.push_back('G'); } else { - wholine.append("H"); + param.push_back('H'); } /* oper? */ - if (IS_OPER(u)) + if (u->IsOper()) { - wholine.push_back('*'); + param.push_back('*'); } - if (ch) - wholine.append(ch->GetPrefixChar(u)); - - wholine.append(" :0 " + u->fullname); + if (memb) + { + char prefix = memb->GetPrefixChar(); + if (prefix) + param.push_back(prefix); + } - FOREACH_MOD(I_OnSendWhoLine, OnSendWhoLine(user, parms, u, wholine)); + wholine.push(param); + wholine.push("0 "); + wholine.GetParams().back().append(u->fullname); - if (!wholine.empty()) + ModResult res; + FIRST_MOD_RESULT(OnSendWhoLine, res, (user, parms, u, memb, wholine)); + if (res != MOD_RES_DENY) whoresults.push_back(wholine); } @@ -251,33 +262,16 @@ CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User * 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; + std::vector<Numeric::Numeric> whoresults; /* 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); + std::string matchtext = ((parameters[0] == "0") ? "*" : parameters[0]); - for (const char* check = matchtext; *check; check++) - { - if (*check == '*' || *check == '?' || *check == '.') - { - usingwildcards = true; - break; - } - } + // WHO flags count as a wildcard + bool usingwildcards = ((parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos)); 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) @@ -327,7 +321,7 @@ CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User * /* who on a channel? */ - ch = ServerInstance->FindChan(matchtext); + Channel* ch = ServerInstance->FindChan(matchtext); if (ch) { @@ -336,23 +330,22 @@ CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User * bool inside = ch->HasUser(user); /* who on a channel. */ - const UserMembList *cu = ch->GetUsers(); - - for (UserMembCIter i = cu->begin(); i != cu->end(); i++) + const Channel::MemberMap& cu = ch->GetUsers(); + for (Channel::MemberMap::const_iterator i = cu.begin(); i != cu.end(); ++i) { /* None of this applies if we WHO ourselves */ if (user != i->first) { /* opers only, please */ - if (opt_viewopersonly && !IS_OPER(i->first)) + if (opt_viewopersonly && !i->first->IsOper()) continue; /* If we're not inside the channel, hide +i users */ - if (i->first->IsModeSet('i') && !inside && !user->HasPrivPermission("users/auspex")) + if (i->first->IsModeSet(invisiblemode) && !inside && !user->HasPrivPermission("users/auspex")) continue; } - SendWhoLine(user, parameters, initial, ch, i->first, whoresults); + SendWhoLine(user, parameters, i->second, i->first, whoresults); } } } @@ -362,43 +355,45 @@ CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User * if (opt_viewopersonly) { /* Showing only opers */ - for (std::list<User*>::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 (whomatch(user, oper, matchtext)) + if (whomatch(user, oper, matchtext.c_str())) { if (!user->SharesChannelWith(oper)) { - if (usingwildcards && (oper->IsModeSet('i')) && (!user->HasPrivPermission("users/auspex"))) + if (usingwildcards && (oper->IsModeSet(invisiblemode)) && (!user->HasPrivPermission("users/auspex"))) continue; } - SendWhoLine(user, parameters, initial, NULL, oper, whoresults); + SendWhoLine(user, parameters, NULL, oper, whoresults); } } } else { - for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end(); i++) + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ++i) { - if (whomatch(user, i->second, matchtext)) + if (whomatch(user, i->second, matchtext.c_str())) { if (!user->SharesChannelWith(i->second)) { - if (usingwildcards && (i->second->IsModeSet('i')) && (!user->HasPrivPermission("users/auspex"))) + if (usingwildcards && (i->second->IsModeSet(invisiblemode)) && (!user->HasPrivPermission("users/auspex"))) continue; } - SendWhoLine(user, parameters, initial, NULL, i->second, whoresults); + SendWhoLine(user, parameters, 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() : "*"); + for (std::vector<Numeric::Numeric>::const_iterator n = whoresults.begin(); n != whoresults.end(); ++n) + user->WriteNumeric(*n); + user->WriteNumeric(RPL_ENDOFWHO, (*parameters[0].c_str() ? parameters[0] : "*"), "End of /WHO list."); // Penalize the user a bit for large queries // (add one unit of penalty per 200 results) diff --git a/src/coremods/core_whois.cpp b/src/coremods/core_whois.cpp new file mode 100644 index 000000000..81e62c8e5 --- /dev/null +++ b/src/coremods/core_whois.cpp @@ -0,0 +1,304 @@ +/* + * 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" + +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, unsigned long signon, unsigned long idle); + void SendChanList(WhoisContextImpl& whois); + + public: + /** 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(const std::vector<std::string>& parameters, LocalUser* user); + CmdResult HandleRemote(const std::vector<std::string>& parameters, RemoteUser* target); +}; + +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), 319, false, whois.GetSource()->nick.size() + whois.GetTarget()->nick.size() + 1) + { + GetNumeric().push(whois.GetTarget()->nick).push(std::string()); + } +}; + +class WhoisChanList +{ + const ServerConfig::OperSpyWhoisState spywhois; + WhoisChanListNumericBuilder num; + WhoisChanListNumericBuilder spynum; + 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) + : spywhois(whois.GetSource()->HasPrivPermission("users/auspex") ? ServerInstance->Config->OperSpyWhois : ServerConfig::SPYWHOIS_NONE) + , num(whois) + , spynum(whois) + { + } + + void AddVisible(Membership* memb) + { + AddMember(memb, num); + } + + void AddHidden(Membership* memb) + { + if (spywhois == ServerConfig::SPYWHOIS_NONE) + return; + AddMember(memb, (spywhois == ServerConfig::SPYWHOIS_SPLITMSG ? spynum : num)); + } + + void Flush(WhoisContextImpl& whois) + { + num.Flush(); + if (!spynum.IsEmpty()) + whois.SendLine(336, "is on private/secret channels:"); + spynum.Flush(); + } +}; + +void CommandWhois::SendChanList(WhoisContextImpl& whois) +{ + WhoisChanList chanlist(whois); + + User* const target = whois.GetTarget(); + for (User::ChanList::iterator i = target->chans.begin(); i != target->chans.end(); ++i) + { + Membership* memb = *i; + Channel* c = memb->chan; + /* 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 ((whois.IsSelfWhois()) || ((!c->IsModeSet(privatemode)) && (!c->IsModeSet(secretmode))) || (c->HasUser(whois.GetSource()))) + chanlist.AddVisible(memb); + else + chanlist.AddHidden(memb); + } + + chanlist.Flush(whois); +} + +void CommandWhois::DoWhois(LocalUser* user, User* dest, unsigned long signon, unsigned long idle) +{ + WhoisContextImpl whois(user, dest, lineevprov); + + whois.SendLine(311, dest->ident, dest->dhost, '*', dest->fullname); + if (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex")) + { + whois.SendLine(378, InspIRCd::Format("is connecting from %s@%s %s", dest->ident.c_str(), dest->host.c_str(), dest->GetIPString().c_str())); + } + + SendChanList(whois); + + if (!whois.IsSelfWhois() && !ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex")) + { + whois.SendLine(312, ServerInstance->Config->HideWhoisServer, ServerInstance->Config->Network); + } + else + { + whois.SendLine(312, dest->server->GetName(), dest->server->GetDesc()); + } + + if (dest->IsAway()) + { + whois.SendLine(301, dest->awaymsg); + } + + if (dest->IsOper()) + { + if (ServerInstance->Config->GenericOper) + whois.SendLine(313, "is an IRC operator"); + else + whois.SendLine(313, 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(379, InspIRCd::Format("is using modes %s %s", dest->GetModeLetters().c_str(), snomaskmode->GetUserParameter(dest).c_str())); + } + else + { + whois.SendLine(379, 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 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)) + { + whois.SendLine(317, idle, signon, "seconds idle, signon time"); + } + + whois.SendLine(318, "End of /WHOIS list."); +} + +CmdResult CommandWhois::HandleRemote(const std::vector<std::string>& parameters, RemoteUser* target) +{ + 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 = ConvToInt(parameters.back()); + DoWhois(localuser, target, target->signon, idle); + + return CMD_SUCCESS; +} + +CmdResult CommandWhois::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) +{ + User *dest; + int userindex = 0; + unsigned long idle = 0, 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 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 + */ + LocalUser* localuser = IS_LOCAL(dest); + if (localuser && (ServerInstance->Config->HideWhoisServer.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; +} + +COMMAND_INIT(CommandWhois) diff --git a/src/coremods/core_whowas.cpp b/src/coremods/core_whowas.cpp new file mode 100644 index 000000000..0bb47b273 --- /dev/null +++ b/src/coremods/core_whowas.cpp @@ -0,0 +1,297 @@ +/* + * 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" + +CommandWhowas::CommandWhowas( Module* parent) + : Command(parent, "WHOWAS", 1) +{ + syntax = "<nick>{,<nick>}"; + Penalty = 2; +} + +CmdResult CommandWhowas::Handle (const std::vector<std::string>& parameters, User* user) +{ + /* 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->gecos); + + 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->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex")); + user->WriteNumeric(RPL_WHOISSERVER, parameters[0], (hide_server ? ServerInstance->Config->HideWhoisServer : 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->host) + , dhost(user->dhost) + , ident(user->ident) + , server(user->server->GetName()) + , gecos(user->fullname) + , 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 +{ + CommandWhowas cmd; + + public: + ModuleWhoWas() : 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->getInt("groupsize", 10, 0, 10000); + unsigned int NewMaxGroups = tag->getInt("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..26b49894b 100644 --- a/src/commands/cmd_eline.cpp +++ b/src/coremods/core_xline/cmd_eline.cpp @@ -20,26 +20,14 @@ #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 */ @@ -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..49932ba9d 100644 --- a/src/commands/cmd_gline.cpp +++ b/src/coremods/core_xline/cmd_gline.cpp @@ -20,27 +20,14 @@ #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 */ @@ -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..db8862d37 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) { - 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..6dc0da9ba 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) { 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..af9d54a5b 100644 --- a/src/commands/cmd_zline.cpp +++ b/src/coremods/core_xline/cmd_zline.cpp @@ -21,25 +21,14 @@ #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) { @@ -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..d6c804748 --- /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); + + 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..5b34e7a4d --- /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) + { + 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; + +/** 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(const std::vector<std::string>& parameters, User* user); +}; 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 25178cfa1..f138b04d1 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..aa06759a4 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,88 +47,107 @@ * ******************************************************/ -/** 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 */ + 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 }; -/** Case sensitive map. - * Can technically also be used for ASCII case sensitive comparisons, as [ != {, etc. + + +/** + * 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_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 +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 }; -/* 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) +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(); + for (; *n1 && *n2; n1++, n2++) + if (national_case_insensitive_map[*n1] != national_case_insensitive_map[*n2]) + return false; + return (national_case_insensitive_map[*n1] == national_case_insensitive_map[*n2]); +} + +bool irc::insensitive_swo::operator()(const std::string& a, const std::string& b) const { - if (n) + 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); + + for (std::string::size_type i = 0; i < maxsize; i++) { - for (char* t = n; *t; t++) - *t = national_case_insensitive_map[(unsigned char)*t]; + 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; } + return (asize < bsize); } -#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 - +size_t irc::insensitive::operator()(const std::string &s) const { /* XXX: NO DATA COPIES! :) * The hash function here is practically @@ -146,25 +161,6 @@ void nspace::strlower(char *n) 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; -} - -bool irc::StrHashComp::operator()(const std::string& s1, const std::string& s2) const -{ - const unsigned char* n1 = (const unsigned char*)s1.c_str(); - const unsigned char* n2 = (const unsigned char*)s2.c_str(); - for (; *n1 && *n2; n1++, n2++) - if (national_case_insensitive_map[*n1] != national_case_insensitive_map[*n2]) - return false; - return (national_case_insensitive_map[*n1] == national_case_insensitive_map[*n2]); -} - /****************************************************** * * This is the implementation of our special irc::string @@ -217,68 +213,30 @@ const char* irc::irc_char_traits::find(const char* s1, int n, char c) 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(); -} - -irc::tokenstream::~tokenstream() +irc::tokenstream::tokenstream(const std::string &source) : spacesepstream(source) { } bool irc::tokenstream::GetToken(std::string &token) { - std::string::iterator lsp = last_starting_position; - - while (n != tokens.end()) - { - /** Skip multi space, converting " " into " " - */ - while ((n+1 != tokens.end()) && (*n == ' ') && (*(n+1) == ' ')) - n++; + bool first = !pos; - 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()); - return true; - } - - last_pushed = false; + if (!spacesepstream::GetToken(token)) + return false; - if ((*n == ' ') || (n+1 == tokens.end())) + /* This is the last parameter */ + if (token[0] == ':' && !first) + { + token.erase(token.begin()); + if (!StreamEnd()) { - /* 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(); + token += ' '; + token += GetRemaining(); } - - n++; + pos = tokens.length() + 1; } - token.clear(); - return false; -} -bool irc::tokenstream::GetToken(irc::string &token) -{ - std::string stdstring; - bool returnval = GetToken(stdstring); - token = assign(stdstring); - return returnval; + return true; } bool irc::tokenstream::GetToken(int &token) @@ -297,182 +255,59 @@ bool irc::tokenstream::GetToken(long &token) return returnval; } -irc::sepstream::sepstream(const std::string &source, char seperator) : tokens(source), sep(seperator) +irc::sepstream::sepstream(const std::string& source, char separator, bool allowempty) + : tokens(source), sep(separator), pos(0), allow_empty(allowempty) { - last_starting_position = tokens.begin(); - n = tokens.begin(); } bool irc::sepstream::GetToken(std::string &token) { - std::string::iterator lsp = last_starting_position; - - while (n != tokens.end()) - { - 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; -} - -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 (this->StreamEnd()) { - hexbuf[i++] = hex[raw[j] / 16]; - hexbuf[i++] = hex[raw[j] % 16]; - } - hexbuf[i] = 0; - - return hexbuf; -} - -CoreExport const char* irc::Spacify(const char* n) -{ - 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); -} - -void irc::modestacker::Push(char modeletter) -{ - this->Push(modeletter,""); -} - -void irc::modestacker::PushPlus() -{ - this->Push('+',""); -} - -void irc::modestacker::PushMinus() -{ - this->Push('-',""); -} - -int irc::modestacker::GetStackedLine(std::vector<std::string> &result, int max_line_size) -{ - if (sequence.empty()) - { - 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 + 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 std::deque<std::string> &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) : ""; } -irc::stringjoiner::stringjoiner(const std::string &seperator, const char* const* sequence, int begin, int end) +bool irc::sepstream::StreamEnd() { - 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->pos > this->tokens.length(); } -std::string& irc::stringjoiner::GetJoined() +std::string irc::stringjoiner(const std::vector<std::string>& sequence, char separator) { + std::string joined; + if (sequence.empty()) + return joined; // nothing to do here + + for (std::vector<std::string>::const_iterator i = sequence.begin(); i != sequence.end(); ++i) + joined.append(*i).push_back(separator); + joined.erase(joined.end()-1); return joined; } @@ -528,10 +363,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 +383,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..8bb81481e 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); + 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); - - 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(); @@ -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; @@ -281,47 +193,35 @@ void InspIRCd::ProcessColors(file_cache& input) } /* true for valid channel name, false else */ -bool IsChannelHandler::Call(const char *chname, size_t max) +bool IsChannelHandler::Call(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 IsNickHandler::Call(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 +229,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 +239,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 IsIdentHandler::Call(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,7 +266,7 @@ bool IsIdentHandler::Call(const char* n) return true; } -bool IsSIDHandler::Call(const std::string &str) +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 @@ -377,66 +276,22 @@ bool IsSIDHandler::Call(const std::string &str) ((str[2] >= 'A' && str[2] <= 'Z') || isdigit(str[2]))); } -/* open the proper logfile */ -bool InspIRCd::OpenLog(char**, int) -{ - if (!Config->cmdline.writelog) return true; // Skip opening default log if -nolog - - if (Config->cmdline.startup_log.empty()) - Config->cmdline.startup_log = LOG_PATH "/startup.log"; - FILE* startup = fopen(Config->cmdline.startup_log.c_str(), "a+"); - - if (!startup) - { - return false; - } - - FileWriter* fw = new FileWriter(startup); - FileLogStream *f = new FileLogStream((Config->cmdline.forcedebug ? DEBUG : DEFAULT), fw); - - this->Logs->AddLogType("*", f, true); - - return true; -} - void InspIRCd::CheckRoot() { #ifndef _WIN32 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 +331,44 @@ long InspIRCd::Duration(const std::string &str) return total + subtotal; } -bool InspIRCd::ULine(const std::string& sserver) +const char* 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; + } + + formatBuffer.resize(formatBuffer.size() * 2); + } - return (Config->ulines.find(sserver.c_str()) != Config->ulines.end()); + return &formatBuffer[0]; } -bool InspIRCd::SilentULine(const std::string& sserver) +const char* 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; + const char* 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,27 +382,15 @@ 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 %H:%M:%S %Y"; -// 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) @@ -574,28 +430,3 @@ void GenRandomHandler::Call(char *output, size_t max) output[i] = random(); #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..47660f752 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 @@ -54,12 +52,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 +71,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 +100,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() @@ -263,8 +158,8 @@ bool InspIRCd::DaemonSeed() // Do not use QuickExit 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) { @@ -287,13 +182,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 @@ -304,7 +199,7 @@ void InspIRCd::WritePID(const std::string& filename, bool exitonfail) #ifndef _WIN32 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 +207,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) + IsIdent(&HandleIsIdent) { 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_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; @@ -427,28 +279,26 @@ InspIRCd::InspIRCd(int argc, char** argv) : 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 }, { "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; + ConfigFileName = ServerInstance->Config->Paths.PrependConfig(optarg); break; case 0: /* getopt_long_only() set an int variable, just keep going */ @@ -458,19 +308,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] [--debug] [--config <config>]" << std::endl << + std::string(static_cast<int>(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 +336,22 @@ 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); 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 +359,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 +391,32 @@ 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(); + ModeParser::InitBuiltinModes(); - 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,8 +425,8 @@ InspIRCd::InspIRCd(int argc, char** argv) : this->Modules->LoadAll(); - /* Just in case no modules were loaded - fix for bug #101 */ - this->BuildISupport(); + // Build ISupport as ModuleManager::LoadAll() does not do it + this->ISupport.Build(); Config->ApplyDisabledCommands(Config->DisabledCommands); if (!pl.empty()) @@ -613,13 +437,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 +451,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 +465,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 +474,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,67 +495,52 @@ 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], %d 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->Logs->Log("STARTUP", LOG_DEFAULT, "setgroups() failed (wtf?): %s", strerror(errno)); this->QuickExit(0); } - // 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->Logs->Log("STARTUP", LOG_DEFAULT, "getgrnam(%s) failed (wrong group?): %s", SetGroup.c_str(), strerror(errno)); this->QuickExit(0); } - 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->Logs->Log("STARTUP", LOG_DEFAULT, "setgid(%d) failed (wrong group?): %s", g->gr_gid, strerror(errno)); this->QuickExit(0); } } + 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->Logs->Log("STARTUP", LOG_DEFAULT, "getpwnam(%s) failed (wrong user?): %s", SetUser.c_str(), strerror(errno)); this->QuickExit(0); } - 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->Logs->Log("STARTUP", LOG_DEFAULT, "setuid(%d) failed (wrong user?): %s", u->pw_uid, strerror(errno)); this->QuickExit(0); } } @@ -760,15 +569,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 +594,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,18 +614,18 @@ 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 @@ -827,21 +638,25 @@ int InspIRCd::Run() { SNO->WriteToSnoMask('d', "\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 +668,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 +681,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..9bfc6a73e 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,7 +45,7 @@ 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) @@ -66,7 +64,7 @@ BufferedSocketError BufferedSocket::BeginConnect(const std::string &ipaddr, int 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; } @@ -92,13 +90,13 @@ BufferedSocketError BufferedSocket::BeginConnect(const irc::sockets::sockaddrs& if (bind.sa.sa_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.sa, dest.sa_size()) == -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 += elem.length(); + } + 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..b59492738 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,19 @@ 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); +} diff --git a/src/listensocket.cpp b/src/listensocket.cpp index ae11c3b48..4ec6c2b06 100644 --- a/src/listensocket.cpp +++ b/src/listensocket.cpp @@ -19,15 +19,16 @@ #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); if (this->fd == -1) @@ -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,55 +106,32 @@ 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)); } } -/* 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) @@ -156,7 +164,7 @@ void ListenSocket::AcceptInternal() } } - ServerInstance->SE->NonBlocking(incomingSockfd); + SocketEngine::NonBlocking(incomingSockfd); ModResult res; FIRST_MOD_RESULT(OnAcceptConnection, res, (incomingSockfd, this, &client, &server)); @@ -171,29 +179,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..23f98bf3f --- /dev/null +++ b/src/listmode.cpp @@ -0,0 +1,235 @@ +/* + * 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->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. + 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::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..e3e956325 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,13 @@ * */ +const char LogStream::LogHeader[] = + "Log started for " INSPIRCD_VERSION " (" MODULE_INIT_STR ")" + " - compiled on " INSPIRCD_SYSTEM; + LogManager::LogManager() + : Logging(false) { - Logging = false; } LogManager::~LogManager() @@ -82,43 +84,43 @@ void LogManager::OpenFileLogs() } std::string type = tag->getString("type"); std::string level = tag->getString("level"); - int loglevel = DEFAULT; + LogLevel loglevel = LOG_DEFAULT; if (level == "rawio") { - loglevel = RAWIO; + loglevel = LOG_RAWIO; ServerInstance->Config->RawLog = true; } else if (level == "debug") { - loglevel = DEBUG; + loglevel = LOG_DEBUG; } else if (level == "verbose") { - loglevel = VERBOSE; + loglevel = LOG_VERBOSE; } else if (level == "default") { - loglevel = DEFAULT; + loglevel = LOG_DEFAULT; } else if (level == "sparse") { - loglevel = SPARSE; + loglevel = LOG_SPARSE; } else if (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, static_cast<unsigned int>(tag->getInt("flush", 20, 1, INT_MAX))); logmap.insert(std::make_pair(target, fw)); } else @@ -126,7 +128,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 +137,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 +192,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 +207,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 +230,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 +267,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 +288,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 +309,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 +323,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..fd5e30707 100644 --- a/src/mode.cpp +++ b/src/mode.cpp @@ -24,41 +24,18 @@ #include "inspircd.h" +#include "builtinmodes.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), levelrequired(HALFOP_VALUE) { } CullResult ModeHandler::cull() { - if (ServerInstance->Modes) + if (ServerInstance) ServerInstance->Modes->DelMode(this); return classbase::cull(); } @@ -67,31 +44,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 ""; } @@ -123,11 +90,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 +109,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 +119,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 +128,107 @@ 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) -{ - 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; -} - -bool ParamChannelModeHandler::ParamValidate(std::string& parameter) -{ - return true; -} - -ModeWatcher::ModeWatcher(Module* Creator, char modeletter, ModeType type) - : mode(modeletter), m_type(type), creator(Creator) +ModeWatcher::ModeWatcher(Module* Creator, const std::string& modename, ModeType type) + : mode(modename), m_type(type), creator(Creator) { + ServerInstance->Modes->AddModeWatcher(this); } ModeWatcher::~ModeWatcher() { + ServerInstance->Modes->DelModeWatcher(this); } -char ModeWatcher::GetModeChar() +bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool) { - return mode; + return true; } -ModeType ModeWatcher::GetModeType() +void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool) { - return m_type; } -bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool, ModeType) +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) { - return true; + list = true; } -void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool, ModeType) +ModeAction PrefixMode::OnModeChange(User* source, User*, Channel* chan, std::string& parameter, bool adding) { -} + User* target; + if (IS_LOCAL(source)) + target = ServerInstance->FindNickOnly(parameter); + else + target = ServerInstance->FindNick(parameter); -User* ModeParser::SanityChecks(User *user, const char *dest, Channel *chan, int) -{ - User *d; - if ((!user) || (!dest) || (!chan) || (!*dest)) + if (!target) { - 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; + source->WriteNumeric(Numerics::NoSuchNick(parameter)); + return MODEACTION_DENY; } - return d; + + Membership* memb = chan->GetUser(target); + if (!memb) + return MODEACTION_DENY; + + parameter = target->nick; + return (memb->SetPrefix(this, adding) ? MODEACTION_ALLOW : MODEACTION_DENY); } -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); @@ -286,11 +246,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 +259,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; - } - - ModResult MOD_RESULT; - FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (user, targetuser, targetchannel, parameters)); - - bool SkipAccessChecks = false; + if (endindex > parameters.size()) + endindex = parameters.size(); - 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 +354,166 @@ 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); - continue; - } - else if (pcnt) - { + if ((mh->NeedsParam(adding)) && (param_at < endindex)) 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)) + + 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) +{ + LastParse.clear(); + LastChangeList.clear(); + + unsigned int modes_processed = 0; + std::string output_mode; + std::string output_parameters; + + char output_pm = '\0'; // current output state, '+' or '-' + 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 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)) + { + // 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 ? '+' : '-'; + char needed_pm = item.adding ? '+' : '-'; if (needed_pm != output_pm) { output_pm = needed_pm; output_mode.append(1, output_pm); } - output_mode.append(1, modechar); + output_mode.push_back(mh->GetModeChar()); - if (pcnt) + if (!item.param.empty()) { - 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); + output_parameters.push_back(' '); + output_parameters.append(item.param); } + LastChangeList.push(mh, item.adding, item.param); - if ( (output_mode.length() + output_parameters.str().length() > 450) + if ((output_mode.length() + output_parameters.length() > 450) || (output_mode.length() > 100) - || (LastParseParams.size() > ServerInstance->Config->Limits.MaxModes)) + || (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes)) { /* mode sequence is getting too long */ break; } } - LastParseParams[0] = output_mode; - if (!output_mode.empty()) { LastParse = targetchannel ? targetchannel->name : targetuser->nick; LastParse.append(" "); LastParse.append(output_mode); - LastParse.append(output_parameters.str()); + LastParse.append(output_parameters); if (targetchannel) - { - targetchannel->WriteChannel(user, "MODE %s", LastParse.c_str()); - FOREACH_MOD(I_OnMode,OnMode(user, targetchannel, TYPE_CHANNEL, LastParseParams, LastParseTranslate)); - } + targetchannel->WriteChannel(user, "MODE " + LastParse); else - { - targetuser->WriteFrom(user, "MODE %s", LastParse.c_str()); - FOREACH_MOD(I_OnMode,OnMode(user, targetuser, TYPE_USER, LastParseParams, LastParseTranslate)); - } - } - 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); + targetuser->WriteFrom(user, "MODE " + LastParse); + + FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags, output_mode)); } + + 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 +521,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 +557,79 @@ 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()); + + RecreateModeListFor004Numeric(); } 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 +638,106 @@ 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())); + RecreateModeListFor004Numeric(); return true; } -ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt) +ModeHandler* ModeParser::FindMode(const std::string& modename, 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; + ModeHandlerMap& mhmap = modehandlersbyname[mt]; + ModeHandlerMap::const_iterator it = mhmap.find(modename); + if (it != mhmap.end()) + return it->second; - return modehandlers[pos]; + return NULL; } -std::string ModeParser::UserModeList() +ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt) { - char modestr[256]; - int pointer = 0; + if (!ModeParser::IsModeChar(modeletter)) + return NULL; - for (unsigned char mode = 'A'; mode <= 'z'; mode++) - { - unsigned char pos = (mode-65) | MASK_USER; + return modehandlers[mt][modeletter-65]; +} - if (modehandlers[pos]) - modestr[pointer++] = mode; - } - modestr[pointer++] = 0; - return modestr; +PrefixMode* ModeParser::FindPrefixMode(unsigned char modeletter) +{ + ModeHandler* mh = FindMode(modeletter, MODETYPE_CHANNEL); + if (!mh) + return NULL; + return mh->IsPrefixMode(); } -std::string ModeParser::ChannelModeList() +std::string ModeParser::CreateModeList(ModeType mt, bool needparam) { - char modestr[256]; - int pointer = 0; + std::string modestr; for (unsigned char mode = 'A'; mode <= 'z'; mode++) { - unsigned char pos = (mode-65) | MASK_CHANNEL; - - if (modehandlers[pos]) - modestr[pointer++] = mode; + ModeHandler* mh = modehandlers[mt][mode-65]; + if ((mh) && ((!needparam) || (mh->NeedsParam(true)))) + modestr.push_back(mode); } - modestr[pointer++] = 0; + return modestr; } -std::string ModeParser::ParaModeList() +void ModeParser::RecreateModeListFor004Numeric() { - 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; + Cached004ModeList[0] = CreateModeList(MODETYPE_USER); + Cached004ModeList[1] = CreateModeList(MODETYPE_CHANNEL); + Cached004ModeList[2] = CreateModeList(MODETYPE_CHANNEL, true); } -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 +746,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 +787,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 +797,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,103 +817,68 @@ 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); + } +} + +void PrefixMode::RemoveMode(Channel* chan, Modes::ChangeList& changelist) +{ + const Channel::MemberMap& userlist = chan->GetUsers(); + for (Channel::MemberMap::const_iterator i = userlist.begin(); i != userlist.end(); ++i) + { + if (i->second->HasMode(this)) + changelist.push_remove(this, i->first->nick); } } struct builtin_modes { - ModeChannelSecret s; - ModeChannelPrivate p; - ModeChannelModerated m; - ModeChannelTopicOps t; + SimpleChannelModeHandler s; + SimpleChannelModeHandler p; + SimpleChannelModeHandler m; + SimpleChannelModeHandler t; - ModeChannelNoExternal n; - ModeChannelInviteOnly i; + SimpleChannelModeHandler n; + SimpleChannelModeHandler i; ModeChannelKey k; ModeChannelLimit l; @@ -975,45 +886,47 @@ struct builtin_modes ModeChannelOp o; ModeChannelVoice v; - ModeUserWallops uw; - ModeUserInvisible ui; + SimpleUserModeHandler ui; ModeUserOperator uo; ModeUserServerNoticeMask us; - void init(ModeParser* modes) - { - 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); + builtin_modes() + : s(NULL, "secret", 's') + , p(NULL, "private", 'p') + , m(NULL, "moderated", 'm') + , t(NULL, "topiclock", 't') + , n(NULL, "noextmsg", 'n') + , i(NULL, "inviteonly", 'i') + , ui(NULL, "invisible", 'i') + { + } + + void init() + { + ServiceProvider* modes[] = { &s, &p, &m, &t, &n, &i, &k, &l, &b, &o, &v, + &ui, &uo, &us }; + ServerInstance->Modules->AddServices(modes, sizeof(modes)/sizeof(ServiceProvider*)); } }; static builtin_modes static_modes; +void ModeParser::InitBuiltinModes() +{ + static_modes.init(); + static_modes.b.DoRehash(); +} + +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_k.cpp b/src/modes/cmode_k.cpp index 400333fce..980b3215a 100644 --- a/src/modes/cmode_k.cpp +++ b/src/modes/cmode_k.cpp @@ -21,79 +21,54 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/cmode_k.h" +#include "builtinmodes.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); - } - } -} - -void ModeChannelKey::RemoveMode(User*, irc::modestacker* stack) +ModeChannelKey::ModeChannelKey() + : ParamMode<ModeChannelKey, LocalStringExt>(NULL, "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; - + channel->SetMode(this, adding); if (adding) { - std::string ckey; - ckey.assign(parameter, 0, 32); - parameter = ckey; - channel->SetModeParam('k', parameter); + if (parameter.length() > maxkeylen) + parameter.erase(maxkeylen); + ext.set(channel, parameter); } else - { - channel->SetModeParam('k', ""); - } + ext.unset(channel); + 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/modes/cmode_l.cpp index 001d058bb..d61b2597b 100644 --- a/src/modes/cmode_l.cpp +++ b/src/modes/cmode_l.cpp @@ -20,12 +20,10 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/cmode_l.h" +#include "builtinmodes.h" -ModeChannelLimit::ModeChannelLimit() : ParamChannelModeHandler(NULL, "limit", 'l') +ModeChannelLimit::ModeChannelLimit() + : ParamMode<ModeChannelLimit, LocalIntExt>(NULL, "limit", 'l') { } @@ -35,13 +33,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()); - + int limit = ConvToInt(parameter); if (limit < 0) - return false; + return MODEACTION_DENY; + + 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/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_o.cpp b/src/modes/umode_o.cpp index a5f590ba0..9d50e571b 100644 --- a/src/modes/umode_o.cpp +++ b/src/modes/umode_o.cpp @@ -19,10 +19,7 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/umode_o.h" +#include "builtinmodes.h" ModeUserOperator::ModeUserOperator() : ModeHandler(NULL, "oper", 'o', PARAM_NONE, MODETYPE_USER) { @@ -32,7 +29,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 +37,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/modes/umode_s.cpp b/src/modes/umode_s.cpp index 1b782ae85..ffad21662 100644 --- a/src/modes/umode_s.cpp +++ b/src/modes/umode_s.cpp @@ -20,10 +20,7 @@ #include "inspircd.h" -#include "mode.h" -#include "channels.h" -#include "users.h" -#include "modes/umode_s.h" +#include "builtinmodes.h" ModeUserServerNoticeMask::ModeUserServerNoticeMask() : ModeHandler(NULL, "snomask", 's', PARAM_SETONLY, MODETYPE_USER) { @@ -32,41 +29,116 @@ ModeUserServerNoticeMask::ModeUserServerNoticeMask() : ModeHandler(NULL, "snomas 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()); + 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->modes[UM_SNOMASK] != false) + if (dest->IsModeSet(this)) { - dest->modes[UM_SNOMASK] = false; + dest->SetMode(this, false); + dest->snomasks.reset(); return MODEACTION_ALLOW; } } - /* Allow the change */ + // Mode not set and trying to unset, deny return MODEACTION_DENY; } -std::string ModeUserServerNoticeMask::GetUserParameter(User* user) +std::string ModeUserServerNoticeMask::GetUserParameter(const User* user) const { - std::string masks = user->FormatNoticeMasks(); - if (masks.length()) - masks = "+" + masks; - return masks; + 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->WriteServ("NOTICE %s :*** The user mode +s requires a parameter (server notice mask). Please provide a parameter, e.g. '+s +*'.", - user->nick.c_str()); + 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/modmanager_dynamic.cpp b/src/modmanager_dynamic.cpp index 050f41c75..644d2140f 100644 --- a/src/modmanager_dynamic.cpp +++ b/src/modmanager_dynamic.cpp @@ -18,11 +18,6 @@ #include "inspircd.h" -#include "xline.h" -#include "socket.h" -#include "socketengine.h" -#include "command_parse.h" -#include "dns.h" #include "exitcodes.h" #include <iostream> @@ -30,40 +25,44 @@ #include <dirent.h> #endif -#ifndef PURE_STATIC +#ifndef INSPIRCD_STATIC -bool ModuleManager::Load(const std::string& filename, bool defer) +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 (filename.find('/') != std::string::npos) + if (modname.find('/') != std::string::npos) { - LastModuleError = "You can't load modules with a path: " + filename; + LastModuleError = "You can't load modules with a path: " + modname; return false; } - char modfile[MAXBUF]; - snprintf(modfile,MAXBUF,"%s/%s",ServerInstance->Config->ModPath.c_str(),filename.c_str()); + const std::string filename = ExpandModName(modname); + const std::string moduleFile = ServerInstance->Config->Paths.PrependModule(filename); - if (!ServerConfig::FileExists(modfile)) + if (!FileSystem::FileExists(moduleFile)) { LastModuleError = "Module file could not be found: " + filename; - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); + 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", DEFAULT, LastModuleError); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); return false; } Module* newmod = NULL; - DLLManager* newhandle = new DLLManager(modfile); + DLLManager* newhandle = new DLLManager(moduleFile.c_str()); + ServiceList newservices; + if (!defer) + this->NewServices = &newservices; try { newmod = newhandle->CallInit(); + this->NewServices = NULL; if (newmod) { @@ -72,30 +71,39 @@ bool ModuleManager::Load(const std::string& filename, bool defer) newmod->dying = false; Modules[filename] = newmod; std::string version = newhandle->GetVersion(); + if (version.empty()) + version.assign("unknown"); if (defer) { - ServerInstance->Logs->Log("MODULE", DEFAULT,"New module introduced: %s (Module version %s)", + 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", DEFAULT,"New module introduced: %s (Module version %s)%s", + 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", DEFAULT, LastModuleError); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, LastModuleError); delete newhandle; return false; } } catch (CoreException& modexcept) { + this->NewServices = NULL; + // failure in module constructor if (newmod) { @@ -105,109 +113,41 @@ bool ModuleManager::Load(const std::string& filename, bool defer) else delete newhandle; LastModuleError = "Unable to load " + filename + ": " + modexcept.GetReason(); - ServerInstance->Logs->Log("MODULE", DEFAULT, LastModuleError); + ServerInstance->Logs->Log("MODULE", LOG_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)); + FOREACH_MOD(OnLoadModule, (newmod)); + PrioritizeHooks(); + ServerInstance->ISupport.Build(); 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() +void ModuleManager::LoadCoreModules(std::map<std::string, ServiceList>& servicemap) { - ModCount = 0; - std::cout << std::endl << "Loading core commands"; fflush(stdout); - DIR* library = opendir(ServerInstance->Config->ModPath.c_str()); + 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, "cmd_*.so", ascii_case_insensitive_map)) + 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", DEFAULT, this->LastError()); + 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); } @@ -216,57 +156,6 @@ void ModuleManager::LoadAll() 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 index cea40c7a3..4de111b63 100644 --- a/src/modmanager_static.cpp +++ b/src/modmanager_static.cpp @@ -17,13 +17,13 @@ */ -#define MODNAME cmd_all +#define MODNAME "core_all" #include "inspircd.h" #include "exitcodes.h" #include <iostream> -#ifdef PURE_STATIC +#ifdef INSPIRCD_STATIC typedef std::map<std::string, AllModuleList*> modmap; static std::vector<AllCommandList::fn>* cmdlist = NULL; @@ -58,7 +58,6 @@ class AllModule : public Module { Command* c = (*i)(this); cmds.push_back(c); - ServerInstance->AddCommand(c); } } catch (...) @@ -70,11 +69,10 @@ class AllModule : public Module ~AllModule() { - for(std::vector<Command*>::iterator i = cmds.begin(); i != cmds.end(); ++i) - delete *i; + stdalgo::delete_all(cmds); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("All commands", VF_VENDOR|VF_CORE); } @@ -82,12 +80,18 @@ class AllModule : public Module MODULE_INIT(AllModule) -bool ModuleManager::Load(const std::string& name, bool defer) +bool ModuleManager::Load(const std::string& inputname, bool defer) { + const std::string name = ExpandModName(inputname); modmap::iterator it = modlist->find(name); if (it == modlist->end()) return false; Module* mod = NULL; + + ServiceList newservices; + if (!defer) + this->NewServices = &newservices; + try { mod = (*it->second->init)(); @@ -95,147 +99,50 @@ bool ModuleManager::Load(const std::string& name, bool defer) mod->ModuleDLLManager = NULL; mod->dying = false; Modules[name] = mod; + this->NewServices = NULL; if (defer) { - ServerInstance->Logs->Log("MODULE", DEFAULT,"New module introduced: %s", name.c_str()); + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "New module introduced: %s", name.c_str()); return true; } else { + ConfigStatus confstatus; + + AttachAll(mod); + AddServices(newservices); mod->init(); + mod->ReadConfig(confstatus); } } catch (CoreException& modexcept) { + this->NewServices = NULL; + if (mod) DoSafeUnload(mod); - ServerInstance->Logs->Log("MODULE", DEFAULT, "Unable to load " + name + ": " + modexcept.GetReason()); + ServerInstance->Logs->Log("MODULE", LOG_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)); + FOREACH_MOD(OnLoadModule, (mod)); + PrioritizeHooks(); + ServerInstance->ISupport.Build(); return true; } -void ModuleManager::Reload(Module* mod, HandlerBase1<void, bool>* callback) +void ModuleManager::LoadCoreModules(std::map<std::string, ServiceList>& servicemap) { - 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++) + for (modmap::const_iterator i = modlist->begin(); i != modlist->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) + const std::string modname = i->first; + if (InspIRCd::Match(modname, "core_*.so", ascii_case_insensitive_map)) { - ServerInstance->Logs->Log("MODULE", DEFAULT, "Hook priority dependency loop detected"); - ServerInstance->Exit(EXIT_STATUS_MODULE); + this->NewServices = &servicemap[modname]; + Load(modname, true); } } + this->NewServices = NULL; } #endif diff --git a/src/modules.cpp b/src/modules.cpp index b2d2f23c6..5c5e5c5c0 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,100 +63,101 @@ 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::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::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, const std::string&) { 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::OnInfo(User*) { DetachEvent(I_OnInfo); } +ModResult Module::OnUserPreInvite(User*, User*, Channel*, time_t) { DetachEvent(I_OnUserPreInvite); return MOD_RES_PASSTHRU; } +ModResult Module::OnUserPreMessage(User*, void*, int, std::string&, char, CUList&, MessageType) { 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&, std::vector<std::string>&, LocalUser*, bool, const std::string&) { DetachEvent(I_OnPreCommand); return MOD_RES_PASSTHRU; } +void Module::OnPostCommand(Command*, const std::vector<std::string>&, LocalUser*, CmdResult, const std::string&) { 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::OnStats(Stats::Context&) { DetachEvent(I_OnStats); return MOD_RES_PASSTHRU; } +ModResult Module::OnChangeLocalUserHost(LocalUser*, const std::string&) { DetachEvent(I_OnChangeLocalUserHost); return MOD_RES_PASSTHRU; } +ModResult Module::OnChangeLocalUserGECOS(LocalUser*, const std::string&) { DetachEvent(I_OnChangeLocalUserGECOS); 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::OnUserMessage(User*, void*, int, const std::string&, char, const CUList&, MessageType) { DetachEvent(I_OnUserMessage); } +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::OnSyncUser(User*, ProtocolInterface::Server&) { DetachEvent(I_OnSyncUser); } +void Module::OnSyncChannel(Channel*, ProtocolInterface::Server&) { DetachEvent(I_OnSyncChannel); } +void Module::OnSyncNetwork(ProtocolInterface::Server&) { DetachEvent(I_OnSyncNetwork); } +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::OnChangeName(User*, const std::string&) { DetachEvent(I_OnChangeName); } +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(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&) { } +ModResult Module::OnChannelPreDelete(Channel*) { DetachEvent(I_OnChannelPreDelete); return MOD_RES_PASSTHRU; } +void Module::OnChannelDelete(Channel*) { DetachEvent(I_OnChannelDelete); } +ModResult Module::OnSetAway(User*, const std::string &) { DetachEvent(I_OnSetAway); return MOD_RES_PASSTHRU; } +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::OnText(User*, void*, int, const std::string&, char, CUList&) { DetachEvent(I_OnText); } +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); } + +#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() { } @@ -188,7 +167,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); @@ -197,13 +176,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) @@ -212,18 +185,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) @@ -254,22 +231,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) @@ -284,7 +264,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) { @@ -324,6 +304,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); @@ -331,13 +334,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; } @@ -345,24 +342,48 @@ 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); 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) 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 @@ -370,16 +391,7 @@ void ModuleManager::DoSafeUnload(Module* mod) mod->OnCleanup(TYPE_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++; @@ -389,19 +401,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() @@ -427,40 +433,105 @@ void ModuleManager::UnloadAll() } } -std::string& ModuleManager::LastError() +namespace { - return LastModuleError; + 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(); + // 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 = tag->getString("name"); + this->NewServices = &servicemap[ExpandModName(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", 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) @@ -468,10 +539,11 @@ 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)); } + dynamic_reference_base::reset_all(); return; } default: - throw ModuleException("Cannot add unknown service type"); + item.RegisterService(); } } @@ -482,17 +554,11 @@ 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: @@ -518,85 +584,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; @@ -604,178 +651,21 @@ Module* ModuleManager::Find(const std::string &name) return modfind->second; } -const std::vector<std::string> ModuleManager::GetAllModuleNames(int filter) -{ - 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; - if (!SlowGetTag(tag, index)->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"); - return SlowGetTag(tag, index)->getBool(name, 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()); - int result = SlowGetTag(tag, index)->getInt(name, 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) +void ModuleManager::AddReferent(const std::string& name, ServiceProvider* service) { - 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 a36c39bc8..c7b0fd210 100644 --- a/src/modules/extra/m_geoip.cpp +++ b/src/modules/extra/m_geoip.cpp @@ -17,19 +17,29 @@ * 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("ubuntu") libgeoip-dev pkg-config #include "inspircd.h" #include "xline.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> #ifdef _WIN32 # pragma comment(lib, "GeoIP.lib") #endif -/* $ModDesc: Provides a way to restrict users by country using GeoIP lookup */ -/* $LinkerFlags: -lGeoIP */ - class ModuleGeoIP : public Module { LocalStringExt ext; @@ -37,7 +47,7 @@ class ModuleGeoIP : public Module std::string* SetExt(LocalUser* 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"; @@ -47,21 +57,20 @@ class ModuleGeoIP : public Module } public: - ModuleGeoIP() : ext("geoip_cc", this), gi(NULL) + ModuleGeoIP() + : ext("geoip_cc", ExtensionItem::EXT_USER, this) + , 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_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 +86,12 @@ class ModuleGeoIP : public Module GeoIP_delete(gi); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides a way to assign users to connect classes by country using GeoIP lookup", VF_VENDOR); } - ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE { std::string* cc = ext.get(user); if (!cc) @@ -99,14 +108,16 @@ class ModuleGeoIP : public Module return MOD_RES_DENY; } - ModResult OnStats(char symbol, User* user, string_list &out) + 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) @@ -115,18 +126,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..08d81e960 --- /dev/null +++ b/src/modules/extra/m_ldap.cpp @@ -0,0 +1,676 @@ +/* + * 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("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 (scope == "base") + searchscope = LDAP_SCOPE_BASE; + else if (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 (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 01b1553b0..39b0c369d 100644 --- a/src/modules/extra/m_mysql.cpp +++ b/src/modules/extra/m_mysql.cpp @@ -19,13 +19,25 @@ * 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("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 +45,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 @@ -90,7 +98,7 @@ struct RQueueItem RQueueItem(SQLQuery* 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; @@ -105,11 +113,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 @@ -119,8 +127,8 @@ class DispatcherThread : public SocketThread public: DispatcherThread(ModuleSQL* CreatorModule) : Parent(CreatorModule) { } ~DispatcherThread() { } - virtual void Run(); - virtual void OnNotify(); + void Run(); + void OnNotify(); }; #if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID<32224 @@ -186,21 +194,17 @@ class MySQLresult : public SQLResult } - ~MySQLresult() - { - } - - virtual int Rows() + int Rows() { return rows; } - virtual void GetCols(std::vector<std::string>& result) + void GetCols(std::vector<std::string>& result) { result.assign(colnames.begin(), colnames.end()); } - virtual SQLEntry GetValue(int row, int column) + SQLEntry GetValue(int row, int column) { if ((row >= 0) && (row < rows) && (column >= 0) && (column < (int)fieldlists[row].size())) { @@ -209,7 +213,7 @@ class MySQLresult : public SQLResult return SQLEntry(); } - virtual bool GetRow(SQLEntries& result) + bool GetRow(SQLEntries& result) { if (currentrow < rows) { @@ -260,6 +264,12 @@ class SQLConnection : public SQLProvider 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)) { @@ -383,12 +393,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() @@ -405,7 +410,7 @@ ModuleSQL::~ModuleSQL() } } -void ModuleSQL::OnRehash(User* user) +void ModuleSQL::ReadConfig(ConfigStatus& status) { ConnMap conns; ConfigTagList tags = ServerInstance->Config->ConfTags("database"); diff --git a/src/modules/extra/m_pgsql.cpp b/src/modules/extra/m_pgsql.cpp index ac247548a..6835cb558 100644 --- a/src/modules/extra/m_pgsql.cpp +++ b/src/modules/extra/m_pgsql.cpp @@ -21,16 +21,18 @@ * 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("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 +45,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,10 +61,10 @@ 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); }; struct QueueItem @@ -97,12 +99,12 @@ class PgSQLresult : public SQLResult PQclear(res); } - virtual int Rows() + int Rows() { return rows; } - virtual void GetCols(std::vector<std::string>& result) + void GetCols(std::vector<std::string>& result) { result.resize(PQnfields(res)); for(unsigned int i=0; i < result.size(); i++) @@ -111,7 +113,7 @@ class PgSQLresult : public SQLResult } } - virtual SQLEntry GetValue(int row, int column) + SQLEntry GetValue(int row, int column) { char* v = PQgetvalue(res, row, column); if (!v || PQgetisnull(res, row, column)) @@ -120,7 +122,7 @@ class PgSQLresult : public SQLResult return SQLEntry(std::string(v, PQgetlength(res, row, column))); } - virtual bool GetRow(SQLEntries& result) + bool GetRow(SQLEntries& result) { if (currentrow >= PQntuples(res)) return false; @@ -152,12 +154,12 @@ class SQLConn : public SQLProvider, public EventHandler { 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(); ServerInstance->Modules->DelService(*this); @@ -180,18 +182,19 @@ class SQLConn : public SQLProvider, public EventHandler } } - 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 +245,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 +260,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: @@ -350,17 +353,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 +389,7 @@ restart: } } - void submit(SQLQuery *req, const std::string& q) + void submit(SQLQuery *req, const std::string& q) CXX11_OVERRIDE { if (qinprog.q.empty()) { @@ -399,7 +402,7 @@ restart: } } - void submit(SQLQuery *req, const std::string& q, const ParamL& p) + void submit(SQLQuery *req, const std::string& q, const ParamL& p) CXX11_OVERRIDE { std::string res; unsigned int param = 0; @@ -413,14 +416,10 @@ 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); } } @@ -428,7 +427,7 @@ restart: submit(req, res); } - void submit(SQLQuery *req, const std::string& q, const ParamM& p) + void submit(SQLQuery *req, const std::string& q, const ParamM& p) CXX11_OVERRIDE { std::string res; for(std::string::size_type i = 0; i < q.length(); i++) @@ -448,14 +447,10 @@ restart: { 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); } } @@ -488,7 +483,7 @@ restart: void Close() { - ServerInstance->SE->DelFd(this); + SocketEngine::DelFd(this); if(sql) { @@ -505,25 +500,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(); } @@ -564,7 +551,7 @@ class ModulePgSQL : public Module connections.clear(); } - void OnUnloadModule(Module* mod) + void OnUnloadModule(Module* mod) CXX11_OVERRIDE { SQLerror err(SQL_BAD_DBID); for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++) @@ -592,16 +579,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 +604,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..e270ca039 100644 --- a/src/modules/extra/m_regex_pcre.cpp +++ b/src/modules/extra/m_regex_pcre.cpp @@ -17,35 +17,27 @@ * 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("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 +45,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 +65,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 +73,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..2f0ee2998 --- /dev/null +++ b/src/modules/extra/m_regex_re2.cpp @@ -0,0 +1,85 @@ +/* + * 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("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..7a888ed72 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,23 +60,20 @@ 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") ref.regextype = std::regex::basic; else if(regextype == "ere") diff --git a/src/modules/extra/m_regex_tre.cpp b/src/modules/extra/m_regex_tre.cpp index 4b9eab472..e2eafcd01 100644 --- a/src/modules/extra/m_regex_tre.cpp +++ b/src/modules/extra/m_regex_tre.cpp @@ -17,29 +17,19 @@ * 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("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 +50,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 +78,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..ac7146e38 100644 --- a/src/modules/extra/m_sqlite3.cpp +++ b/src/modules/extra/m_sqlite3.cpp @@ -19,22 +19,31 @@ * 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("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 { @@ -48,16 +57,12 @@ class SQLite3Result : public SQLResult { } - ~SQLite3Result() - { - } - - virtual int Rows() + int Rows() { return rows; } - virtual bool GetRow(SQLEntries& result) + bool GetRow(SQLEntries& result) { if (currentrow < rows) { @@ -72,7 +77,7 @@ class SQLite3Result : public SQLResult } } - virtual void GetCols(std::vector<std::string>& result) + void GetCols(std::vector<std::string>& result) { result.assign(columns.begin(), columns.end()); } @@ -80,7 +85,6 @@ class SQLite3Result : public SQLResult class SQLConn : public SQLProvider { - private: sqlite3* conn; reference<ConfigTag> config; @@ -93,7 +97,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")); } } @@ -154,13 +158,13 @@ class SQLConn : public SQLProvider sqlite3_finalize(stmt); } - virtual void submit(SQLQuery* query, const std::string& q) + void submit(SQLQuery* query, const std::string& q) { Query(query, q); delete query; } - virtual void submit(SQLQuery* query, const std::string& q, const ParamL& p) + void submit(SQLQuery* query, const std::string& q, const ParamL& p) { std::string res; unsigned int param = 0; @@ -181,7 +185,7 @@ class SQLConn : public SQLProvider submit(query, res); } - virtual void submit(SQLQuery* query, const std::string& q, const ParamM& p) + void submit(SQLQuery* query, const std::string& q, const ParamM& p) { std::string res; for(std::string::size_type i = 0; i < q.length(); i++) @@ -211,23 +215,10 @@ class SQLConn : public SQLProvider 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,7 +234,7 @@ class ModuleSQLite3 : public Module conns.clear(); } - void ReadConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ClearConns(); ConfigTagList tags = ServerInstance->Config->ConfTags("database"); @@ -257,12 +248,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..8b414230a 100644 --- a/src/modules/extra/m_ssl_gnutls.cpp +++ b/src/modules/extra/m_ssl_gnutls.cpp @@ -20,119 +20,99 @@ * 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") + +/// $LinkerFlags: find_linker_flags("gnutls" "-lgnutls") +/// $LinkerFlags: require_version("gnutls" "1.0" "2.12") execute("libgcrypt-config --libs" "LIBGCRYPT_LDFLAGS") + +/// $PackageInfo: require_system("centos") gnutls-devel pkgconfig +/// $PackageInfo: require_system("darwin") gnutls 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" +# else +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# endif +#endif + #include <gnutls/gnutls.h> #include <gnutls/x509.h> -#include "ssl.h" -#include "m_cap.h" -#ifdef _WIN32 -# pragma comment(lib, "libgnutls-30.lib") +#ifndef GNUTLS_VERSION_NUMBER +#define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER +#define GNUTLS_VERSION LIBGNUTLS_VERSION #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 */ +// 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)) -#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 +#if INSPIRCD_GNUTLS_HAS_VERSION(2, 9, 8) +#define GNUTLS_HAS_MAC_GET_ID +#include <gnutls/crypto.h> #endif -// 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 -#endif - -#if(GNUTLS_VERSION_MAJOR < 2) -typedef gnutls_certificate_credentials_t gnutls_certificate_credentials; -typedef gnutls_dh_params_t gnutls_dh_params; -#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]); - - 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, 0, 0)) +typedef gnutls_certificate_credentials_t gnutls_certificate_credentials; +typedef gnutls_dh_params_t gnutls_dh_params; #endif - } -}; -static reference<SSLConfig> currconf; +enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN }; -static SSLConfig* GetSessionConfig(gnutls_session_t session); +#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 -#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 + +static Module* thismod; class RandGen : public HandlerBase2<void, char*, size_t> { public: - RandGen() {} void Call(char* buffer, size_t len) { #ifdef GNUTLS_HAS_RND @@ -143,749 +123,670 @@ 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) -{ - issl_session* session = reinterpret_cast<issl_session*>(gnutls_transport_get_ptr(sess)); - return session->config; -} - -class CommandStartTLS : public SplitCommand +namespace GnuTLS { - 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 (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 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(); - - ConfigTag* Conf = ServerInstance->Config->ConfValue("gnutls"); - starttls.enabled = Conf->getBool("starttls", true); + std::vector<gnutls_x509_crt_t> certs; - 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; + // 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); + } - 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()); + ThrowOnError(ret, "Unable to load certificates"); - 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; - } - } + // Resize the vector to the actual number of certs because we rely on its size being correct + // when deallocating the certs + certs.resize(certcount); } - } - - void OnModuleRehash(User* user, const std::string ¶m) - { - if(param != "ssl") - return; - reference<SSLConfig> newconf = new SSLConfig; - try + ~X509CertList() { - InitSSLConfig(newconf); - } - catch (ModuleException& ex) - { - 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"); + } - void On005Numeric(std::string &output) - { - if (!sslports.empty()) - output.append(" SSL=" + sslports); - if (starttls.enabled) - output.append(" STARTTLS"); - } + trustedca = certlist; + crl = CRL; + } + } + }; - void OnHookIO(StreamSocket* user, ListenSocket* lsb) + class DataReader { - if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "gnutls") + int retval; +#ifdef INSPIRCD_GNUTLS_HAS_RECV_PACKET + gnutls_packet_t packet; + + 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 : public refcountbase { - 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; + + Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr, + std::auto_ptr<DHParams>& DH, unsigned int mindh, const std::string& hashstr, + const std::string& priostr, std::auto_ptr<X509CertList>& CA, std::auto_ptr<X509CRL>& CRL, + unsigned int recsize, bool Requestclientcert) + : name(profilename) + , x509cred(certstr, keystr) + , min_dh_bits(mindh) + , hash(hashstr) + , priority(priostr) + , outrecsize(recsize) + , requestclientcert(Requestclientcert) { - CloseSession(session); - user->SetError("No SSL session"); - return -1; + x509cred.SetDH(DH); + x509cred.SetCA(CA, CRL); } - if (session->status == ISSL_HANDSHAKING_READ || session->status == ISSL_HANDSHAKING_WRITE) + static std::string ReadFile(const std::string& filename) { - // The handshake isn't finished, try to finish it. + FileReader reader(filename); + std::string ret = reader.GetString(); + if (ret.empty()) + throw Exception("Cannot read file " + filename); + return ret; + } - if(!Handshake(session, user)) + static std::string GetPrioStr(const std::string& profilename, ConfigTag* tag) + { + // 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: + static reference<Profile> Create(const std::string& profilename, ConfigTag* tag) { - 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) - { - user->SetError("Connection closed"); - CloseSession(session); - return -1; - } - else + std::string certstr = ReadFile(tag->getString("certfile", "cert.pem")); + std::string keystr = ReadFile(tag->getString("keyfile", "key.pem")); + + std::auto_ptr<DHParams> dh = DHParams::Import(ReadFile(tag->getString("dhfile", "dhparams.pem"))); + + std::string priostr = GetPrioStr(profilename, tag); + unsigned int mindh = tag->getInt("mindhbits", 1024); + std::string hashstr = tag->getString("hash", "md5"); + + // Load trusted CA and revocation list, if set + std::auto_ptr<X509CertList> ca; + std::auto_ptr<X509CRL> crl; + std::string filename = tag->getString("cafile"); + if (!filename.empty()) { - user->SetError(gnutls_strerror(ret)); - CloseSession(session); - return -1; + ca.reset(new X509CertList(ReadFile(filename))); + + filename = tag->getString("crlfile"); + if (!filename.empty()) + crl.reset(new X509CRL(ReadFile(filename))); } - } - else if (session->status == ISSL_CLOSING) - return -1; - return 0; - } +#ifdef INSPIRCD_GNUTLS_HAS_CORK + // If cork support is available outrecsize represents the (rough) max amount of data we give GnuTLS while corked + unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512); +#else + unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512, 16384); +#endif - int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) - { - issl_session* session = &sessions[user->GetFd()]; + const bool requestclientcert = tag->getBool("requestclientcert", true); - if (!session->sess) - { - CloseSession(session); - user->SetError("No SSL session"); - return -1; + return new Profile(profilename, certstr, keystr, dh, mindh, hashstr, priostr, ca, crl, outrecsize, requestclientcert); } - 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; + reference<GnuTLS::Profile> profile; +#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; - } - } + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); - 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()); - } + return 1; } } - void CloseSession(issl_session* session) + void VerifyCertificate() { - if (session->sess) - { - gnutls_bye(session->sess, GNUTLS_SHUT_WR); - gnutls_deinit(session->sess); - } - session->socket = NULL; - session->sess = NULL; - session->cert = NULL; - session->status = ISSL_NONE; - session->config = NULL; - } - - void VerifyCertificate(issl_session* session, StreamSocket* user) - { - 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 +794,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 +817,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 +835,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, profile->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 +873,490 @@ 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) { - if (starttls.enabled) - capHandler.HandleEvent(ev); + 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, const reference<GnuTLS::Profile>& sslprofile) + : SSLIOHook(hookprov) + , sess(NULL) + , status(ISSL_NONE) + , profile(sslprofile) +#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); + profile->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 < profile->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, profile->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)))); + } + + GnuTLS::Profile* GetProfile() { return profile; } + 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 refcountbase, public IOHookProvider +{ + reference<GnuTLS::Profile> profile; + + public: + GnuTLSIOHookProvider(Module* mod, reference<GnuTLS::Profile>& prof) + : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL) + , profile(prof) + { + 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, profile); + } + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + new GnuTLSIOHook(this, sock, GNUTLS_CLIENT, profile); + } +}; + +class ModuleSSLGnuTLS : public Module +{ + typedef std::vector<reference<GnuTLSIOHookProvider> > ProfileList; + + // First member of the class, gets constructed first and destructed last + GnuTLS::Init libinit; + RandGen randhandler; + 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 + { + reference<GnuTLS::Profile> profile(GnuTLS::Profile::Create(defname, tag)); + newprofiles.push_back(new GnuTLSIOHookProvider(this, profile)); + } + 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 (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<GnuTLS::Profile> profile; + try + { + profile = GnuTLS::Profile::Create(name, tag); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); + } + + newprofiles.push_back(new GnuTLSIOHookProvider(this, profile)); + } + + // New profiles are ok, begin using them + // Old profiles are deleted when their refcount drops to zero + 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 = &randhandler; + } + + 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 = &ServerInstance->HandleGenRandom; + } + + void OnCleanup(int target_type, void* item) CXX11_OVERRIDE + { + if(target_type == TYPE_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..f3b5adfd5 --- /dev/null +++ b/src/modules/extra/m_ssl_mbedtls.cpp @@ -0,0 +1,935 @@ +/* + * 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("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 : public refcountbase + { + /** 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; + + Profile(const std::string& profilename, const std::string& certstr, const std::string& keystr, + const std::string& dhstr, unsigned int mindh, const std::string& hashstr, + const std::string& ciphersuitestr, const std::string& curvestr, + const std::string& castr, const std::string& crlstr, + unsigned int recsize, + CTRDRBG& ctrdrbg, + int minver, int maxver, + bool requestclientcert + ) + : name(profilename) + , x509cred(certstr, keystr) + , ciphersuites(ciphersuitestr) + , curves(curvestr) + , serverctx(ctrdrbg, MBEDTLS_SSL_IS_SERVER) + , clientctx(ctrdrbg, MBEDTLS_SSL_IS_CLIENT) + , cacerts(castr, true) + , crl(crlstr) + , hash(hashstr) + , outrecsize(recsize) + { + serverctx.SetX509CertAndKey(x509cred); + clientctx.SetX509CertAndKey(x509cred); + clientctx.SetMinDHBits(mindh); + + if (!ciphersuites.empty()) + { + serverctx.SetCiphersuites(ciphersuites); + clientctx.SetCiphersuites(ciphersuites); + } + + if (!curves.empty()) + { + serverctx.SetCurves(curves); + clientctx.SetCurves(curves); + } + + serverctx.SetVersion(minver, maxver); + clientctx.SetVersion(minver, maxver); + + if (!dhstr.empty()) + { + dhparams.set(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 (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; + } + + public: + static reference<Profile> Create(const std::string& profilename, ConfigTag* tag, CTRDRBG& ctr_drbg) + { + const std::string certstr = ReadFile(tag->getString("certfile", "cert.pem")); + const std::string keystr = ReadFile(tag->getString("keyfile", "key.pem")); + const std::string dhstr = ReadFile(tag->getString("dhfile", "dhparams.pem")); + + const std::string ciphersuitestr = tag->getString("ciphersuites"); + const std::string curvestr = tag->getString("curves"); + unsigned int mindh = tag->getInt("mindhbits", 2048); + std::string hashstr = tag->getString("hash", "sha256"); + + std::string crlstr; + std::string castr = tag->getString("cafile"); + if (!castr.empty()) + { + castr = ReadFile(castr); + crlstr = tag->getString("crlfile"); + if (!crlstr.empty()) + crlstr = ReadFile(crlstr); + } + + int minver = tag->getInt("minver"); + int maxver = tag->getInt("maxver"); + unsigned int outrecsize = tag->getInt("outrecsize", 2048, 512, 16384); + const bool requestclientcert = tag->getBool("requestclientcert", true); + return new Profile(profilename, certstr, keystr, dhstr, mindh, hashstr, ciphersuitestr, curvestr, castr, crlstr, outrecsize, ctr_drbg, minver, maxver, requestclientcert); + } + + /** 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; + reference<mbedTLS::Profile> profile; + + 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 = profile->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, mbedTLS::Profile* sslprofile) + : SSLIOHook(hookprov) + , status(ISSL_NONE) + , profile(sslprofile) + { + mbedtls_ssl_init(&sess); + if (isserver) + profile->SetupServerSession(&sess); + else + profile->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, profile->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 IsHandshakeDone() const { return (status == ISSL_HANDSHAKEN); } +}; + +class mbedTLSIOHookProvider : public refcountbase, public IOHookProvider +{ + reference<mbedTLS::Profile> profile; + + public: + mbedTLSIOHookProvider(Module* mod, mbedTLS::Profile* prof) + : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL) + , profile(prof) + { + 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, profile); + } + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + new mbedTLSIOHook(this, sock, false, profile); + } +}; + +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 + { + reference<mbedTLS::Profile> profile(mbedTLS::Profile::Create(defname, tag, ctr_drbg)); + newprofiles.push_back(new mbedTLSIOHookProvider(this, profile)); + } + 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 (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<mbedTLS::Profile> profile; + try + { + profile = mbedTLS::Profile::Create(name, tag, ctr_drbg); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); + } + + newprofiles.push_back(new mbedTLSIOHookProvider(this, profile)); + } + + // New profiles are ok, begin using them + // Old profiles are deleted when their refcount drops to zero + 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(int target_type, void* item) CXX11_OVERRIDE + { + if (target_type != TYPE_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 aee7a5e34..68f75c87f 100644 --- a/src/modules/extra/m_ssl_openssl.cpp +++ b/src/modules/extra/m_ssl_openssl.cpp @@ -21,843 +21,974 @@ * 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("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 "ssl.h" #ifdef _WIN32 # pragma comment(lib, "ssleay32.lib") # pragma comment(lib, "libeay32.lib") -# undef MAX_DESCRIPTORS -# define MAX_DESCRIPTORS 10000 #endif -/* $ModDesc: Provides SSL support for clients */ - -/* $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 */ - +#if ((OPENSSL_VERSION_NUMBER >= 0x10000000L) && (!(defined(OPENSSL_NO_ECDH)))) +// OpenSSL 0.9.8 includes some ECC support, but it's unfinished. Enable only for 1.0.0 and later. +#define INSPIRCD_OPENSSL_ENABLE_ECDH +#endif -class ModuleSSLOpenSSL; +// BIO is opaque in OpenSSL 1.1 but the access API does not exist in 1.0 and older. +#if ((defined LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x10100000L)) +# 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; +#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_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)); + 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); + } - // 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); - } + ~Context() + { + SSL_CTX_free(ctx); + } -#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH - void SetupECDH(ConfigTag* tag) - { - std::string curvename = tag->getString("ecdhcurve", "prime256v1"); - if (curvename.empty()) - return; + bool SetDH(DHParams& dh) + { + ERR_clear_error(); + return (SSL_CTX_set_tmp_dh(ctx, dh.get()) >= 0); + } - int nid = OBJ_sn2nid(curvename.c_str()); - if (nid == 0) +#ifdef INSPIRCD_OPENSSL_ENABLE_ECDH + void SetECDH(const std::string& curvename) { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Unknown curve: \"%s\"", curvename.c_str()); - return; + int nid = OBJ_sn2nid(curvename.c_str()); + if (nid == 0) + throw Exception("Unknown curve: " + curvename); + + 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 - EC_KEY* eckey = EC_KEY_new_by_curve_name(nid); - if (!eckey) + bool SetCiphers(const std::string& ciphers) { - 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_cipher_list(ctx, ciphers.c_str()); } - ERR_clear_error(); - if (SSL_CTX_set_tmp_ecdh(ctx, eckey) < 0) + bool SetCerts(const std::string& filename) { - ServerInstance->Logs->Log("m_ssl_openssl", DEFAULT, "m_ssl_openssl.so: Couldn't set ECDH parameters"); - ERR_print_errors_cb(error_callback, this); + ERR_clear_error(); + return SSL_CTX_use_certificate_chain_file(ctx, filename.c_str()); } - EC_KEY_free(eckey); - } -#endif + bool SetPrivateKey(const std::string& filename) + { + ERR_clear_error(); + return SSL_CTX_use_PrivateKey_file(ctx, filename.c_str(), SSL_FILETYPE_PEM); + } -#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 SetCA(const std::string& filename) + { + ERR_clear_error(); + return SSL_CTX_load_verify_locations(ctx, filename.c_str(), 0); + } - if ((where & SSL_CB_HANDSHAKE_START) && (session.status == ISSL_OPEN)) + long GetDefaultContextOptions() const { - // 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); + return ctx_options; } - } - bool CheckRenego(StreamSocket* sock, issl_session* session) - { - if (session->status != ISSL_NONE) - return true; + long SetRawContextOptions(long setoptions, long clearoptions) + { + // Clear everything + SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx)); - 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 + // 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); + } - public: + void SetVerifyCert() + { + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, OnVerify); + } - ModuleSSLOpenSSL() : iohook(this, "ssl/openssl", SERVICE_IOHOOK) - { -#ifdef INSPIRCD_OPENSSL_ENABLE_RENEGO_DETECTION - opensslmod = this; -#endif - sessions = new issl_session[ServerInstance->SE->GetMaxFds()]; + SSL* CreateServerSession() + { + SSL* sess = SSL_new(ctx); + SSL_set_accept_state(sess); // Act as server + return sess; + } - /* Global SSL library initialization*/ - SSL_library_init(); - SSL_load_error_strings(); + SSL* CreateClientSession() + { + SSL* sess = SSL_new(ctx); + SSL_set_connect_state(sess); // Act as client + return sess; + } + }; - /* Build our SSL contexts: - * NOTE: OpenSSL makes us have two contexts, one for servers and one for clients. ICK. + class Profile : public refcountbase + { + /** Name of this profile */ - ctx = SSL_CTX_new( SSLv23_server_method() ); - clictx = SSL_CTX_new( SSLv23_client_method() ); + const std::string name; - 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); + /** DH parameters in use + */ + DHParams dh; - 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); + /** OpenSSL makes us have two contexts, one for servers and one for clients + */ + Context ctx; + Context clictx; - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); - SSL_CTX_set_session_cache_mode(clictx, SSL_SESS_CACHE_OFF); + /** Digest to use when generating fingerprints + */ + const EVP_MD* digest; - 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 + /** Last error, set by error_callback() + */ + std::string lasterr; - ctx_options = SSL_CTX_set_options(ctx, opts); - clictx_options = SSL_CTX_set_options(clictx, opts); - } + /** True if renegotiations are allowed, false if not + */ + const bool allowrenego; - 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); - } + /** Rough max size of records to send + */ + const unsigned int outrecsize; - void OnHookIO(StreamSocket* user, ListenSocket* lsb) - { - if (!user->GetIOHook() && lsb->bind_tag->getString("ssl") == "openssl") + static int error_callback(const char* str, size_t len, void* u) { - /* Hook the user with our module */ - user->AddIOHook(this); + Profile* profile = reinterpret_cast<Profile*>(u); + profile->lasterr = std::string(str, len - 1); + return 0; } - } - void OnRehash(User* user) - { - sslports.clear(); - - ConfigTag* Conf = ServerInstance->Config->ConfValue("openssl"); - -#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)) - { - SSL_CTX_set_info_callback(ctx, NULL); - SSL_CTX_set_info_callback(clictx, NULL); - } - 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"); + long clearoptions = tag->getInt(ctxname + "clearoptions"); +#ifdef SSL_OP_NO_COMPRESSION + if (!tag->getBool("compression", false)) // Disable compression by default + setoptions |= SSL_OP_NO_COMPRESSION; #endif - - if (Conf->getBool("showports", true)) - { - sslports = Conf->getString("advertisedports"); - if (!sslports.empty()) - return; - - for (size_t i = 0; i < ServerInstance->ports.size(); i++) + if (!tag->getBool("sslv3", false)) // Disable SSLv3 by default + setoptions |= SSL_OP_NO_SSLv3; + if (!tag->getBool("tlsv1", true)) + setoptions |= SSL_OP_NO_TLSv1; + + if (!setoptions && !clearoptions) + return; // Nothing to do + + 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", "dh.pem"))) + , ctx(SSL_CTX_new(SSLv23_server_method())) + , clictx(SSL_CTX_new(SSLv23_client_method())) + , allowrenego(tag->getBool("renegotiation")) // Disallow by default + , outrecsize(tag->getInt("outrecsize", 2048, 512, 16384)) + { + if ((!ctx.SetDH(dh)) || (!clictx.SetDH(dh))) + throw Exception("Couldn't set DH parameters"); + + std::string hash = tag->getString("hash", "md5"); + digest = EVP_get_digestbyname(hash.c_str()); + if (digest == NULL) + throw Exception("Unknown hash type " + hash); + + std::string ciphers = tag->getString("ciphers"); + if (!ciphers.empty()) { - ListenSocket* port = ServerInstance->ports[i]; - if (port->bind_tag->getString("ssl") != "openssl") - continue; - - 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()); - - if (port->bind_tag->getString("type", "clients") == "clients" && port->bind_addr != "127.0.0.1") + 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"); +#ifdef INSPIRCD_OPENSSL_ENABLE_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()); } + + 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; + reference<OpenSSL::Profile> profile; + + // 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 - } + status = ISSL_OPEN; - void On005Numeric(std::string &output) - { - if (!sslports.empty()) - output.append(" SSL=" + sslports); - } + SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE); - ~ModuleSSLOpenSSL() - { - SSL_CTX_free(ctx); - SSL_CTX_free(clictx); - delete[] sessions; - } - - 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, profile->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_get_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_get_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 (profile->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, const reference<OpenSSL::Profile>& sslprofile) + : SSLIOHook(hookprov) + , sess(session) + , status(ISSL_NONE) + , data_to_write(false) + , profile(sslprofile) { - 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(); + // Finish handshake if needed + int prepret = PrepareIO(user); + if (prepret <= 0) + return prepret; - issl_session* session = &sessions[fd]; + data_to_write = true; - if (!session->sess) - { - CloseSession(session); - return -1; - } - - session->data_to_write = true; - - if (session->status == ISSL_HANDSHAKING) - { - if (!Handshake(user, session)) - { - // Couldn't resume handshake. - if (session->status == ISSL_NONE) - return -1; - return 0; - } - } - - 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, profile->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 IsHandshakeDone() const { return (status == ISSL_OPEN); } +}; - if (ret < 0) +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 refcountbase, public IOHookProvider +{ + reference<OpenSSL::Profile> profile; + + public: + OpenSSLIOHookProvider(Module* mod, reference<OpenSSL::Profile>& prof) + : IOHookProvider(mod, "ssl/" + prof->GetName(), IOHookProvider::IOH_SSL) + , profile(prof) + { + 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(), profile); + } + + void OnConnect(StreamSocket* sock) CXX11_OVERRIDE + { + new OpenSSLIOHook(this, sock, profile->CreateClientSession(), profile); + } +}; + +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) - { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE); - session->status = ISSL_HANDSHAKING; - return true; - } - else if (err == SSL_ERROR_WANT_WRITE) + try { - ServerInstance->SE->ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE); - session->status = ISSL_HANDSHAKING; - return true; + reference<OpenSSL::Profile> profile(new OpenSSL::Profile(defname, tag)); + newprofiles.push_back(new OpenSSLIOHookProvider(this, profile)); } - 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 (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<OpenSSL::Profile> profile; + try + { + profile = new OpenSSL::Profile(name, tag); + } + catch (CoreException& ex) + { + throw ModuleException("Error while initializing SSL profile \"" + name + "\" at " + tag->getTagLocation() + " - " + ex.GetReason()); + } - return true; - } - else if (ret == 0) - { - CloseSession(session); + newprofiles.push_back(new OpenSSLIOHookProvider(this, profile)); } - 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 + SSL_library_init(); + SSL_load_error_strings(); +#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 "\"", SSLeay_version(SSLEAY_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(int target_type, void* item) CXX11_OVERRIDE + { + if (target_type == TYPE_USER) { - certinfo->fingerprint = irc::hex(md, n); - } + LocalUser* user = IS_LOCAL((User*)item); - if ((ASN1_UTCTIME_cmp_time_t(X509_get_notAfter(cert), ServerInstance->Time()) == -1) || (ASN1_UTCTIME_cmp_time_t(X509_get_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/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..2a38bd3a6 100644 --- a/src/modules/m_abbreviation.cpp +++ b/src/modules/m_abbreviation.cpp @@ -19,49 +19,37 @@ #include "inspircd.h" -/* $ModDesc: Provides the ability to abbreviate commands a-la BBC BASIC keywords. */ - 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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(420, "Ambiguous abbreviation and too many possible matches."); return MOD_RES_DENY; } @@ -79,16 +67,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(420, 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..95667e7ed 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,26 @@ 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; - 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 +81,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 +90,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() + ModuleAlias() + : botmode(this, "bot") { - ReadAliases(); - Implementation eventlist[] = { I_OnPreCommand, I_OnRehash, I_OnUserMessage }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual ~ModuleAlias() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides aliases of commands.", VF_VENDOR); } @@ -142,10 +129,8 @@ 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) + ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) CXX11_OVERRIDE { - std::multimap<irc::string, Alias>::iterator i, upperbound; - /* If theyre not registered yet, we dont want * to know. */ @@ -153,19 +138,16 @@ 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 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,29 +156,27 @@ 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) + void OnUserMessage(User *user, void *dest, int target_type, const std::string &text, char status, const CUList &exempt_list, MessageType msgtype) CXX11_OVERRIDE { - if (target_type != TYPE_CHANNEL) + if ((target_type != TYPE_CHANNEL) || (msgtype != 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; } @@ -207,87 +187,67 @@ class ModuleAlias : public Module // text is like "!moo cows bite me", we want "!moo" first irc::spacesepstream ss(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()); + scommand.erase(0, fprefix.size()); - std::multimap<irc::string, Alias>::iterator i = Aliases.find(fcommand); - - 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(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, 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 +258,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 +267,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]; @@ -329,32 +289,37 @@ class ModuleAlias : public Module 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); 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); i += 5; } + else if (!newline.compare(i, 12, "$requirement", 12)) + { + result.append(a->RequiredNick); + i += 11; + } else result.push_back(c); } @@ -367,23 +332,18 @@ class ModuleAlias : public Module std::string command, token; ss.GetToken(command); - while (ss.GetToken(token) && (pars.size() <= MAXPARAMETERS)) + while (ss.GetToken(token)) { pars.push_back(token); } - ServerInstance->Parser->CallHandler(command, pars, user); + ServerInstance->Parser.CallHandler(command, pars, user); } - virtual void OnRehash(User* user) - { - ReadAliases(); - } - - 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_OnUserMessage, PRIORITY_AFTER, linkmod); } }; diff --git a/src/modules/m_allowinvite.cpp b/src/modules/m_allowinvite.cpp index 08a5f542a..6a4db1822 100644 --- a/src/modules/m_allowinvite.cpp +++ b/src/modules/m_allowinvite.cpp @@ -19,8 +19,6 @@ #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: @@ -36,19 +34,12 @@ class ModuleAllowInvite : public Module { } - void init() - { - ServerInstance->Modules->AddService(ni); - Implementation eventlist[] = { I_OnUserPreInvite, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('A'); + tokens["EXTBAN"].push_back('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 +47,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 +60,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..73c0fa994 100644 --- a/src/modules/m_alltime.cpp +++ b/src/modules/m_alltime.cpp @@ -21,26 +21,21 @@ #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) { - 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; @@ -52,7 +47,6 @@ class CommandAlltime : public Command } }; - 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_auditorium.cpp b/src/modules/m_auditorium.cpp index 2a8edb9d4..cd257eff3 100644 --- a/src/modules/m_auditorium.cpp +++ b/src/modules/m_auditorium.cpp @@ -21,56 +21,33 @@ #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; } - - 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 : public Module { - private: + CheckExemption::EventProvider exemptionprov; AuditoriumMode aum; bool OpsVisible; bool OpsCanSee; bool OperCanSee; - public: - ModuleAuditorium() : aum(this) - { - } - - void init() - { - ServerInstance->Modules->AddService(aum); - - 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) { } - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* tag = ServerInstance->Config->ConfValue("auditorium"); OpsVisible = tag->getBool("opvisible"); @@ -78,7 +55,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 +66,8 @@ class ModuleAuditorium : public Module if (!memb->chan->IsModeSet(&aum)) return true; - ModResult res = ServerInstance->OnCheckExemption(memb->user, memb->chan, "auditorium-vis"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (memb->user, memb->chan, "auditorium-vis")); return res.check(OpsVisible && memb->getRank() >= OP_VALUE); } @@ -105,26 +83,24 @@ class ModuleAuditorium : public Module return true; // Can you see the list by permission? - ModResult res = ServerInstance->OnCheckExemption(issuer,memb->chan,"auditorium-see"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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 +109,45 @@ 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 OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE { BuildExcept(memb, excepts); } - void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) + void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE { BuildExcept(memb, excepts); } - 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 { BuildExcept(memb, excepts); } - void OnBuildNeighborList(User* source, UserChanList &include, std::map<User*,bool> &exception) + void OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) CXX11_OVERRIDE { - 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,17 +155,15 @@ 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; } }; diff --git a/src/modules/m_autoop.cpp b/src/modules/m_autoop.cpp index 0c0e8f579..8c7f300da 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 */ @@ -34,17 +32,13 @@ class AutoOpList : public ListModeBase 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) @@ -53,13 +47,12 @@ class AutoOpList : public ListModeBase 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(415, mid, InspIRCd::Format("Cannot find prefix mode '%s' for autoop", mid.c_str())); return MOD_RES_DENY; } else if (!mh) @@ -70,8 +63,7 @@ class AutoOpList : public ListModeBase return MOD_RES_DENY; if (mh->GetLevelRequired() > 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 1811f743d..b29b39747 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"] = "e"; } - 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[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[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 1b9e361bf..f98cbd420 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 ¶m, bool adding) { /* 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; } } @@ -195,8 +199,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); @@ -224,26 +227,19 @@ 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(int target_type, void* item) CXX11_OVERRIDE { if(target_type == TYPE_CHANNEL) { @@ -252,33 +248,21 @@ class ModuleBanRedirect : public Module 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) { @@ -322,19 +306,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() >= atoi(destlimit.c_str()))) { - 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; } @@ -345,14 +329,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..1698b0727 --- /dev/null +++ b/src/modules/m_bcrypt.cpp @@ -0,0 +1,987 @@ +/* + * 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/>. + */ + +/* + * Most of the code in this file is taken from + * http://openwall.com/crypt/crypt_blowfish-1.3.tar.gz + */ + +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer <solar at openwall.com> in 1998-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos <provos at citi.umich.edu>, and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres <dm at lcs.mit.edu>. For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include <string.h> + +#ifdef __i386__ +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_SCALE 1 +#else +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 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, 64, 64, 64, 64, 64, + 64, 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, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +static int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +static char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + /* pretend we don't support this hash type */ + return NULL; +} + +static char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} + +// Start inspircd-specific code + +#include "inspircd.h" +#include "modules/hash.h" + +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->getInt("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..7d97069f5 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,13 +35,13 @@ 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) { } }; @@ -55,46 +53,35 @@ class ModuleBlockAmsg : public Module 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", -1); std::string act = tag->getString("action"); - if(act == "notice") + if (act == "notice") action = IBLOCK_NOTICE; - else if(act == "noticeopers") + else if (act == "noticeopers") action = IBLOCK_NOTICEOPERS; - else if(act == "silent") + else if (act == "silent") action = IBLOCK_SILENT; - else if(act == "kill") + else if (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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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 != -1) && (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..c26d92caa 100644 --- a/src/modules/m_blockcaps.cpp +++ b/src/modules/m_blockcaps.cpp @@ -21,8 +21,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides support to block all-CAPS channel messages and notices */ +#include "modules/exemption.h" /** Handles the +B channel mode @@ -35,35 +34,25 @@ class BlockCaps : public SimpleChannelModeHandler class ModuleBlockCAPS : public Module { + CheckExemption::EventProvider exemptionprov; BlockCaps bc; - int percent; + unsigned int percent; unsigned int minlen; char capsmap[256]; -public: - - 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) { - 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (target_type == TYPE_CHANNEL) { @@ -71,33 +60,26 @@ public: return MOD_RES_PASSTHRU; Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"blockcaps"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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; - - for (std::string::iterator i = text.begin(); i != text.end(); i++) - { - /* Smart fix for suggestion from Jobe, ignore CTCP ACTION (part of /ME) */ - if (*actstr && *i == *actstr++ && act != -1) - { - act++; - continue; - } - else - act = -1; + std::string::size_type caps = 0; + unsigned int offset = 0; + // Ignore the beginning of the text if it's a CTCP ACTION (/me) + if (!text.compare(0, 8, "\1ACTION ", 8)) + offset = 8; + for (std::string::const_iterator i = text.begin() + offset; i != text.end(); ++i) caps += capsmap[(unsigned char)*i]; - } - if ( ((caps*100)/(int)text.length()) >= percent ) + + if (((caps * 100) / text.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 +87,18 @@ 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); + percent = tag->getInt("percent", 100, 1, 100); + minlen = tag->getInt("minlen", 1, 1, ServerInstance->Config->Limits.MaxLine); 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() - { } - 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..7c961d96a 100644 --- a/src/modules/m_blockcolor.cpp +++ b/src/modules/m_blockcolor.cpp @@ -22,8 +22,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel mode +c to block color */ +#include "modules/exemption.h" /** Handles the +c channel mode */ @@ -35,36 +34,33 @@ class BlockColor : public SimpleChannelModeHandler class ModuleBlockColor : public Module { + CheckExemption::EventProvider exemptionprov; BlockColor bc; public: - ModuleBlockColor() : bc(this) + ModuleBlockColor() + : exemptionprov(this) + , bc(this) { } - 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) - { - ServerInstance->AddExtBanChar('c'); - } - - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) { Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"blockcolor"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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++) { @@ -76,7 +72,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 +82,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..e0236bc17 100644 --- a/src/modules/m_botmode.cpp +++ b/src/modules/m_botmode.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides user mode +B to mark the user as a bot */ - /** Handles user mode +B */ class BotMode : public SimpleUserModeHandler @@ -31,40 +29,28 @@ class BotMode : public SimpleUserModeHandler BotMode(Module* Creator) : SimpleUserModeHandler(Creator, "bot", 'B') { } }; -class ModuleBotMode : public Module +class ModuleBotMode : public Module, public Whois::EventListener { BotMode bm; public: ModuleBotMode() - : bm(this) + : Whois::EventListener(this) + , bm(this) { } - void init() - { - 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(335, "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..a00da6a6f 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,18 +69,26 @@ 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 { - 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) { + if (format == FORMAT_NETWORK) + return; + void* old = get_raw(container); if (old) this->free(old); @@ -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) { @@ -112,21 +130,18 @@ struct CallerIDExtInfo : public ExtensionItem 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; } @@ -140,6 +155,28 @@ public: 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 +185,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, int index) { - 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. @@ -193,54 +209,58 @@ public: */ CmdResult Handle(const std::vector<std::string> ¶meters, User* user) { - 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) { - 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 +268,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 +280,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 +293,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,43 +303,35 @@ 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; @@ -339,17 +351,13 @@ 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(); @@ -360,53 +368,39 @@ public: { } - 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["CALLERID"] = "g"; } - ModResult PreText(User* user, User* dest, std::string& text) + ModResult OnUserPreMessage(User* user, void* voiddest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { - if (!dest->IsModeSet('g') || (user == dest)) + if (!IS_LOCAL(user) || target_type != TYPE_USER) return MOD_RES_PASSTHRU; - if (operoverride && IS_OPER(user)) + User* dest = static_cast<User*>(voiddest); + 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 (operoverride && user->IsOper()) + 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->dhost.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 +408,31 @@ 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"); 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 6b4387fdd..868294fe4 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,414 @@ #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 */ +namespace Cap +{ + class ManagerImpl; +} -/* -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 +static Cap::ManagerImpl* managerimpl; + +class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener { - public: - LocalIntExt reghold; - CommandCAP (Module* mod) : Command(mod, "CAP", 1), - reghold("CAP_REGHOLD", mod) + /** 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; + + CapModData* capmoddata = new CapModData; + cd.add(this, capmoddata); - CapEvent Data(creator, user, CapEvent::CAPEVENT_REQ); + 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; - // tokenize the input into a nice list of requested caps - std::string cap_; - irc::spacesepstream cap_stream(parameters[1]); + 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; - reghold.set(user, 1); - Data.Send(); + 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; - std::string Result; - if (Data.wanted.size() > 0) - Result = irc::stringjoiner(" ", Data.wanted, 0, Data.wanted.size() - 1).GetJoined(); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unregistering cap %s", cap->GetName().c_str()); - user->WriteServ("CAP %s %s :%s", user->nick.c_str(), subcommand.c_str(), Result.c_str()); + // 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)); + + // 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.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) { - CommandCAP cmd; +} + +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 CommandCap : public SplitCommand +{ + Events::ModuleEventProvider evprov; + Cap::ManagerImpl manager; + + static void DisplayResult(LocalUser* user, std::string& result) + { + if (*result.rbegin() == ' ') + result.erase(result.end()-1); + user->WriteCommand("CAP", result); + } + public: - ModuleCAP() - : cmd(this) + LocalIntExt holdext; + + CommandCap(Module* mod) + : SplitCommand(mod, "CAP", 1) + , evprov(mod, "event/cap") + , manager(mod, evprov) + , holdext("cap_hold", ExtensionItem::EXT_USER, mod) { + works_before_reg = true; } - void init() + CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(cmd.reghold); + 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; + + std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :"); + result.append(parameters[1]); + user->WriteCommand("CAP", result); + } + 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); - Implementation eventlist[] = { I_OnCheckReady }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); + std::string result = subcommand + " :"; + // 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, result); + } + else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY)) + { + std::string result = "ACK :"; + manager.HandleClear(user, result); + DisplayResult(user, result); + } + else + { + user->WriteNumeric(ERR_INVALIDCAPSUBCOMMAND, subcommand, "Invalid CAP subcommand"); + return CMD_FAILURE; + } + + return CMD_SUCCESS; } +}; - ModResult OnCheckReady(LocalUser* user) - { - /* Users in CAP state get held until CAP END */ - if (cmd.reghold.get(user)) - return MOD_RES_DENY; +class ModuleCap : public Module +{ + CommandCap cmd; - return MOD_RES_PASSTHRU; + public: + ModuleCap() + : cmd(this) + { } - ~ModuleCAP() + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { + return (cmd.holdext.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { - return Version("Client CAP extension support", VF_VENDOR); + return Version("Provides support for CAP capability negotiation", VF_VENDOR); } }; -MODULE_INIT(ModuleCAP) - +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..42cff2850 100644 --- a/src/modules/m_cban.cpp +++ b/src/modules/m_cban.cpp @@ -23,22 +23,17 @@ #include "inspircd.h" #include "xline.h" -/* $ModDesc: Gives /cban, aka C:lines. Think Q:lines, for channels. */ - /** 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) { } @@ -50,20 +45,12 @@ public: bool Matches(const std::string &s) { - 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() { - return matchtext.c_str(); + return matchtext; } }; @@ -95,7 +82,6 @@ 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) @@ -111,14 +97,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,14 +117,14 @@ 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; } } @@ -164,51 +150,46 @@ class ModuleCBan : public Module { } - 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(384, cname, InspIRCd::Format("Cannot join channel, CBANed (%s)", 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..56639b298 100644 --- a/src/modules/m_censor.cpp +++ b/src/modules/m_censor.cpp @@ -20,15 +20,10 @@ */ -/* $ModDesc: Provides user and channel +G mode */ - -#define _CRT_SECURE_NO_DEPRECATE -#define _SCL_SECURE_NO_DEPRECATE - #include "inspircd.h" -#include <iostream> +#include "modules/exemption.h" -typedef std::map<irc::string,irc::string> censor_t; +typedef insp::flat_map<irc::string, irc::string> censor_t; /** Handles usermode +G */ @@ -48,31 +43,21 @@ class CensorChannel : public SimpleChannelModeHandler class ModuleCensor : public Module { + CheckExemption::EventProvider exemptionprov; censor_t censors; CensorUser cu; CensorChannel 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) + , cc(this) { } // 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; @@ -80,12 +65,13 @@ class ModuleCensor : public Module bool active = false; if (target_type == TYPE_USER) - active = ((User*)dest)->IsModeSet('G'); + active = ((User*)dest)->IsModeSet(cu); else if (target_type == TYPE_CHANNEL) { - active = ((Channel*)dest)->IsModeSet('G'); Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"censor"); + active = c->IsModeSet(cc); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (user, c, "censor")); if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; @@ -101,23 +87,18 @@ class ModuleCensor : public Module { 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()); + user->WriteNumeric(ERR_WORDFILTERED, ((target_type == TYPE_CHANNEL) ? ((Channel*)dest)->name : ((User*)dest)->nick), index->first.c_str(), "Your message contained a censored word, and was blocked"); return MOD_RES_DENY; } - SearchAndReplace(text2, index->first, index->second); + stdalgo::string::replace_all(text2, index->first, 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 @@ -136,7 +117,7 @@ class ModuleCensor : public Module } } - 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 9e1a546d6..d8dbe6da7 100644 --- a/src/modules/m_cgiirc.cpp +++ b/src/modules/m_cgiirc.cpp @@ -25,11 +25,17 @@ #include "inspircd.h" #include "xline.h" +#include "modules/dns.h" -/* $ModDesc: Change user's hosts connecting from known CGI:IRC hosts */ - -enum CGItype { PASS, IDENT, PASSFIRST, IDENTFIRST, WEBIRC }; +enum CGItype { IDENT, WEBIRC }; +// We need this method up here so that it can be accessed from anywhere +static void ChangeIP(User* user, const std::string& newip) +{ + ServerInstance->Users->RemoveCloneCounts(user); + user->SetClientIP(newip.c_str()); + ServerInstance->Users->AddClone(user); +} /** Holds a CGI site's details */ @@ -50,34 +56,34 @@ 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: 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) + 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 client hostname ip"; + this->syntax = "password gateway hostname ip"; } - CmdResult Handle(const std::vector<std::string> ¶meters, User *user) + CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) { if(user->registered == REG_ALL) return CMD_FAILURE; @@ -85,7 +91,7 @@ class CommandWebirc : public Command irc::sockets::sockaddrs ipaddr; if (!irc::sockets::aptosa(parameters[3], 0, ipaddr)) { - IS_LOCAL(user)->CommandFloodPenalty += 5000; + 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; } @@ -96,29 +102,30 @@ class CommandWebirc : public Command { if(iter->type == WEBIRC && parameters[0] == iter->password) { + gateway.set(user, parameters[1]); realhost.set(user, user->host); realip.set(user, user->GetIPString()); - bool host_ok = (parameters[2].length() < 64) && (parameters[2].find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") == std::string::npos); + // Check if we're happy with the provided hostname. If it's problematic then make sure we won't set a host later, just the IP + bool host_ok = (parameters[2].length() <= ServerInstance->Config->Limits.MaxHost) && (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()); + ServerInstance->SNO->WriteGlobalSno('w', "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()); - // Check if we're happy with the provided hostname. If it's problematic then make sure we won't set a host later, just the IP - if (host_ok) - webirc_hostname.set(user, parameters[2]); - else - webirc_hostname.unset(user); + // Where the magic happens - change their IP + ChangeIP(user, parameters[3]); + // And follow this up by changing their host + user->host = user->dhost = newhost; + user->InvalidateCache(); - 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()); + user->CommandFloodPenalty += 5000; + ServerInstance->SNO->WriteGlobalSno('w', "Connecting user %s tried to use WEBIRC, but didn't match any configured webirc blocks.", user->GetFullRealHost().c_str()); return CMD_FAILURE; } }; @@ -126,39 +133,44 @@ class CommandWebirc : public Command /** Resolver for CGI:IRC hostnames encoded in ident/GECOS */ -class CGIResolver : public Resolver +class CGIResolver : public DNS::Request { - 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) + CGIResolver(DNS::Manager* mgr, Module* me, bool NotifyOpers, const std::string &source, LocalUser* u, LocalIntExt& ext) + : DNS::Request(mgr, me, source, DNS::QUERY_PTR) + , theiruid(u->uuid) + , waiting(ext) + , notify(NotifyOpers) { } - 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 */ User* them = ServerInstance->FindUUID(theiruid); if ((them) && (!them->quitting)) { - 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()); + LocalUser* lu = IS_LOCAL(them); + if (!lu) + return; - if (result.length() > 64) + const DNS::ResourceRecord &ans_record = r->answers[0]; + if (ans_record.rdata.empty() || ans_record.rdata.length() > ServerInstance->Config->Limits.MaxHost) return; - them->host = result; - them->dhost = result; + + if (notify) + ServerInstance->SNO->WriteGlobalSno('w', "Connecting user %s detected as using CGI:IRC (%s), changing real host to %s", them->nick.c_str(), them->host.c_str(), ans_record.rdata.c_str()); + + them->host = them->dhost = ans_record.rdata; them->InvalidateCache(); - them->CheckLines(true); + lu->CheckLines(true); } } - virtual void OnError(ResolverError e, const std::string &errormessage) + void OnError(const DNS::Query *r) CXX11_OVERRIDE { if (!notify) return; @@ -166,11 +178,11 @@ class CGIResolver : public Resolver User* them = ServerInstance->FindUUID(theiruid); if ((them) && (!them->quitting)) { - 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()); + ServerInstance->SNO->WriteToSnoMask('w', "Connecting user %s detected as using CGI:IRC (%s), but their host can't be resolved!", them->nick.c_str(), them->host.c_str()); } } - virtual ~CGIResolver() + ~CGIResolver() { User* them = ServerInstance->FindUUID(theiruid); if (!them) @@ -183,8 +195,9 @@ class CGIResolver : public Resolver class ModuleCgiIRC : public Module { - CommandWebirc cmd; + CommandWebIRC cmd; LocalIntExt waiting; + dynamic_reference<DNS::Manager> DNS; static void RecheckClass(LocalUser* user) { @@ -193,15 +206,7 @@ class ModuleCgiIRC : public Module user->CheckClass(); } - static void ChangeIP(LocalUser* user, const std::string& newip) - { - ServerInstance->Users->RemoveCloneCounts(user); - user->SetClientIP(newip.c_str()); - ServerInstance->Users->AddLocalClone(user); - ServerInstance->Users->AddGlobalClone(user); - } - - void HandleIdentOrPass(LocalUser* user, const std::string& newip, bool was_pass) + void HandleIdent(LocalUser* user, const std::string& newip) { cmd.realhost.set(user, user->host); cmd.realip.set(user, user->GetIPString()); @@ -209,40 +214,42 @@ class ModuleCgiIRC : public Module user->host = user->dhost = user->GetIPString(); 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) + if (user->quitting || !DNS || !user->MyClass->resolvehostnames) return; + CGIResolver* r = new CGIResolver(*this->DNS, this, cmd.notify, newip, user, waiting); try { - 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); + this->DNS->Process(r); } - catch (...) + catch (DNS::Exception &ex) { + int count = waiting.get(user); + if (count) + waiting.set(user, count - 1); + delete r; 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()); + ServerInstance->SNO->WriteToSnoMask('w', "Connecting user %s detected as using CGI:IRC (%s), but I could not resolve their hostname; %s", user->nick.c_str(), user->host.c_str(), ex.GetReason().c_str()); } } public: - ModuleCgiIRC() : cmd(this), waiting("cgiirc-delay", this) + ModuleCgiIRC() + : cmd(this) + , waiting("cgiirc-delay", ExtensionItem::EXT_USER, this) + , DNS(this, "DNS") { } - 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(); @@ -261,23 +268,19 @@ public: { if (type == "webirc" && password.empty()) { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc: Missing password in config: %s", hostmask.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Missing password in config: %s", hostmask.c_str()); } else { CGItype cgitype; - if (type == "pass") - cgitype = PASS; - else if (type == "ident") + 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()); + cgitype = IDENT; + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Invalid <cgihost:type> value in config: %s, setting it to \"ident\"", type.c_str()); } cmd.Hosts.push_back(CGIhost(hostmask, cgitype, password)); @@ -285,27 +288,20 @@ public: } else { - ServerInstance->Logs->Log("CONFIG",DEFAULT, "m_cgiirc.so: Invalid <cgihost:mask> value in config: %s", hostmask.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Invalid <cgihost:mask> value in config: %s", hostmask.c_str()); continue; } } } - ModResult OnCheckReady(LocalUser *user) + ModResult OnCheckReady(LocalUser *user) CXX11_OVERRIDE { if (waiting.get(user)) return MOD_RES_DENY; - std::string *webirc_ip = cmd.webirc_ip.get(user); - if (!webirc_ip) + if (!cmd.realip.get(user)) return MOD_RES_PASSTHRU; - ChangeIP(user, *webirc_ip); - - std::string* webirc_hostname = cmd.webirc_hostname.get(user); - user->host = user->dhost = (webirc_hostname ? *webirc_hostname : user->GetIPString()); - user->InvalidateCache(); - RecheckClass(user); if (user->quitting) return MOD_RES_DENY; @@ -314,41 +310,39 @@ public: if (user->quitting) return MOD_RES_DENY; - cmd.webirc_hostname.unset(user); - cmd.webirc_ip.unset(user); - return MOD_RES_PASSTHRU; } - ModResult OnUserRegister(LocalUser* user) + ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE + { + // 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; + + // 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; + + // 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) 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) + 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 @@ -359,18 +353,6 @@ public: return MOD_RES_PASSTHRU; } - bool CheckPass(LocalUser* user) - { - if(IsValidHost(user->password)) - { - HandleIdentOrPass(user, user->password, true); - user->password.clear(); - return true; - } - - return false; - } - bool CheckIdent(LocalUser* user) { const char* ident; @@ -391,37 +373,15 @@ public: std::string newipstr(inet_ntoa(newip)); user->ident = "~cgiirc"; - HandleIdentOrPass(user, newipstr, false); + HandleIdent(user, newipstr); return true; } - bool IsValidHost(const std::string &host) - { - 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())) ) - - continue; - else - return false; - } - - return true; - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Change user's hosts connecting from known CGI:IRC hosts",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..b642cb933 100644 --- a/src/modules/m_chanfilter.cpp +++ b/src/modules/m_chanfilter.cpp @@ -23,13 +23,9 @@ */ -/* $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" /** Handles channel mode +g */ @@ -38,82 +34,77 @@ class ChanFilter : public ListModeBase public: 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) { - if ((word.length() > 35) || (word.empty())) + if (word.length() > 35) { - 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(935, chan->name, word, "%word is too long for censor list"); return false; } return true; } - virtual bool TellListTooLong(User* user, Channel* chan, std::string &word) + void TellListTooLong(User* user, Channel* chan, std::string &word) { - 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(939, 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) { - 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(937, 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) { - user->WriteNumeric(938, "%s %s :No such spamfilter word is set",user->nick.c_str(), chan->name.c_str()); + user->WriteNumeric(938, chan->name, "No such spamfilter word is set"); } }; class ModuleChanFilter : public Module { + CheckExemption::EventProvider exemptionprov; ChanFilter cf; bool hidemask; public: ModuleChanFilter() - : cf(this) + : exemptionprov(this) + , cf(this) { } - void init() - { - 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) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { hidemask = ServerInstance->Config->ConfValue("chanfilter")->getBool("hidemask"); cf.DoRehash(); } - virtual ModResult ProcessMessages(User* user,Channel* chan,std::string &text) + ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { - ModResult res = ServerInstance->OnCheckExemption(user,chan,"filter"); + if (target_type != TYPE_CHANNEL) + return MOD_RES_PASSTHRU; + + Channel* chan = static_cast<Channel*>(dest); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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 (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, i->mask, "Cannot send to channel (your message contained a censored word)"); return MOD_RES_DENY; } } @@ -122,33 +113,10 @@ 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) - { - 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); - } - - virtual void OnSyncChannel(Channel* chan, Module* proto, void* opaque) - { - cf.DoSyncChannel(chan, proto, opaque); - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel-specific censor lists (like mode +G but varies from channel to channel)", VF_VENDOR); } - - virtual ~ModuleChanFilter() - { - } }; MODULE_INIT(ModuleChanFilter) diff --git a/src/modules/m_chanhistory.cpp b/src/modules/m_chanhistory.cpp index e48e67fe5..a0929a0d0 100644 --- a/src/modules/m_chanhistory.cpp +++ b/src/modules/m_chanhistory.cpp @@ -19,8 +19,6 @@ #include "inspircd.h" -/* $ModDesc: Provides channel history for a given number of lines */ - struct HistoryItem { time_t ts; @@ -32,10 +30,13 @@ 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,110 +53,98 @@ 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) { - if (adding) + std::string::size_type colon = parameter.find(':'); + if (colon == std::string::npos) + return MODEACTION_DENY; + + std::string duration(parameter, colon+1); + if ((IS_LOCAL(source)) && ((duration.length() > 10) || (!IsValidDuration(duration)))) + return MODEACTION_DENY; + + unsigned int len = ConvToInt(parameter.substr(0, colon)); + int time = InspIRCd::Duration(duration); + if (len == 0 || time < 0) + return MODEACTION_DENY; + if (len > maxlines && IS_LOCAL(source)) + return MODEACTION_DENY; + if (len > maxlines) + len = maxlines; + + HistoryList* history = ext.get(channel); + if (history) { - 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)); + // 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; - } - else - { - ext.set(channel, new HistoryList(len, time)); - } - channel->SetModeParam('H', parameter); + 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) - { - } - - void init() + ModuleChanHistory() : m(this), botmode(this, "bot") { - 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); 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 OnUserMessage(User* user, void* dest, int target_type, const std::string &text, char status, const CUList&, MessageType msgtype) CXX11_OVERRIDE { - if (target_type == TYPE_CHANNEL && status == 0) + if ((target_type == TYPE_CHANNEL) && (status == 0) && (msgtype == MSG_PRIVMSG)) { Channel* c = (Channel*)dest; 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)); + const std::string line = ":" + user->GetFullHost() + " PRIVMSG " + c->name + " :" + text; + list->lines.push_back(HistoryItem(line)); 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)) return; + if (memb->user->IsModeSet(botmode) && !dobots) + return; + HistoryList* list = m.ext.get(memb->chan); if (!list) return; @@ -165,8 +154,7 @@ class ModuleChanHistory : public Module if (sendnotice) { - 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); + memb->user->WriteNotice("Replaying up to " + ConvToStr(list->maxlen) + " lines of pre-join history spanning up to " + ConvToStr(list->maxtime) + " seconds"); } for(std::deque<HistoryItem>::iterator i = list->lines.begin(); i != list->lines.end(); ++i) @@ -176,7 +164,7 @@ class ModuleChanHistory : public Module } } - 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..f618a539c 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,44 @@ 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); + c->WriteChannelWithServ(ServerInstance->Config->ServerName, "PRIVMSG %s :%s", c->name.c_str(), snotice.c_str()); + 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..7513cb33a 100644 --- a/src/modules/m_channames.cpp +++ b/src/modules/m_channames.cpp @@ -19,83 +19,78 @@ #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 HandlerBase1<bool, const std::string&> { public: - NewIsChannelHandler() { } - virtual ~NewIsChannelHandler() { } - virtual bool Call(const char*, size_t); + 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; + caller1<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); } 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 +99,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 +129,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..2cb45ad43 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,139 @@ class CommandCheck : public Command return ret; } - void dumpExt(User* user, const std::string& checkstr, Extensible* ext) - { - 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->fullname); + 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"); + for (OperInfo::PrivSet::const_iterator i = oper->AllowedOperCommands.begin(); i != oper->AllowedOperCommands.end(); ++i) + opcmdlist.Add(*i); + opcmdlist.Flush(); + CheckContext::List privlist(context, "permissions"); + for (OperInfo::PrivSet::const_iterator i = oper->AllowedPrivs.begin(); i != oper->AllowedPrivs.end(); ++i) + privlist.Add(*i); + 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->dhost.c_str(), i->first->fullname.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 +280,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)) { /* 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->fullname); } /* 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->fullname); } } - 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) { - 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..ad8353cbd 100644 --- a/src/modules/m_chghost.cpp +++ b/src/modules/m_chghost.cpp @@ -21,13 +21,10 @@ #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) @@ -35,24 +32,22 @@ class CommandChghost : public Command 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) { - 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]) { - user->WriteServ("NOTICE "+user->nick+" :*** CHGHOST: Invalid characters in hostname"); + user->WriteNotice("*** CHGHOST: Invalid characters in hostname"); return CMD_FAILURE; } } @@ -60,15 +55,15 @@ 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); @@ -80,10 +75,7 @@ class CommandChghost : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -92,20 +84,13 @@ class ModuleChgHost : public Module { CommandChghost cmd; char hostmap[256]; + public: ModuleChgHost() : cmd(this, hostmap) { } - void init() - { - 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"); @@ -114,15 +99,10 @@ class ModuleChgHost : public Module hostmap[(unsigned char)*n] = 1; } - ~ModuleChgHost() - { - } - - 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..8ba5b4a5b 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,7 +32,7 @@ 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) @@ -43,27 +41,27 @@ class CommandChgident : public Command 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()); } @@ -72,14 +70,10 @@ class CommandChgident : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..2582ef652 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 @@ -32,7 +30,7 @@ class CommandChgname : public Command allow_empty_last_param = false; flags_needed = 'o'; syntax = "<nick> <newname>"; - TRANSLATE3(TR_NICK, TR_TEXT, TR_END); + TRANSLATE2(TR_NICK, TR_TEXT); } CmdResult Handle(const std::vector<std::string> ¶meters, User *user) @@ -41,25 +39,25 @@ class CommandChgname : public Command 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: GECOS must be specified"); return CMD_FAILURE; } if (parameters[1].length() > ServerInstance->Config->Limits.MaxGecos) { - user->WriteServ("NOTICE %s :*** CHGNAME: GECOS too long", user->nick.c_str()); + user->WriteNotice("*** CHGNAME: GECOS too long"); return CMD_FAILURE; } if (IS_LOCAL(dest)) { - dest->ChangeName(parameters[1].c_str()); + dest->ChangeName(parameters[1]); 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()); } @@ -68,14 +66,10 @@ class CommandChgname : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..1b4b8724a --- /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(const std::vector<std::string>& parameters, User* user) + { + 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. + std::vector<std::string> 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->host); + 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..7e9c78b31 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 */ @@ -49,14 +43,13 @@ class CloakUser : public ModeHandler { public: 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) + ext("cloaked_host", ExtensionItem::EXT_USER, source), debounce_ts(0), debounce_count(0) { } @@ -70,7 +63,7 @@ class CloakUser : public ModeHandler */ if (!user) { - dest->SetMode('x',adding); + dest->SetMode(this, adding); return MODEACTION_ALLOW; } @@ -87,7 +80,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 */ @@ -110,8 +103,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 +115,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->SetMode(this, false); user->ChangeDisplayedHost(user->host.c_str()); return MODEACTION_ALLOW; } } - }; class CommandCloak : public Command @@ -151,26 +143,13 @@ 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() - { - 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, * or the last domain part if only two are available. Failing that it just returns what it was given. * @@ -182,7 +161,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 +171,7 @@ class ModuleCloaking : public Module splitdot = x; dots++; } - if (dots >= 3) + if (dots >= domainparts) break; } @@ -217,7 +196,7 @@ 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); + std::string rv = Hash->GenerateRaw(input).substr(0,len); for(int i=0; i < len; i++) { // this discards 3 bits per byte. We have an @@ -228,63 +207,6 @@ 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; @@ -335,24 +257,22 @@ class ModuleCloaking : public Module } else { - char buf[50]; if (ip.sa.sa_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) @@ -363,15 +283,14 @@ class ModuleCloaking : public Module /* Check if they have a cloaked host, but are not using it */ if (cloak && *cloak != user->dhost) { - 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 +298,32 @@ 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)) { - u->SetMode('x', false); - u->WriteServ("MODE %s -x", u->nick.c_str()); + u->SetMode(cu, false); + u->WriteCommand("MODE", "-" + ConvToStr(cu.GetModeChar())); } } - ~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 +332,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 (modestr == "half") + { mode = MODE_HALF_CLOAK; + domainparts = tag->getInt("domainparts", 3, 1, 10); + } else if (modestr == "full") mode = MODE_OPAQUE; else - throw ModuleException("Bad value for <cloak:mode>; must be one of compat-host, compat-ip, half, full"); + throw ModuleException("Bad value for <cloak:mode>; must be half or 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"; - } - - 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 +363,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,7 +378,7 @@ 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) @@ -561,7 +399,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..b3e695bfd 100644 --- a/src/modules/m_clones.cpp +++ b/src/modules/m_clones.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides the /CLONES command to retrieve information on clones. */ - /** Handle /CLONES */ class CommandClones : public Command @@ -36,7 +34,7 @@ class CommandClones : public Command CmdResult Handle (const std::vector<std::string> ¶meters, User *user) { - std::string clonesstr = "304 " + user->nick + " :CLONES"; + std::string clonesstr = "CLONES "; unsigned long limit = atoi(parameters[0].c_str()); @@ -47,47 +45,35 @@ class CommandClones : public Command * :server.name 304 target :CLONES END */ - user->WriteServ(clonesstr + " START"); + user->WriteNumeric(304, clonesstr + "START"); /* 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) + user->WriteNumeric(304, clonesstr + ConvToStr(counts.global) + " " + i->first.str()); } - user->WriteServ(clonesstr + " END"); + user->WriteNumeric(304, clonesstr + "END"); return CMD_SUCCESS; } }; - class ModuleClones : public Module { - private: CommandClones cmd; public: ModuleClones() : cmd(this) { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleClones() - { - } - - 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..3f0eedaaf 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) { 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..e04217e71 100644 --- a/src/modules/m_commonchans.cpp +++ b/src/modules/m_commonchans.cpp @@ -19,8 +19,6 @@ #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 @@ -37,41 +35,24 @@ class ModulePrivacyMode : public Module { } - void init() - { - ServerInstance->Modules->AddService(pm); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModulePrivacyMode() - { - } - - 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (target_type == TYPE_USER) { User* t = (User*)dest; - if (!IS_OPER(user) && (t->IsModeSet('c')) && (!ServerInstance->ULine(user->server)) && !user->SharesChannelWith(t)) + 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..acac7640a 100644 --- a/src/modules/m_conn_umodes.cpp +++ b/src/modules/m_conn_umodes.cpp @@ -22,38 +22,25 @@ #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"); @@ -62,29 +49,17 @@ class ModuleModesOnConnect : public Module std::string buf; std::stringstream ss(ThisModes); - std::vector<std::string> tokens; - - // split ThisUserModes into modes and mode params - while (ss >> buf) - tokens.push_back(buf); - std::vector<std::string> 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 >> 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..87b6b51f2 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,31 @@ 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()); 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, std::vector<std::string> ¶meters, LocalUser* user, bool validated, const std::string &original_line) CXX11_OVERRIDE { if (command == "PONG") { @@ -90,20 +80,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..e0f9717c4 100644 --- a/src/modules/m_connectban.cpp +++ b/src/modules/m_connectban.cpp @@ -20,61 +20,39 @@ #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->getInt("ipv4cidr", 32, 1, 32); + ipv6_cidr = tag->getInt("ipv6cidr", 128, 1, 128); + threshold = tag->getInt("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; switch (u->client_sa.sa.sa_family) { @@ -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..1d27e3990 100644 --- a/src/modules/m_connflood.cpp +++ b/src/modules/m_connflood.cpp @@ -21,11 +21,8 @@ #include "inspircd.h" -/* $ModDesc: Connection throttle */ - class ModuleConnFlood : public Module { -private: int seconds, timeout, boot_wait; unsigned int conns; unsigned int maxconns; @@ -39,26 +36,19 @@ 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"); + seconds = tag->getDuration("period", tag->getInt("seconds")); maxconns = tag->getInt("maxconns"); - timeout = tag->getInt("timeout"); + timeout = tag->getDuration("timeout"); quitmsg = tag->getString("quitmsg"); /* seconds to wait when the server just booted */ @@ -67,7 +57,7 @@ public: first = ServerInstance->Time(); } - virtual ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { if (user->exempt) return MOD_RES_PASSTHRU; @@ -114,12 +104,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..f6f9a84f6 100644 --- a/src/modules/m_customprefix.cpp +++ b/src/modules/m_customprefix.cpp @@ -19,89 +19,37 @@ #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) + : PrefixMode(parent, Tag->getString("name"), 0, Tag->getInt("rank")) + , 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); + levelrequired = tag->getInt("ranktoset", prefixrank); 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) - { - } - - ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) - { - return MODEACTION_ALLOW; - } }; 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) @@ -110,7 +58,7 @@ class ModuleCustomPrefix : public Module tags.first++; CustomPrefixMode* mh = new CustomPrefixMode(this, tag); modes.push_back(mh); - if (mh->rank <= 0) + if (mh->GetPrefixRank() == 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()); @@ -120,18 +68,17 @@ class ModuleCustomPrefix : public Module } 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..30c0aa4f2 100644 --- a/src/modules/m_customtitle.cpp +++ b/src/modules/m_customtitle.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides the TITLE command which allows setting of CUSTOM WHOIS TITLE line */ - /** Handle /TITLE */ class CommandTitle : public Command @@ -30,32 +28,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) { - 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->host; + 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 +48,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(320, 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..202cb123f 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(const std::vector<std::string> ¶meters, LocalUser* user) { 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(ERR_NOSUCHCHANNEL, parameters[0], "No such channel"); 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..edf9d012f 100644 --- a/src/modules/m_dccallow.cpp +++ b/src/modules/m_dccallow.cpp @@ -25,7 +25,28 @@ #include "inspircd.h" -/* $ModDesc: Provides support for the /DCCALLOW command */ +static const char* const helptext[] = +{ + "DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]", + "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 { @@ -53,13 +74,17 @@ 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 */ @@ -95,21 +120,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(998, "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 +143,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(995, user->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", target->nick.c_str())); break; } } @@ -128,22 +153,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(996, 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(996, user->nick, "Too many nicks on DCCALLOW list"); return CMD_FAILURE; } @@ -151,7 +176,7 @@ 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(996, user->nick, InspIRCd::Format("%s is already on your DCCALLOW list", target->nick.c_str())); return CMD_FAILURE; } } @@ -159,10 +184,10 @@ class CommandDccallow : public Command std::string mask = target->nick+"!"+target->ident+"@"+target->dhost; 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 +195,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 +207,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(993, 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(994, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for this session", target->nick.c_str())); } /* route it. */ @@ -196,7 +221,7 @@ 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; } } @@ -210,26 +235,9 @@ class CommandDccallow : public Command 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()); + for (size_t i = 0; i < sizeof(helptext)/sizeof(helptext[0]); i++) + user->WriteNumeric(998, helptext[i]); + user->WriteNumeric(999, "End of DCCALLOW HELP"); LocalUser* localuser = IS_LOCAL(user); if (localuser) @@ -239,78 +247,53 @@ 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(990, "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(991, 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(992, "End of DCCALLOW list"); } }; class ModuleDCCAllow : public Module { + DCCAllowExt ext; CommandDccallow cmd; - public: + public: ModuleDCCAllow() - : cmd(this) - { - ext = NULL; - } - - void init() - { - 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) + : ext("dccallow", ExtensionItem::EXT_USER, this) + , cmd(this, ext) { - 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) + void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE { - 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; @@ -332,7 +315,7 @@ class ModuleDCCAllow : public Module if (strncmp(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) @@ -345,12 +328,12 @@ class ModuleDCCAllow : public Module 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; @@ -398,16 +381,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->dhost + ") 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->dhost + ") 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 +404,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()) @@ -431,7 +414,7 @@ class ModuleDCCAllow : public Module { if (iter2->length != 0 && (iter2->set_on + iter2->length) <= 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(997, u->nick, InspIRCd::Format("DCCALLOW entry for %s has expired", iter2->nickname.c_str())); iter2 = dl->erase(iter2); } else @@ -455,7 +438,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 +448,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(995, u->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", i->nickname.c_str())); dl->erase(i); break; } @@ -495,8 +478,11 @@ class ModuleDCCAllow : public Module } } - void ReadFileConf() + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { + ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow"); + cmd.maxentries = tag->getInt("maxentries", 20); + bfl.clear(); ConfigTagList tags = ServerInstance->Config->ConfTags("banfile"); for (ConfigIter i = tags.first; i != tags.second; ++i) @@ -508,12 +494,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..88919e91b 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 @@ -32,31 +30,20 @@ class User_d : public ModeHandler ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { + 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) 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 != 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 = static_cast<Channel*>(dest); + bool is_bypasschar = (deaf_bypasschars.find(text[0]) != std::string::npos); + bool is_bypasschar_uline = (deaf_bypasschars_uline.find(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); } - } - 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..e864a8289 100644 --- a/src/modules/m_delayjoin.cpp +++ b/src/modules/m_delayjoin.cpp @@ -20,14 +20,10 @@ */ -/* $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) @@ -43,33 +39,27 @@ class ModuleDelayJoin : public Module DelayJoinMode djm; public: LocalIntExt unjoined; - ModuleDelayJoin() : djm(this), unjoined("delayjoin", this) + ModuleDelayJoin() + : djm(this) + , unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this) { } - void init() - { - 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 OnText(User* user, void* dest, int target_type, const std::string &text, char status, CUList &exempt_list) CXX11_OVERRIDE; + ModResult OnRawMode(User* user, Channel* channel, ModeHandler* mh, const std::string& param, bool adding) CXX11_OVERRIDE; }; 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 +68,36 @@ 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) + const Channel::MemberMap& users = channel->GetUsers(); + for (Channel::MemberMap::const_iterator n = users.begin(); n != users.end(); ++n) creator->OnText(n->first, channel, TYPE_CHANNEL, "", 0, empty); } - 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,7 +107,7 @@ 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); @@ -138,24 +126,20 @@ 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) { - /* Server origin */ - if (!user) - return; - if (target_type != TYPE_CHANNEL) return; @@ -177,14 +161,13 @@ void ModuleDelayJoin::OnText(User* user, void* dest, int target_type, const std: } /* 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; diff --git a/src/modules/m_delaymsg.cpp b/src/modules/m_delaymsg.cpp index 978ab55d2..1ad41cc57 100644 --- a/src/modules/m_delaymsg.cpp +++ b/src/modules/m_delaymsg.cpp @@ -19,14 +19,13 @@ #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; } @@ -36,65 +35,51 @@ class DelayMsgMode : public ModeHandler 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); + 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) 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,19 +87,18 @@ 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) { - /* Server origin */ - if ((!user) || (!IS_LOCAL(user))) + if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; - if (target_type != TYPE_CHANNEL) + if ((target_type != TYPE_CHANNEL) || ((!allownotice) && (msgtype == MSG_NOTICE))) return MOD_RES_PASSTHRU; Channel* channel = (Channel*) dest; @@ -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..467c8a03b 100644 --- a/src/modules/m_denychans.cpp +++ b/src/modules/m_denychans.cpp @@ -22,18 +22,17 @@ #include "inspircd.h" -/* $ModDesc: Implements config tags which allow blocking of joins to channels */ - class ModuleDenyChannels : public Module { + ChanModeReference redirectmode; + public: - void init() + ModuleDenyChannels() + : 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"); @@ -45,10 +44,10 @@ class ModuleDenyChannels : public Module if (!redirect.empty()) { - if (!ServerInstance->IsChannel(redirect.c_str(), ServerInstance->Config->Limits.ChanMax)) + if (!ServerInstance->IsChannel(redirect)) { - if (user) - user->WriteServ("NOTICE %s :Invalid badchan redirect '%s'", user->nick.c_str(), redirect.c_str()); + if (status.srcuser) + status.srcuser->WriteNotice("Invalid badchan redirect '" + redirect + "'"); throw ModuleException("Invalid badchan redirect, not a channel"); } @@ -67,8 +66,8 @@ class ModuleDenyChannels : public Module 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()); + if (status.srcuser) + status.srcuser->WriteNotice("Badchan " + name + " redirects to badchan " + redirect); throw ModuleException("Badchan redirect loop"); } } @@ -77,24 +76,20 @@ class ModuleDenyChannels : public Module } } - virtual ~ModuleDenyChannels() - { - } - - 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) { if (InspIRCd::Match(cname, j->second->getString("name"))) { - if (IS_OPER(user) && j->second->getBool("allowopers")) + if (user->IsOper() && j->second->getBool("allowopers")) { return MOD_RES_PASSTHRU; } @@ -112,19 +107,19 @@ class ModuleDenyChannels : public Module } } - if (ServerInstance->IsChannel(redirect.c_str(), ServerInstance->Config->Limits.ChanMax)) + if (ServerInstance->IsChannel(redirect)) { /* simple way to avoid potential loops: don't redirect to +L channels */ Channel *newchan = ServerInstance->FindChan(redirect); - if ((!newchan) || (!(newchan->IsModeSet('L')))) + if ((!newchan) || (!newchan->IsModeSet(redirectmode))) { - 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()); + user->WriteNumeric(926, cname, InspIRCd::Format("Channel %s is forbidden, redirecting to %s: %s", cname.c_str(), redirect.c_str(), reason.c_str())); + Channel::JoinUser(user, redirect); return MOD_RES_DENY; } } - user->WriteNumeric(926, "%s %s :Channel %s is forbidden: %s",user->nick.c_str(),cname,cname,reason.c_str()); + user->WriteNumeric(926, cname, InspIRCd::Format("Channel %s is forbidden: %s", cname.c_str(), reason.c_str())); return MOD_RES_DENY; } } diff --git a/src/modules/m_devoice.cpp b/src/modules/m_devoice.cpp index 2b5de2bd6..4e4b3a354 100644 --- a/src/modules/m_devoice.cpp +++ b/src/modules/m_devoice.cpp @@ -24,8 +24,6 @@ * Syntax: /DEVOICE <#chan> */ -/* $ModDesc: Provides voiced users with the ability to devoice themselves. */ - #include "inspircd.h" /** Handle /DEVOICE @@ -36,24 +34,17 @@ class CommandDevoice : public Command 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; - } + std::vector<std::string> modes; + modes.push_back(parameters[0]); + modes.push_back("-v"); + modes.push_back(user->nick); - return CMD_FAILURE; + ServerInstance->Parser.CallHandler("MODE", modes, ServerInstance->FakeClient); + return CMD_SUCCESS; } }; @@ -65,16 +56,7 @@ class ModuleDeVoice : public Module { } - void init() - { - ServerInstance->Modules->AddService(cmd); - } - - virtual ~ModuleDeVoice() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides voiced users with the ability to devoice themselves.", VF_VENDOR); } diff --git a/src/modules/m_dnsbl.cpp b/src/modules/m_dnsbl.cpp index 3dea080ce..b2b9c30ff 100644 --- a/src/modules/m_dnsbl.cpp +++ b/src/modules/m_dnsbl.cpp @@ -23,8 +23,7 @@ #include "inspircd.h" #include "xline.h" - -/* $ModDesc: Provides handling of DNS blacklists */ +#include "modules/dns.h" /* Class holding data for a single entry */ class DNSBLConfEntry : public refcountbase @@ -40,13 +39,12 @@ class DNSBLConfEntry : public refcountbase 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 */ -class DNSBLResolver : public Resolver +class DNSBLResolver : public DNS::Request { std::string theiruid; LocalStringExt& nameExt; @@ -55,175 +53,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('a', "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->WriteNumeric(304, "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->WriteNumeric(304, "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('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); } + 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 { std::vector<reference<DNSBLConfEntry> > DNSBLConfEntries; + dynamic_reference<DNS::Manager> DNS; LocalStringExt nameExt; LocalIntExt countExt; @@ -246,25 +246,21 @@ class ModuleDNSBL : public Module return DNSBLConfEntry::I_UNKNOWN; } public: - ModuleDNSBL() : nameExt("dnsbl_match", this), countExt("dnsbl_pending", this) { } - - void init() + ModuleDNSBL() + : DNS(this, "DNS") + , nameExt("dnsbl_match", ExtensionItem::EXT_USER, this) + , countExt("dnsbl_pending", ExtensionItem::EXT_USER, this) { - 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)); } - 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(); @@ -296,7 +292,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 */ @@ -321,11 +317,6 @@ class ModuleDNSBL : public Module 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()); - } else { if (e->reason.empty()) @@ -341,14 +332,9 @@ class ModuleDNSBL : public Module } } - void OnRehash(User* user) + void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE { - ReadConf(); - } - - void OnSetUserIP(LocalUser* user) - { - if ((user->exempt) || (user->client_sa.sa.sa_family != AF_INET)) + if ((user->exempt) || !DNS) return; if (user->MyClass) @@ -357,40 +343,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.sa.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.sa.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 +408,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 +428,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..0c67037f0 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 */ @@ -31,121 +30,111 @@ class ExemptChanOps : public ListModeBase 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())) + std::string::size_type p = word.find(':'); + if (p == std::string::npos) + { + user->WriteNumeric(955, chan->name, 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(955, chan->name, restriction, "Unknown restriction"); return false; } return true; } - bool TellListTooLong(User* user, Channel* chan, std::string &word) + void TellListTooLong(User* user, Channel* chan, std::string &word) { - 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) { - 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) { - 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..0badd1377 100644 --- a/src/modules/m_filter.cpp +++ b/src/modules/m_filter.cpp @@ -22,11 +22,7 @@ #include "inspircd.h" #include "xline.h" -#include "m_regex.h" - -/* $ModDesc: Text (spam) filtering */ - -class ModuleFilter; +#include "modules/regex.h" enum FilterFlags { @@ -48,6 +44,7 @@ enum FilterAction class FilterResult { public: + Regex* regex; std::string freeform; std::string reason; FilterAction action; @@ -60,9 +57,12 @@ 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), gline_time(gt) { + if (!RegexEngine) + throw ModuleException("Regex module implementing '"+RegexEngine.GetProvider()+"' is not loaded!"); + regex = RegexEngine->Create(free); this->FillFlags(fla); } @@ -154,17 +154,10 @@ class CommandFilter : public Command } }; -class ImplFilter : public FilterResult -{ - public: - Regex* regex; - - ImplFilter(ModuleFilter* mymodule, const std::string &rea, FilterAction act, long glinetime, const std::string &pat, const std::string &flgs); -}; - - class ModuleFilter : public Module { + typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet; + bool initing; RegexFactory* factory; void FreeFilters(); @@ -173,28 +166,30 @@ 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); + CullResult cull() CXX11_OVERRIDE; + ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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); @@ -209,13 +204,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()); + user->WriteNotice("*** Removed filter '" + parameters[0] + "'"); ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "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,7 +227,7 @@ 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()); + user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent' and 'kill'."); return CMD_FAILURE; } @@ -240,12 +235,12 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User { 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 type filter, a gline duration must be specified as the third parameter."); return CMD_FAILURE; } } @@ -258,9 +253,9 @@ 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]); @@ -268,13 +263,13 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> ¶meters, User } 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 +278,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; @@ -301,14 +296,6 @@ ModuleFilter::ModuleFilter() { } -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); -} - CullResult ModuleFilter::cull() { FreeFilters(); @@ -317,29 +304,19 @@ 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) { + // 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 = (msgtype == MSG_PRIVMSG) ? FLAG_PRIVMSG : FLAG_NOTICE; FilterResult* f = this->FilterMatch(user, text, flags); if (f) @@ -348,12 +325,16 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s if (target_type == TYPE_USER) { User* t = (User*)dest; + // 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) { Channel* t = (Channel*)dest; - if (exemptfromfilter.find(t->name) != exemptfromfilter.end()) + if (exemptedchans.count(t->name)) return MOD_RES_PASSTHRU; target = t->name; @@ -362,16 +343,16 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s { 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()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked and opers notified (%s)", f->reason.c_str())); else - user->WriteServ("NOTICE "+user->nick+" :Your message to "+target+" was blocked and opers notified: "+f->reason); + user->WriteNotice("Your message to "+target+" was blocked and opers notified: "+f->reason); } 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()); + user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked (%s)", f->reason.c_str())); else - user->WriteServ("NOTICE "+user->nick+" :Your message to "+target+" was blocked: "+f->reason); + user->WriteNotice("Your message to "+target+" was blocked: "+f->reason); } else if (f->action == FA_KILL) { @@ -388,7 +369,7 @@ 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; @@ -396,7 +377,7 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) { - if (validated && IS_LOCAL(user)) + if (validated) { flags = 0; bool parting; @@ -416,7 +397,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,7 +427,7 @@ 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) @@ -466,15 +447,25 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri 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"); @@ -554,11 +545,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))); } } @@ -573,27 +564,19 @@ void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extna } 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 +588,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 +610,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 +620,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 +632,15 @@ 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, "none")) fa = FA_NONE; else return false; @@ -695,7 +671,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 gline_time = i->second->getDuration("duration", 10*60, 1); if (flgs.empty()) flgs = "*"; @@ -705,27 +681,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, gline_time, 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->gline_time)+" :"+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..4e7af4b83 --- /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 (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 (ls->bind_tag->getString("type", "clients") != "clients" || ls->bind_tag->getString("ssl", "plaintext") != "plaintext") + 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..a15f19418 100644 --- a/src/modules/m_gecosban.cpp +++ b/src/modules/m_gecosban.cpp @@ -19,27 +19,15 @@ #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); } - 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] == ':')) { @@ -49,9 +37,9 @@ class ModuleGecosBan : public Module 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..36a90bf55 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,7 +33,6 @@ 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) @@ -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 @@ -79,6 +76,13 @@ class CommandGunloadmodule : public Command CmdResult Handle (const std::vector<std::string> ¶meters, User *user) { + 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()); @@ -112,25 +115,6 @@ class CommandGunloadmodule : public Command } }; -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 @@ -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; } } @@ -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..1cb87324b 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,7 +31,6 @@ 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) @@ -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_helpop.cpp b/src/modules/m_helpop.cpp index 4bbe8785e..95f69774b 100644 --- a/src/modules/m_helpop.cpp +++ b/src/modules/m_helpop.cpp @@ -21,11 +21,10 @@ */ -/* $ModDesc: Provides the /HELPOP command for useful information */ - #include "inspircd.h" -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 +41,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) { - 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 +82,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 +141,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(310, "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..08caae6b2 100644 --- a/src/modules/m_hidechans.cpp +++ b/src/modules/m_hidechans.cpp @@ -20,8 +20,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for hiding channels with user mode +I */ - /** Handles user mode +I */ class HideChans : public SimpleUserModeHandler @@ -30,49 +28,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 +68,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 +76,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..97173c14b --- /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) + { + // 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->getInt("rank", HALFOP_VALUE, 0); + 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_hideoper.cpp b/src/modules/m_hideoper.cpp index 32999d9f0..ff9e78ca2 100644 --- a/src/modules/m_hideoper.cpp +++ b/src/modules/m_hideoper.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for hiding oper status with user mode +H */ - /** Handles user mode +H */ class HideOper : public SimpleUserModeHandler @@ -50,43 +48,32 @@ class HideOper : public SimpleUserModeHandler } }; -class ModuleHideOper : public Module +class ModuleHideOper : public Module, public Whois::LineEventListener { HideOper hm; bool active; public: ModuleHideOper() - : hm(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 +81,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->dhost + ") 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..6d5896ef5 100644 --- a/src/modules/m_hostchange.cpp +++ b/src/modules/m_hostchange.cpp @@ -21,8 +21,6 @@ #include "inspircd.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 @@ -47,21 +45,13 @@ typedef std::vector<std::pair<std::string, Host> > hostchanges_t; class ModuleHostChange : public Module { - private: hostchanges_t hostchanges; std::string MySuffix; std::string MyPrefix; std::string MySeparator; public: - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnRehash, I_OnUserConnect }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* host = ServerInstance->Config->ConfValue("host"); MySuffix = host->getString("suffix"); @@ -97,14 +87,14 @@ class ModuleHostChange : public Module } } - 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); } - virtual void OnUserConnect(LocalUser* user) + void OnUserConnect(LocalUser* user) CXX11_OVERRIDE { for (hostchanges_t::iterator i = hostchanges.begin(); i != hostchanges.end(); i++) { @@ -160,9 +150,9 @@ class ModuleHostChange : public Module } 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); + 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..621f06a27 --- /dev/null +++ b/src/modules/m_hostcycle.cpp @@ -0,0 +1,126 @@ +/* + * 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; + + /** Send fake quit/join/mode messages for host or ident cycle. + */ + void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const char* quitmsg) + { + // GetFullHost() returns the original data at the time this function is called + const std::string quitline = ":" + user->GetFullHost() + " 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->Write(quitline); + } + 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; + const std::string joinline = ":" + newfullhost + " JOIN " + c->name; + std::string modeline; + + if (!memb->modes.empty()) + { + modeline = ":" + (ServerInstance->Config->CycleHostsFromUser ? newfullhost : ServerInstance->Config->ServerName) + + " MODE " + c->name + " +" + memb->modes; + + for (size_t j = 0; j < memb->modes.length(); j++) + modeline.append(" ").append(user->nick); + } + + 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->Write(quitline); + u->already_sent = seen_id; + } + + u->Write(joinline); + if (!memb->modes.empty()) + u->Write(modeline); + } + } + } + + public: + ModuleHostCycle() + : chghostcap(this, "chghost") + { + } + + void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE + { + DoHostCycle(user, newident, user->dhost, "Changing ident"); + } + + void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE + { + DoHostCycle(user, user->ident, newhost, "Changing host"); + } + + 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 2b079c6ff..05112eb9c 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,9 +95,9 @@ 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) @@ -188,13 +206,8 @@ class HttpServerSocket : public BufferedSocket 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,14 +331,14 @@ 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); } @@ -337,73 +350,76 @@ class HttpServerSocket : public BufferedSocket SendHeaders(n->str().length(), response, *hheaders); WriteData(n->str()); } + + void AddToCull() + { + if (waitingcull) + return; + + waitingcull = true; + Close(); + ServerInstance->GlobalCulls.AddItem(this); + } +}; + +class HTTPdAPIImpl : public HTTPdAPIBase +{ + public: + HTTPdAPIImpl(Module* parent) + : HTTPdAPIBase(parent) + { + } + + void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE + { + resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers); + } }; class ModuleHttpServer : public Module { + HTTPdAPIImpl APIImpl; unsigned int timeoutsec; + Events::ModuleEventProvider acleventprov; + Events::ModuleEventProvider reqeventprov; public: - - void init() + ModuleHttpServer() + : APIImpl(this) + , acleventprov(this, "event/http-acl") + , reqeventprov(this, "event/http-request") { - HttpModule = this; - Implementation eventlist[] = { I_OnAcceptConnection, I_OnBackgroundTimer, I_OnRehash, I_OnUnloadModule }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - OnRehash(NULL); + aclevprov = &acleventprov; + reqevprov = &reqeventprov; } - void OnRehash(User* user) + void init() CXX11_OVERRIDE { - ConfigTag* tag = ServerInstance->Config->ConfValue("httpd"); - timeoutsec = tag->getInt("timeout"); + HttpModule = this; } - void OnRequest(Request& request) + void ReadConfig(ConfigStatus& status) 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); + ConfigTag* tag = ServerInstance->Config->ConfValue("httpd"); + timeoutsec = tag->getDuration("timeout", 10, 1); } - ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) + ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) 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; - } - void OnBackgroundTimer(time_t curtime) - { - if (!timeoutsec) - return; - - 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; @@ -411,20 +427,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..00a50d7a1 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"); @@ -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="") { - ServerInstance->Logs->Log("m_httpd_acl", DEBUG, "BlockAccess (%d)", returnval); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BlockAccess (%d)", 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..c47ddb5b2 100644 --- a/src/modules/m_httpd_config.cpp +++ b/src/modules/m_httpd_config.cpp @@ -19,18 +19,17 @@ #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) @@ -67,14 +66,12 @@ class ModuleHttpConfig : public Module return ret; } - 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() == "/config") || (http->GetURI() == "/config/")) { @@ -84,8 +81,8 @@ class ModuleHttpConfig : public Module 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++) + const ConfigItems& items = x->second->getItems(); + for (ConfigItems::const_iterator j = items.begin(); j != items.end(); j++) { data << Sanitize(j->first) << "="" << Sanitize(j->second) << "" "; } @@ -95,20 +92,23 @@ class ModuleHttpConfig : public Module 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("X-Powered-By", MODNAME); response.headers.SetHeader("Content-Type", "text/html"); - response.Send(); + API->SendResponse(response); + return MOD_RES_DENY; // Handled } } + return MOD_RES_PASSTHRU; } - virtual ~ModuleHttpConfig() + ModResult OnHTTPRequest(HTTPRequest& req) CXX11_OVERRIDE { + return HandleRequest(&req); } - 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..41d85a3b4 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,14 +88,12 @@ 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/")) { @@ -107,19 +102,21 @@ class ModuleHttpStats : public Module << 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><socketengine>" INSPIRCD_SOCKETENGINE_NAME "</socketengine>"; + 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)) + << Sanitize(u->fullname) << "</gecos><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,10 +203,10 @@ 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>"; @@ -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..806c70ad2 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 @@ -119,33 +117,32 @@ 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.sa, connaddr.sa_size()) == -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]; @@ -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,66 @@ 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 { 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 +298,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 +316,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 +342,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..6229e1fa2 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"] = "I"; } - 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..543fb49a4 100644 --- a/src/modules/m_ircv3.cpp +++ b/src/modules/m_ircv3.cpp @@ -16,119 +16,57 @@ * 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/cap.h" +#include "modules/ircv3.h" -class ModuleIRCv3 : public Module +class ModuleIRCv3 : public Module, public AccountEventListener { - GenericCap cap_accountnotify; - GenericCap cap_awaynotify; - GenericCap cap_extendedjoin; - bool accountnotify; - bool awaynotify; - bool extendedjoin; + Cap::Capability cap_accountnotify; + Cap::Capability cap_awaynotify; + Cap::Capability cap_extendedjoin; CUList last_excepts; - void WriteNeighboursWithExt(User* user, const std::string& line, const LocalIntExt& ext) - { - 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); - } - } - } - public: - ModuleIRCv3() : cap_accountnotify(this, "account-notify"), + ModuleIRCv3() + : AccountEventListener(this) + , cap_accountnotify(this, "account-notify"), cap_awaynotify(this, "away-notify"), cap_extendedjoin(this, "extended-join") { } - void init() - { - OnRehash(NULL); - Implementation eventlist[] = { I_OnUserJoin, I_OnPostJoin, I_OnSetAway, I_OnEvent, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3"); - accountnotify = conf->getBool("accountnotify", conf->getBool("accoutnotify", true)); - awaynotify = conf->getBool("awaynotify", true); - extendedjoin = conf->getBool("extendedjoin", true); + cap_accountnotify.SetActive(conf->getBool("accountnotify", true)); + cap_awaynotify.SetActive(conf->getBool("awaynotify", true)); + cap_extendedjoin.SetActive(conf->getBool("extendedjoin", true)); } - void OnEvent(Event& ev) + void OnAccountChange(User* user, const std::string& newaccount) CXX11_OVERRIDE { - if (awaynotify) - cap_awaynotify.HandleEvent(ev); - if (extendedjoin) - cap_extendedjoin.HandleEvent(ev); - - if (accountnotify) - { - 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); - } - } + // :nick!user@host ACCOUNT account + // or + // :nick!user@host ACCOUNT * + std::string line = ":" + user->GetFullHost() + " ACCOUNT "; + if (newaccount.empty()) + line += "*"; + else + line += newaccount; + + IRCv3::WriteNeighborsWithCap(user, line, cap_accountnotify); } - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) + void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE { // Remember who is not going to see the JOIN because of other modules - if ((awaynotify) && (IS_AWAY(memb->user))) + if ((cap_awaynotify.IsActive()) && (memb->user->IsAway())) last_excepts = excepts; - if (!extendedjoin) + if (!cap_extendedjoin.IsActive()) return; /* @@ -143,12 +81,12 @@ class ModuleIRCv3 : public Module std::string line; std::string mode; - const UserMembList* userlist = memb->chan->GetUsers(); - for (UserMembCIter it = userlist->begin(); it != userlist->end(); ++it) + const Channel::MemberMap& userlist = memb->chan->GetUsers(); + for (Channel::MemberMap::const_iterator 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())) + if ((member) && (cap_extendedjoin.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()) @@ -195,9 +133,9 @@ class ModuleIRCv3 : public Module } } - ModResult OnSetAway(User* user, const std::string &awaymsg) + ModResult OnSetAway(User* user, const std::string &awaymsg) CXX11_OVERRIDE { - if (awaynotify) + if (cap_awaynotify.IsActive()) { // Going away: n!u@h AWAY :reason // Back from away: n!u@h AWAY @@ -205,24 +143,24 @@ class ModuleIRCv3 : public Module if (!awaymsg.empty()) line += " :" + awaymsg; - WriteNeighboursWithExt(user, line, cap_awaynotify.ext); + IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify); } return MOD_RES_PASSTHRU; } - void OnPostJoin(Membership *memb) + void OnPostJoin(Membership *memb) CXX11_OVERRIDE { - if ((!awaynotify) || (!IS_AWAY(memb->user))) + if ((!cap_awaynotify.IsActive()) || (!memb->user->IsAway())) 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) + const Channel::MemberMap& userlist = memb->chan->GetUsers(); + for (Channel::MemberMap::const_iterator 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)) + if ((member) && (cap_awaynotify.get(member)) && (last_excepts.find(member) == last_excepts.end()) && (it->second != memb)) { member->Write(line); } @@ -231,12 +169,12 @@ class ModuleIRCv3 : public Module last_excepts.clear(); } - void Prioritize() + void Prioritize() CXX11_OVERRIDE { ServerInstance->Modules->SetPriority(this, I_OnUserJoin, PRIORITY_LAST); } - 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_capnotify.cpp b/src/modules/m_ircv3_capnotify.cpp new file mode 100644 index 000000000..93c30df12 --- /dev/null +++ b/src/modules/m_ircv3_capnotify.cpp @@ -0,0 +1,149 @@ +/* + * 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 ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public ReloadModule::EventListener +{ + CapNotify capnotify; + std::string reloadedmod; + std::vector<std::string> reloadedcaps; + + void Send(const std::string& capname, Cap::Capability* cap, bool add) + { + std::string msg = (add ? "NEW :" : "DEL :"); + msg.append(capname); + std::string msgwithval = msg; + msgwithval.push_back('='); + std::string::size_type msgpos = msgwithval.size(); + + 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.append(*capvalue); + user->WriteCommand("CAP", msgwithval); + msgwithval.erase(msgpos); + continue; + } + } + user->WriteCommand("CAP", msg); + } + } + + public: + ModuleIRCv3CapNotify() + : Cap::EventListener(this) + , ReloadModule::EventListener(this) + , capnotify(this) + { + } + + 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.2 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..af3503108 --- /dev/null +++ b/src/modules/m_ircv3_chghost.cpp @@ -0,0 +1,57 @@ +/* + * 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; + + void DoChgHost(User* user, const std::string& ident, const std::string& host) + { + std::string line(1, ':'); + line.append(user->GetFullHost()).append(" CHGHOST ").append(ident).append(1, ' ').append(host); + IRCv3::WriteNeighborsWithCap(user, line, cap); + } + + public: + ModuleIRCv3ChgHost() + : cap(this, "chghost") + { + } + + void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE + { + DoChgHost(user, newident, user->dhost); + } + + 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.2 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..8773d7187 --- /dev/null +++ b/src/modules/m_ircv3_echomessage.cpp @@ -0,0 +1,70 @@ +/* + * 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" + +static const char* MessageTypeStringSp[] = { "PRIVMSG ", "NOTICE " }; + +class ModuleIRCv3EchoMessage : public Module +{ + Cap::Capability cap; + + public: + ModuleIRCv3EchoMessage() + : cap(this, "echo-message") + { + } + + void OnUserMessage(User* user, void* dest, int target_type, const std::string& text, char status, const CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE + { + if (!cap.get(user)) + return; + + std::string msg = MessageTypeStringSp[msgtype]; + if (target_type == TYPE_USER) + { + User* destuser = static_cast<User*>(dest); + msg.append(destuser->nick); + } + else if (target_type == TYPE_CHANNEL) + { + if (status) + msg.push_back(status); + + Channel* chan = static_cast<Channel*>(dest); + msg.append(chan->name); + } + else + { + const char* servername = static_cast<const char*>(dest); + msg.append(servername); + } + msg.append(" :").append(text); + user->WriteFrom(user, msg); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides the echo-message IRCv3.2 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..3783ff33c --- /dev/null +++ b/src/modules/m_ircv3_invitenotify.cpp @@ -0,0 +1,68 @@ +/* + * 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 + { + std::string msg = "INVITE "; + msg.append(dest->nick).append(1, ' ').append(chan->name); + 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; + + // Send and add the user to the exceptions so they won't get the NOTICE invite announcement message + user->WriteFrom(source, msg); + 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.2 extension", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleIRCv3InviteNotify) diff --git a/src/modules/m_joinflood.cpp b/src/modules/m_joinflood.cpp index 63bcc38a4..077ceff52 100644 --- a/src/modules/m_joinflood.cpp +++ b/src/modules/m_joinflood.cpp @@ -23,35 +23,35 @@ #include "inspircd.h" -/* $ModDesc: Provides channel mode +j (join flood protection) */ +// 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 +66,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) { - 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(608, channel->name, "Invalid flood 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(608, channel->name, "Invalid flood 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(609, 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 +168,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..89391c8a4 100644 --- a/src/modules/m_jumpserver.cpp +++ b/src/modules/m_jumpserver.cpp @@ -20,8 +20,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides support for the RPL_REDIR numeric and the /JUMPSERVER command. */ +#include "modules/ssl.h" /** Handle /JUMPSERVER */ @@ -32,11 +31,14 @@ 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; } @@ -53,11 +55,12 @@ class CommandJumpserver : public Command if (!parameters.size()) { 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 +87,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 +128,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 +155,29 @@ class ModuleJumpServer : public Module { } - void init() + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { - ServerInstance->Modules->AddService(js); - Implementation eventlist[] = { I_OnUserRegister, I_OnRehash }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } - - virtual ~ModuleJumpServer() - { - } - - virtual ModResult OnUserRegister(LocalUser* user) - { - 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..ad8bfdcb6 100644 --- a/src/modules/m_kicknorejoin.cpp +++ b/src/modules/m_kicknorejoin.cpp @@ -25,49 +25,94 @@ #include "inspircd.h" -/* $ModDesc: Provides channel mode +J (delay rejoin after kick) */ +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; + + 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; + } -typedef std::map<std::string, time_t> delaylist; + 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) { - if (adding) - { - int v = ConvToInt(parameter); - if (v <= 0) - return MODEACTION_DENY; - if (parameter == channel->GetModeParameter(this)) - return MODEACTION_DENY; + int v = ConvToInt(parameter); + if (v <= 0) + return MODEACTION_DENY; - if ((IS_LOCAL(source) && ((unsigned int)v > max))) - v = max; + if ((IS_LOCAL(source) && ((unsigned int)v > max))) + v = max; - parameter = ConvToStr(v); - channel->SetModeParam(this, parameter); - } - else - { - if (!channel->IsModeSet(this)) - return MODEACTION_DENY; - - ext.unset(channel); - channel->SetModeParam(this, ""); - } + 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 @@ -75,85 +120,41 @@ class ModuleKickNoRejoin : public Module KickRejoin kr; public: - ModuleKickNoRejoin() : kr(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) && (!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_DELAYREJOIN, 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() CXX11_OVERRIDE { - } - - Version GetVersion() - { - 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..cf623c4ab 100644 --- a/src/modules/m_knock.cpp +++ b/src/modules/m_knock.cpp @@ -21,20 +21,23 @@ #include "inspircd.h" -/* $ModDesc: Provides support for /KNOCK and channel mode +K */ - /** 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) @@ -42,35 +45,35 @@ class CommandKnock : public Command 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::NoSuchNick(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(480, 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()); - user->WriteServ("NOTICE %s :KNOCKing on %s", user->nick.c_str(), c->name.c_str()); + user->WriteNotice("KNOCKing on " + c->name); return CMD_SUCCESS; } @@ -80,43 +83,27 @@ class CommandKnock : public Command } }; -/** 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 +115,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..9deb9a203 --- /dev/null +++ b/src/modules/m_ldapoper.cpp @@ -0,0 +1,248 @@ +/* + * 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; + + std::vector<std::string> params; + params.push_back(opername); + params.push_back(password); + oper_command->Handle(params, user); + } + + 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, std::vector<std::string>& parameters, LocalUser* user, bool validated, const std::string& original_line) 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->host; + 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..068573e0d 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) { - 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) { - 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,38 @@ 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() - { - } - - - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { // Emergency way to unlock - if (!user) locked = false; + if (!status.srcuser) + 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..41de2997d 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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..26ff4cffc 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) { 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..9d119b6c3 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,7 +35,7 @@ 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) { @@ -56,92 +55,78 @@ 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) { - 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(608, channel->name, "Invalid flood 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(608, channel->name, "Invalid flood 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, void* voiddest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) 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 != 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 = static_cast<Channel*>(voiddest); + if ((!IS_LOCAL(user)) || !dest->IsModeSet(mf)) return MOD_RES_PASSTHRU; - if (ServerInstance->OnCheckExemption(user,dest,"flood") == MOD_RES_ALLOW) + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (user, dest, "flood")); + if (res == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; floodsettings *f = mf.ext.get(dest); @@ -153,17 +138,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->dhost); + 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 +155,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..056eb4a62 100644 --- a/src/commands/cmd_modenotice.cpp +++ b/src/modules/m_modenotice.cpp @@ -30,8 +30,10 @@ class CommandModeNotice : public Command CmdResult Handle(const std::vector<std::string>& parameters, User *src) { + 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,8 +41,7 @@ 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; @@ -48,8 +49,24 @@ next_user: ; RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..c69732a73 --- /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(unset_raw(container)); + } + + std::string serialize(SerializeFormat format, const Extensible* container, void* item) const + { + 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); + + void free(void* item) + { + 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(const std::vector<std::string>& parameters, LocalUser* user) + { + 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->getInt("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..c9caf6a6a 100644 --- a/src/modules/m_muteban.cpp +++ b/src/modules/m_muteban.cpp @@ -20,28 +20,15 @@ #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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (!IS_LOCAL(user) || target_type != TYPE_CHANNEL) return MOD_RES_PASSTHRU; @@ -49,24 +36,17 @@ class ModuleQuietBan : public Module Channel* chan = static_cast<Channel*>(dest); 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()); + 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..7f2687d66 100644 --- a/src/modules/m_namedmodes.cpp +++ b/src/modules/m_namedmodes.cpp @@ -17,56 +17,57 @@ */ -/* $ModDesc: Provides the ability to manipulate modes via long names. */ - #include "inspircd.h" -static void DisplayList(User* user, Channel* channel) +static void DisplayList(LocalUser* user, Channel* channel) { - std::stringstream items; - for(char letter = 'A'; letter <= 'z'; letter++) + Numeric::ParamBuilder<1> numeric(user, 961); + 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()) - continue; - if (!channel->IsModeSet(letter)) + ModeHandler* mh = i->second; + if (!channel->IsModeSet(mh)) 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(960, 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(const std::vector<std::string>& parameters, LocalUser* src) { + Channel* const chan = ServerInstance->FindChan(parameters[0]); + if (!chan) + { + src->WriteNumeric(Numerics::NoSuchNick(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 +77,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 +101,13 @@ class DummyZ : public ModeHandler { list = true; } + + // Handle /MODE #chan Z + void DisplayList(User* user, Channel* chan) + { + if (IS_LOCAL(user)) + ::DisplayList(static_cast<LocalUser*>(user), chan); + } }; class ModuleNamedModes : public Module @@ -113,99 +119,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); } - for(char letter = 'A'; modechar == 0 && letter <= 'z'; letter++) + + ModeHandler* mh = ServerInstance->Modes->FindMode(name, MODETYPE_CHANNEL); + if (!mh) { - mh = ServerInstance->Modes->FindMode(letter, MODETYPE_CHANNEL); - if (mh && mh->name == name) + // Mode handler not found + i = list.erase(i); + continue; + } + + curr.param.clear(); + if (mh->NeedsParam(curr.adding)) + { + 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..beac968ef 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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..d03468de7 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 HandlerBase1<bool, const std::string&> { public: - lwbNickHandler() { } - virtual ~lwbNickHandler() { } - virtual bool Call(const char*, size_t); + 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,29 @@ 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; + caller1<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 +242,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 +254,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); } - 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 +290,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 +310,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 +326,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 +344,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..a4a87f691 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) { - 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(608, channel->name, "Invalid flood 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(608, channel->name, "Invalid flood 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"); + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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"); + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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..fae9d36d8 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,7 +41,7 @@ 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) @@ -44,20 +50,20 @@ class CommandNicklock : public Command 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 { @@ -80,10 +86,7 @@ class CommandNicklock : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -98,7 +101,7 @@ 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) @@ -107,7 +110,7 @@ class CommandNickunlock : public Command 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; } } @@ -132,14 +133,10 @@ class CommandNickunlock : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..713964328 100644 --- a/src/modules/m_noctcp.cpp +++ b/src/modules/m_noctcp.cpp @@ -20,8 +20,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel mode +C to block CTCPs */ +#include "modules/exemption.h" class NoCTCP : public SimpleChannelModeHandler { @@ -31,38 +30,22 @@ class NoCTCP : public SimpleChannelModeHandler class ModuleNoCTCP : public Module { - + CheckExemption::EventProvider exemptionprov; NoCTCP nc; public: - ModuleNoCTCP() - : nc(this) - { - } - - void init() + : exemptionprov(this) + , nc(this) { - ServerInstance->Modules->AddService(nc); - Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual ~ModuleNoCTCP() - { - } - - virtual Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +C to block CTCPs", VF_VENDOR); } - 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) { @@ -70,22 +53,23 @@ class ModuleNoCTCP : public Module 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"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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_NOCTCPALLOWED, c->name, "Can't send CTCP to channel (+C set)"); 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 { - ServerInstance->AddExtBanChar('C'); + tokens["EXTBAN"].push_back('C'); } }; diff --git a/src/modules/m_nokicks.cpp b/src/modules/m_nokicks.cpp index 1f58a2e08..fb3455567 100644 --- a/src/modules/m_nokicks.cpp +++ b/src/modules/m_nokicks.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -/* $ModDesc: Provides channel mode +Q to prevent kicks on the channel. */ - class NoKicks : public SimpleChannelModeHandler { public: @@ -40,38 +38,26 @@ class ModuleNoKicks : public Module { } - void init() - { - 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..c6de17e89 100644 --- a/src/modules/m_nonicks.cpp +++ b/src/modules/m_nonicks.cpp @@ -20,8 +20,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides support for channel mode +N & extban +b N: which prevents nick changes on channel */ +#include "modules/exemption.h" class NoNicks : public SimpleChannelModeHandler { @@ -31,61 +30,45 @@ class NoNicks : public SimpleChannelModeHandler class ModuleNoNickChange : public Module { + CheckExemption::EventProvider exemptionprov; NoNicks nn; bool override; public: - ModuleNoNickChange() : nn(this) - { - } - - 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() + ModuleNoNickChange() + : exemptionprov(this) + , nn(this) { } - 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; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (user, curr, "nonick")); if (res == MOD_RES_ALLOW) continue; - if (override && IS_OPER(user)) + if (override && user->IsOper()) 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("Can't change nickname while on %s (+N is set)", + curr->name.c_str())); return MOD_RES_DENY; } } @@ -93,7 +76,7 @@ class ModuleNoNickChange : public Module return MOD_RES_PASSTHRU; } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { override = ServerInstance->Config->ConfValue("nonicks")->getBool("operoverride", false); } diff --git a/src/modules/m_nonotice.cpp b/src/modules/m_nonotice.cpp index c5b9f3a1c..ec5be9517 100644 --- a/src/modules/m_nonotice.cpp +++ b/src/modules/m_nonotice.cpp @@ -20,8 +20,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel mode +T to block notices to the channel */ +#include "modules/exemption.h" class NoNotice : public SimpleChannelModeHandler { @@ -31,40 +30,35 @@ class NoNotice : public SimpleChannelModeHandler class ModuleNoNotice : public Module { + CheckExemption::EventProvider exemptionprov; NoNotice nt; public: ModuleNoNotice() - : nt(this) - { - } - - void init() + : exemptionprov(this) + , nt(this) { - ServerInstance->Modules->AddService(nt); - Implementation eventlist[] = { I_OnUserPreNotice, I_On005Numeric }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('T'); + tokens["EXTBAN"].push_back('T'); } - virtual ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { ModResult res; - if ((target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) + if ((msgtype == MSG_NOTICE) && (target_type == TYPE_CHANNEL) && (IS_LOCAL(user))) { Channel* c = (Channel*)dest; - if (!c->GetExtBanStatus(user, 'T').check(!c->IsModeSet('T'))) + if (!c->GetExtBanStatus(user, 'T').check(!c->IsModeSet(nt))) { - res = ServerInstance->OnCheckExemption(user,c,"nonotice"); + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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 +66,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..76e66bdc2 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(const std::vector<std::string>& parameters, LocalUser* user) { // 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,54 +83,13 @@ 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) + NetworkPrefix(Module* parent, char NPrefix) + : PrefixMode(parent, "official-join", 'Y', NETWORK_VALUE, NPrefix) { - 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() - { - return NETWORK_VALUE; - } - - void RemoveMode(User* user, irc::modestacker* stack) - { } ModResult AccessCheck(User* source, Channel* channel, std::string ¶meter, bool adding) @@ -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..0b074ebab 100644 --- a/src/modules/m_operchans.cpp +++ b/src/modules/m_operchans.cpp @@ -22,8 +22,6 @@ #include "inspircd.h" -/* $ModDesc: Provides support for oper-only chans via the +O channel mode */ - class OperChans : public SimpleChannelModeHandler { public: @@ -42,44 +40,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..bf758b1f7 100644 --- a/src/modules/m_operlevels.cpp +++ b/src/modules/m_operlevels.cpp @@ -20,27 +20,20 @@ */ -/* $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()); @@ -50,8 +43,8 @@ class ModuleOperLevels : public Module 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()); + 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 +53,3 @@ class ModuleOperLevels : public Module }; MODULE_INIT(ModuleOperLevels) - diff --git a/src/modules/m_operlog.cpp b/src/modules/m_operlog.cpp index edb9109e8..68f50bf6d 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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 + " " + irc::stringjoiner(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..33ebb57a0 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; @@ -71,7 +60,7 @@ class ModuleModesOnOper : public Module while (ss >> 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..2431853c2 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 @@ -45,28 +43,27 @@ class CommandOpermotd : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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,34 +79,32 @@ 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)); + try + { + FileReader reader(conf->getString("file", "opermotd")); + cmd.opermotd = reader.GetVector(); + } + catch (CoreException&) + { + // Nothing happens here as we do the error handling in ShowOperMOTD. + } if (conf->getBool("processcolors")) InspIRCd::ProcessColors(cmd.opermotd); diff --git a/src/modules/m_operprefix.cpp b/src/modules/m_operprefix.cpp index 7d5e6d118..73155b394 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]; + levelrequired = INT_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); }; 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..fd09dd6ec 100644 --- a/src/modules/m_override.cpp +++ b/src/modules/m_override.cpp @@ -25,50 +25,70 @@ #include "inspircd.h" - -/* $ModDesc: Provides support for allowing opers to override certain things. */ +#include "modules/invite.h" class ModuleOverride : public Module { bool RequireKey; bool NoisyOverride; + ChanModeReference topiclock; + ChanModeReference inviteonly; + ChanModeReference key; + ChanModeReference limit; + Invite::API invapi; - static bool IsOverride(unsigned int userlevel, const std::string& modeline) + 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; - + ModeHandler* mh = i->mh; if (mh->GetLevelRequired() > 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() + : 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)); } - 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) @@ -80,11 +100,11 @@ class ModuleOverride : public Module } - 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 +116,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 +131,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() >= ConvToInt(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..3050dba0b 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,17 +72,21 @@ 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; } @@ -102,7 +96,7 @@ class ModulePassForward : public Module tmp.clear(); FormatStr(tmp,forwardcmd, user); - ServerInstance->Parser->ProcessBuffer(tmp,user); + ServerInstance->Parser.ProcessBuffer(tmp,user); } }; diff --git a/src/modules/m_password_hash.cpp b/src/modules/m_password_hash.cpp index 98462780b..09cdbb402 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 */ @@ -36,34 +34,39 @@ class CommandMkpasswd : public Command void MakeHash(User* user, const std::string& algo, const std::string& stuff) { - if (algo.substr(0,5) == "hmac-") + if (!algo.compare(0, 5, "hmac-", 5)) { - std::string type = algo.substr(5); + std::string type(algo, 5); HashProvider* hp = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + type); if (!hp) { - user->WriteServ("NOTICE %s :Unknown hash type", user->nick.c_str()); + user->WriteNotice("Unknown hash type"); + return; + } + + if (hp->IsKDF()) + { + user->WriteNotice(type + " does not support HMAC"); return; } - std::string salt = ServerInstance->GenRandomStr(6, false); + + std::string salt = ServerInstance->GenRandomStr(hp->out_size, false); std::string target = hp->hmac(salt, stuff); 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()); + user->WriteNotice(algo + " hashed password for " + stuff + " is " + 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()); + std::string hexsum = hp->Generate(stuff); + user->WriteNotice(algo + " hashed password for " + stuff + " is " + hexsum); } else { - user->WriteServ("NOTICE %s :Unknown hash type", user->nick.c_str()); + user->WriteNotice("Unknown hash type"); } } @@ -84,24 +87,21 @@ class ModuleOperHash : public Module { } - void init() + ModResult OnPassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype) CXX11_OVERRIDE { - /* 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) - { - 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,19 +120,18 @@ 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); } diff --git a/src/modules/m_pbkdf2.cpp b/src/modules/m_pbkdf2.cpp new file mode 100644 index 000000000..314f6b836 --- /dev/null +++ b/src/modules/m_pbkdf2.cpp @@ -0,0 +1,262 @@ +/* + * 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->getInt("iterations", 12288, 1); + unsigned int global_dkey_length = tag->getInt("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->getInt("iterations", global_iterations, 1); + pi->dkey_length = tag->getInt("length", global_dkey_length, 1, 1024); + } + } + } + + public: + ~ModulePBKDF2() + { + stdalgo::delete_all(providers); + } + + void Prioritize() CXX11_OVERRIDE + { + OnLoadModule(NULL); + } + + void OnLoadModule(Module* mod) CXX11_OVERRIDE + { + bool newProv = false; + // As the module doesn't tell us what ServiceProviders it has, let's iterate all (yay ...) the ServiceProviders + // Good thing people don't run loading and unloading those all the time + for (std::multimap<std::string, ServiceProvider*>::iterator i = ServerInstance->Modules->DataProviders.begin(); i != ServerInstance->Modules->DataProviders.end(); ++i) + { + ServiceProvider* provider = i->second; + + // Does the service belong to the new mod? + // In the case this is our first run (mod == NULL, continue anyway) + if (mod && provider->creator != mod) + continue; + + // Check if it's a hash provider + if (provider->name.compare(0, 5, "hash/")) + continue; + + HashProvider* hp = static_cast<HashProvider*>(provider); + + if (hp->IsKDF()) + continue; + + bool has_prov = false; + for (std::vector<PBKDF2Provider*>::const_iterator j = providers.begin(); j != providers.end(); ++j) + { + if ((*j)->provider == hp) + { + has_prov = true; + break; + } + } + if (has_prov) + continue; + + newProv = true; + + PBKDF2Provider* prov = new PBKDF2Provider(this, hp); + providers.push_back(prov); + ServerInstance->Modules->AddService(*prov); + } + + if (newProv) + GetConfig(); + } + + void OnUnloadModule(Module* mod) CXX11_OVERRIDE + { + for (std::vector<PBKDF2Provider*>::iterator i = providers.begin(); i != providers.end(); ) + { + PBKDF2Provider* item = *i; + if (item->provider->creator != mod) + { + ++i; + continue; + } + + ServerInstance->Modules->DelService(*item); + delete item; + i = providers.erase(i); + } + } + + 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..0f2a2ef6f 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) + { + 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; + 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..b14de9ff9 100644 --- a/src/modules/m_redirect.cpp +++ b/src/modules/m_redirect.cpp @@ -24,66 +24,51 @@ #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) { - 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(ERR_NOSUCHCHANNEL, parameter, "Invalid channel name"); + 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; + } + void SerializeParam(Channel* chan, const std::string* str, std::string& out) + { + out += *str; } }; @@ -92,75 +77,62 @@ class Redirect : public ModeHandler class AntiRedirect : public SimpleUserModeHandler { public: - AntiRedirect(Module* Creator) : SimpleUserModeHandler(Creator, "antiredirect", 'L') {} + AntiRedirect(Module* Creator) : SimpleUserModeHandler(Creator, "antiredirect", 'L') + { + if (!ServerInstance->Config->ConfValue("redirect")->getBool("antiredirect")) + DisableAutoRegister(); + } }; class ModuleRedirect : public Module { - Redirect re; AntiRedirect re_u; + ChanModeReference limitmode; bool UseUsermode; public: - ModuleRedirect() - : re(this), re_u(this) + : re(this) + , re_u(this) + , limitmode(this, "limit") { } - void init() + void init() CXX11_OVERRIDE { /* 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() >= ConvToInt(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 (UseUsermode && user->IsModeSet(re_u)) { - 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 +141,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..5872b5978 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(const std::vector<std::string>& parameters, User *user, 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. @@ -74,40 +76,48 @@ class RemoveBase : public Command /* Fix by brain - someone needs to learn to validate their input! */ if ((!target) || (target->registered != REG_ALL) || (!channel)) { - 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(channel ? username.c_str() : channame.c_str())); 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(482, 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. + * 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. + std::vector<std::string> 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 +130,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,25 +157,17 @@ class RemoveBase : public Command class CommandRemove : public RemoveBase { public: - CommandRemove(Module* Creator, bool& snk) - : RemoveBase(Creator, snk, "REMOVE") + CommandRemove(Module* Creator, bool& snk, ChanModeReference& nkm) + : RemoveBase(Creator, snk, nkm, "REMOVE") { - syntax = "<nick> <channel> [<reason>]"; - TRANSLATE4(TR_NICK, TR_TEXT, TR_TEXT, TR_END); + syntax = "<channel> <nick> [<reason>]"; + TRANSLATE3(TR_NICK, TR_TEXT, TR_TEXT); } CmdResult Handle (const std::vector<std::string>& parameters, User *user) { return HandleRMB(parameters, user, false); } - - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - User* dest = ServerInstance->FindNick(parameters[0]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; - } }; /** Handle /FPART @@ -174,67 +175,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); + TRANSLATE3(TR_TEXT, TR_NICK, TR_TEXT); } CmdResult Handle (const std::vector<std::string>& parameters, User *user) { return HandleRMB(parameters, user, true); } - - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) - { - User* dest = ServerInstance->FindNick(parameters[1]); - if (dest) - return ROUTE_OPT_UCAST(dest->server); - return ROUTE_LOCALONLY; - } }; class ModuleRemove : public Module { + ChanModeReference nokicksmode; CommandRemove cmd1; CommandFpart cmd2; bool supportnokicks; - public: - ModuleRemove() : cmd1(this, supportnokicks), cmd2(this, supportnokicks) - { - } - - void init() + ModuleRemove() + : nokicksmode(this, "nokick") + , cmd1(this, supportnokicks, nokicksmode) + , cmd2(this, supportnokicks, nokicksmode) { - 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) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - output.append(" REMOVE"); + tokens["REMOVE"]; } - virtual void OnRehash(User* user) + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { - supportnokicks = ServerInstance->Config->ConfValue("remove")->getBool("supportnokicks"); + ConfigTag* tag = ServerInstance->Config->ConfValue("remove"); + supportnokicks = tag->getBool("supportnokicks"); + cmd1.protectedrank = cmd2.protectedrank = tag->getInt("protectedrank", 50000); } - virtual ~ModuleRemove() - { - } - - 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..6c34648e0 --- /dev/null +++ b/src/modules/m_repeat.cpp @@ -0,0 +1,409 @@ +/* + * 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) + { + ChannelSettings settings; + if (!ParseSettings(source, parameter, settings)) + { + source->WriteNotice("*** Invalid syntax. Syntax is {[~*]}[lines]:[time]{:[difference]}{:[backlog]}"); + return MODEACTION_DENY; + } + + if ((settings.Backlog > 0) && (settings.Lines > settings.Backlog)) + { + source->WriteNotice("*** You can't set needed lines higher than backlog"); + return MODEACTION_DENY; + } + + LocalUser* localsource = IS_LOCAL(source); + if ((localsource) && (!ValidateSettings(localsource, 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->getInt("maxlines", 20); + ms.MaxBacklog = conf->getInt("maxbacklog", 20); + ms.MaxSecs = conf->getDuration("maxtime", conf->getInt("maxsecs", 0)); + + ms.MaxDiff = conf->getInt("maxdistance", 50); + if (ms.MaxDiff > 100) + ms.MaxDiff = 100; + + unsigned int newsize = conf->getInt("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, const ChannelSettings& settings) + { + if (settings.Backlog && !ms.MaxBacklog) + { + source->WriteNotice("*** The server administrator has disabled backlog matching"); + return false; + } + + if (settings.Diff) + { + if (settings.Diff > ms.MaxDiff) + { + if (ms.MaxDiff == 0) + source->WriteNotice("*** The server administrator has disabled matching on edit distance"); + else + source->WriteNotice("*** The distance you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxDiff)); + return false; + } + + if (ms.MaxLines && settings.Lines > ms.MaxLines) + { + source->WriteNotice("*** The line number you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxLines)); + return false; + } + + if (ms.MaxSecs && settings.Seconds > ms.MaxSecs) + { + source->WriteNotice("*** The seconds you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxSecs)); + 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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE + { + if (target_type != TYPE_CHANNEL || !IS_LOCAL(user)) + return MOD_RES_PASSTHRU; + + Channel* chan = reinterpret_cast<Channel*>(dest); + 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; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (user, chan, "repeat")); + if (res == MOD_RES_ALLOW) + return MOD_RES_PASSTHRU; + + if (rm.MatchLine(memb, settings, 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->dhost); + 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..8ca531ed5 100644 --- a/src/modules/m_restrictmsg.cpp +++ b/src/modules/m_restrictmsg.cpp @@ -21,29 +21,10 @@ #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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if ((target_type == TYPE_USER) && (IS_LOCAL(user))) { @@ -54,11 +35,11 @@ class ModuleRestrictMsg : public Module // (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 index 04c27e83d..8d3131bc0 100644 --- a/src/modules/m_ripemd160.cpp +++ b/src/modules/m_ripemd160.cpp @@ -56,25 +56,15 @@ */ -/* $ModDesc: Allows for RIPEMD-160 encrypted oper passwords */ - /* macro definitions */ #include "inspircd.h" -#ifdef HAS_STDINT -#include <stdint.h> -#endif -#include "hash.h" +#include "modules/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 +typedef uint8_t byte; +typedef uint32_t dword; /* collect four bytes into one word: */ #define BYTES_TO_DWORD(strptr) \ @@ -167,7 +157,6 @@ class RIProv : public HashProvider { 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]; @@ -176,7 +165,6 @@ class RIProv : public HashProvider } else { - ServerInstance->Logs->Log("m_ripemd160.so", DEBUG, "initialize with default mdbuf"); MDbuf[0] = 0x67452301UL; MDbuf[1] = 0xefcdab89UL; MDbuf[2] = 0x98badcfeUL; @@ -417,7 +405,6 @@ class RIProv : public HashProvider 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 */ @@ -447,18 +434,13 @@ class RIProv : public HashProvider return (byte *)hashcode; } public: - std::string sum(const std::string& data) + std::string GenerateRaw(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) {} + RIProv(Module* m) : HashProvider(m, "ripemd160", 20, 64) {} }; class ModuleRIPEMD160 : public Module @@ -467,15 +449,12 @@ class ModuleRIPEMD160 : public Module RIProv mr; ModuleRIPEMD160() : mr(this) { - ServerInstance->Modules->AddService(mr); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { 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..eb54e56b7 100644 --- a/src/modules/m_rline.cpp +++ b/src/modules/m_rline.cpp @@ -20,10 +20,8 @@ */ -/* $ModDesc: RLINE: Regexp user banning. */ - #include "inspircd.h" -#include "m_regex.h" +#include "modules/regex.h" #include "xline.h" static bool ZlineOnMatch = false; @@ -60,11 +58,13 @@ class RLine : public XLine bool Matches(User *u) { - 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->host + " " + u->fullname; + const std::string ip = u->nick + "!" + u->ident + "@" + u->GetIPString() + " " + u->fullname; + return (regex->Matches(host) || regex->Matches(ip)); } bool Matches(const std::string &compare) @@ -79,7 +79,7 @@ class RLine : public XLine 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 +90,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() { - return matchtext.c_str(); + return matchtext; } std::string matchtext; @@ -116,7 +110,7 @@ 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) @@ -129,10 +123,6 @@ class RLineFactory : public XLineFactory return new RLine(set_time, duration, source, reason, xline_specific_mask, rxfactory); } - - ~RLineFactory() - { - } }; /** Handle /RLINE @@ -156,7 +146,7 @@ class CommandRLine : public Command { // 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 +155,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 +169,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 +178,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,7 +190,7 @@ 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."); } } @@ -218,7 +208,6 @@ class CommandRLine : public Command class ModuleRLine : public Module { - private: dynamic_reference<RegexFactory> rxfactory; RLineFactory f; CommandRLine r; @@ -233,29 +222,23 @@ class ModuleRLine : public Module { } - 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 +252,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 +285,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 +311,7 @@ class ModuleRLine : public Module } } - virtual void OnBackgroundTimer(time_t curtime) + void OnBackgroundTimer(time_t curtime) CXX11_OVERRIDE { if (added_zline) { @@ -337,7 +320,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 +334,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..37c6e62ff --- /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(const std::vector<std::string> ¶meters, User *user) + { + 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()) + { + 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..8bf865319 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) { - 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) { - 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..81a74502b 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) { 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,20 +58,16 @@ 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; @@ -83,10 +75,7 @@ class CommandSakick : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..394cea722 100644 --- a/src/modules/m_samode.cpp +++ b/src/modules/m_samode.cpp @@ -20,8 +20,6 @@ */ -/* $ModDesc: Provides command SAMODE to allow opers to change modes on channels and users */ - #include "inspircd.h" /** Handle /SAMODE @@ -33,7 +31,7 @@ class CommandSamode : public Command 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; } @@ -44,16 +42,35 @@ class CommandSamode : public Command 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); + 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()); + CmdResult result = ServerInstance->Parser.CallHandler("MODE", parameters, user); this->active = false; + + if (result == CMD_SUCCESS) + { + // If lastparse is empty and the MODE command handler returned CMD_SUCCESS 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 can also result in CMD_SUCCESS, but + // that is not possible with /SAMODE because we require at least 2 parameters. + const std::string& lastparse = ServerInstance->Modes.GetLastParse(); + ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + (lastparse.empty() ? irc::stringjoiner(parameters) : lastparse)); + } + return CMD_SUCCESS; } }; @@ -67,32 +84,22 @@ 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 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..c9ceba78e 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,8 +29,8 @@ 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) @@ -42,21 +40,21 @@ class CommandSanick : public Command /* 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]); } @@ -81,10 +79,7 @@ class CommandSanick : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..b51316dc5 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) { + 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,33 +65,14 @@ 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; @@ -92,10 +80,7 @@ class CommandSapart : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - 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..9f700ec5f 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) { 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) { - 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..eedf968b4 100644 --- a/src/modules/m_sasl.cpp +++ b/src/modules/m_sasl.cpp @@ -19,23 +19,141 @@ #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/spanningtree.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 SpanningTreeEventListener +{ + 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) + : SpanningTreeEventListener(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 parameterlist& parameters) { - if (!ServerInstance->PI->SendEncapsulatedData(params)) + parameterlist params(parameters.size() + 3); + 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)); } } @@ -46,95 +164,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"); - } + params.push_back(user->host); + 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"); 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(); + const std::string fp = SSLClientCert::GetFingerprint(&user->eh); + if (fp.size()) + params.push_back(fp); - if (fp.size()) - params.push_back(fp); - } - - SendSASL(params); + SendSASL(user, "*", 'S', params); } SaslResult GetSaslResult(const std::string &result_) @@ -172,46 +230,32 @@ class SaslAuthenticator 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 +270,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 +286,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(const std::vector<std::string>& parameters, LocalUser* user) { - /* 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])); @@ -289,10 +343,10 @@ class CommandSASL : public Command CmdResult Handle(const std::vector<std::string>& parameters, User *user) { - 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; } @@ -318,52 +372,46 @@ class CommandSASL : public Command class ModuleSASL : public Module { SimpleExtItem<SaslAuthenticator> authExt; - GenericCap cap; + ServerTracker servertracker; + SASLCap cap; CommandAuthenticate auth; CommandSASL sasl; + Events::ModuleEventProvider sasleventprov; + 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") { + saslevprov = &sasleventprov; } - 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", "*"); + servertracker.Reset(); } - void OnUserConnect(LocalUser *user) + void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string& extdata) CXX11_OVERRIDE { - SaslAuthenticator *sasl_ = authExt.get(user); - if (sasl_) - { - sasl_->Abort(); - authExt.unset(user); - } + if ((target == NULL) && (extname == "saslmechlist")) + cap.SetMechlist(extdata); } - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR); } - - void OnEvent(Event &ev) - { - cap.HandleEvent(ev); - } }; MODULE_INIT(ModuleSASL) diff --git a/src/modules/m_satopic.cpp b/src/modules/m_satopic.cpp index ae1c19d91..f45d9c8cd 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,7 +26,7 @@ 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) @@ -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::NoSuchNick(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..e36b9e403 100644 --- a/src/modules/m_securelist.cpp +++ b/src/modules/m_securelist.cpp @@ -21,31 +21,18 @@ #include "inspircd.h" -/* $ModDesc: Disallows /LIST for recently connected clients to hinder spam bots */ - 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)); - } - 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 +40,7 @@ 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); + WaitTime = ServerInstance->Config->ConfValue("securelist")->getDuration("waittime", 60); } @@ -61,13 +48,13 @@ 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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++) @@ -75,20 +62,20 @@ class ModuleSecureList : public Module 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..ac5730374 100644 --- a/src/modules/m_services_account.cpp +++ b/src/modules/m_services_account.cpp @@ -22,10 +22,19 @@ */ -/* $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" + +enum +{ + // 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 */ @@ -40,15 +49,15 @@ class Channel_r : public ModeHandler 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(500, "Only a server may modify the +r channel mode"); } return MODEACTION_DENY; } @@ -66,15 +75,15 @@ class User_r : public ModeHandler { 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(500, "Only a server may modify the +r user mode"); } return MODEACTION_DENY; } @@ -104,86 +113,96 @@ class AChannel_M : public SimpleChannelModeHandler AChannel_M(Module* Creator) : SimpleChannelModeHandler(Creator, "regmoderated", 'M') { } }; -class ModuleServicesAccount : public Module +class AccountExtItemImpl : public AccountExtItem { - AChannel_R m1; - AChannel_M m2; - AUser_R m3; - Channel_r m4; - User_r m5; - AccountExtItem accountname; - bool checking_ban; + Events::ModuleEventProvider eventprov; - static bool ReadCGIIRCExt(const char* extname, User* user, const std::string*& out) + public: + AccountExtItemImpl(Module* mod) + : AccountExtItem("accountname", ExtensionItem::EXT_USER, mod) + , eventprov(mod, "event/account") { - ExtensionItem* wiext = ServerInstance->Extensions.GetItem(extname); - if (!wiext) - return false; + } - if (wiext->creator->ModuleSourceFile != "m_cgiirc.so") - return false; + void unserialize(SerializeFormat format, Extensible* container, const std::string& value) + { + User* user = static_cast<User*>(container); - StringExtItem* stringext = static_cast<StringExtItem*>(wiext); - std::string* addr = stringext->get(user); - if (!addr) - return false; + StringExtItem::unserialize(format, container, value); - out = addr; - return true; - } + // If we are being reloaded then don't send the numeric or run the event + if (format == FORMAT_INTERNAL) + return; - public: - ModuleServicesAccount() : m1(this), m2(this), m3(this), m4(this), m5(this), - accountname("accountname", this), checking_ban(false) - { + 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())); + } + } + + FOREACH_MOD_CUSTOM(eventprov, AccountEventListener, OnAccountChange, (user, value)); } +}; - void init() +class ModuleServicesAccount : public Module, public Whois::EventListener +{ + CheckExemption::EventProvider exemptionprov; + AChannel_R m1; + AChannel_M m2; + AUser_R m3; + Channel_r m4; + User_r m5; + AccountExtItemImpl accountname; + bool checking_ban; + public: + ModuleServicesAccount() + : Whois::EventListener(this) + , exemptionprov(this) + , m1(this), m2(this), m3(this), 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(330, *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(307, "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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; @@ -194,12 +213,13 @@ class ModuleServicesAccount : public Module if (target_type == TYPE_CHANNEL) { Channel* c = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,c,"regmoderated"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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; } } @@ -207,17 +227,17 @@ class ModuleServicesAccount : public Module { User* u = (User*)dest; - 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 +273,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) - { - 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) + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE { - 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 +293,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..97670237b 100644 --- a/src/modules/m_servprotect.cpp +++ b/src/modules/m_servprotect.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" -/* $ModDesc: Provides usermode +k to protect services from kicks, kills and mode changes. */ - /** Handles user mode +k */ class ServProtectMode : public ModeHandler @@ -44,47 +42,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() - { - 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() + : Whois::EventListener(this) + , Whois::LineEventListener(this) + , bm(this) { } - 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(310, "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 +87,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 +99,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(485, 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..e2a5dc281 100644 --- a/src/modules/m_sethost.cpp +++ b/src/modules/m_sethost.cpp @@ -21,41 +21,37 @@ #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) { 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) { - 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) + { + user->WriteNotice("*** SETHOST: Host too long"); + return CMD_FAILURE; + } + + for (std::string::const_iterator x = parameters[0].begin(); x != parameters[0].end(); x++) { if (!hostmap[(const unsigned char)*x]) { - user->WriteServ("NOTICE "+user->nick+" :*** SETHOST: Invalid characters in hostname"); + user->WriteNotice("*** SETHOST: Invalid characters in hostname"); return CMD_FAILURE; } } - if (len > 64) - { - user->WriteServ("NOTICE %s :*** SETHOST: Host too long",user->nick.c_str()); - 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); return CMD_SUCCESS; @@ -70,21 +66,14 @@ class ModuleSetHost : public Module { CommandSethost cmd; char hostmap[256]; + public: ModuleSetHost() : cmd(this, hostmap) { } - void init() - { - 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"); @@ -93,15 +82,10 @@ class ModuleSetHost : public Module hostmap[(unsigned char)*n] = 1; } - 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..93dd4c332 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) { 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..6302b3654 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(const std::vector<std::string>& parameters, LocalUser* user) { - 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..0e71840f7 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 @@ -32,18 +30,17 @@ class CommandSetname : public Command { allow_empty_last_param = false; syntax = "<new-gecos>"; - TRANSLATE2(TR_TEXT, TR_END); } CmdResult Handle (const std::vector<std::string>& parameters, User *user) { if (parameters[0].size() > ServerInstance->Config->Limits.MaxGecos) { - user->WriteServ("NOTICE %s :*** SETNAME: GECOS too long", user->nick.c_str()); + user->WriteNotice("*** SETNAME: GECOS too long"); return CMD_FAILURE; } - if (user->ChangeName(parameters[0].c_str())) + if (user->ChangeName(parameters[0])) { ServerInstance->SNO->WriteGlobalSno('a', "%s used SETNAME to change their GECOS to '%s'", user->nick.c_str(), parameters[0].c_str()); } @@ -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..798539d91 --- /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) + { + 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..48bfc0041 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) { 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..57c501e90 --- /dev/null +++ b/src/modules/m_showfile.cpp @@ -0,0 +1,170 @@ +/* + * 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" + +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(const std::vector<std::string>& parameters, User* user) 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 + { + const char* msgcmd = (method == SF_MSG ? "PRIVMSG" : "NOTICE"); + for (file_cache::const_iterator i = contents.begin(); i != contents.end(); ++i) + { + const std::string& line = *i; + user->WriteCommand(msgcmd, ":" + line); + } + } + 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->getInt("intronumeric", RPL_RULESTART, 0, 999); + textnumeric = tag->getInt("numeric", RPL_RULES, 0, 999); + endnumeric = tag->getInt("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; + if (tag->getBool("colors")) + 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..3cb85f3fb 100644 --- a/src/modules/m_showwhois.cpp +++ b/src/modules/m_showwhois.cpp @@ -23,16 +23,19 @@ #include "inspircd.h" -/* $ModDesc: Allows opers to set +W to see when a user uses WHOIS on them */ - /** 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,9 +49,9 @@ 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 + "@" + + (dest->HasPrivPermission("users/auspex") ? src->host : src->dhost) + + ") did a /whois on you"); } CmdResult Handle(const std::vector<std::string> ¶meters, User *user) @@ -66,49 +69,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)) @@ -118,14 +114,11 @@ class ModuleShowwhois : public Module else { std::vector<std::string> params; - params.push_back(dest->server); - params.push_back("WHOISNOTICE"); 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 8bf4d30e7..28faf898b 100644 --- a/src/modules/m_shun.cpp +++ b/src/modules/m_shun.cpp @@ -23,8 +23,6 @@ #include "inspircd.h" #include "xline.h" -/* $ModDesc: Provides the /SHUN command, which stops a user from executing all except configured commands. */ - class Shun : public XLine { public: @@ -36,19 +34,19 @@ public: { } - ~Shun() - { - } - bool Matches(User *u) { // E: overrides shun - if (u->exempt) + LocalUser* lu = IS_LOCAL(u); + if (lu && lu->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; + if (InspIRCd::MatchCIDR(u->GetIPString(), matchtext, ascii_case_insensitive_map)) + return true; + return false; } @@ -59,15 +57,9 @@ public: return false; } - void DisplayExpiry() + const std::string& Displayable() { - 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(); + return matchtext; } }; @@ -107,7 +99,7 @@ class CommandShun : public Command /* '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,18 +112,18 @@ class CommandShun : public Command } else { - user->WriteServ("NOTICE %s :*** Shun %s not found in list, try /stats H.",user->nick.c_str(),target.c_str()); + user->WriteNotice("*** Shun " + target + " 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 @@ -151,7 +143,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()); } @@ -159,7 +151,7 @@ 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; } } @@ -179,7 +171,7 @@ class ModuleShun : public Module { CommandShun cmd; ShunFactory f; - std::set<std::string> ShunEnabledCommands; + insp::flat_set<std::string> ShunEnabledCommands; bool NotifyOfShun; bool affectopers; @@ -188,38 +180,33 @@ class ModuleShun : public Module { } - 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"); @@ -242,7 +229,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, std::vector<std::string>& parameters, LocalUser* user, bool validated, const std::string &original_line) CXX11_OVERRIDE { if (validated) return MOD_RES_PASSTHRU; @@ -253,18 +240,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; } @@ -283,11 +268,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..cb065d2fc 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 */ @@ -66,7 +64,7 @@ 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) @@ -78,7 +76,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,7 +85,7 @@ class CommandSVSSilence : public Command if (IS_LOCAL(u)) { - ServerInstance->Parser->CallHandler("SILENCE", std::vector<std::string>(parameters.begin() + 1, parameters.end()), u); + ServerInstance->Parser.CallHandler("SILENCE", std::vector<std::string>(parameters.begin() + 1, parameters.end()), u); } return CMD_SUCCESS; @@ -95,10 +93,7 @@ class CommandSVSSilence : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - User* target = ServerInstance->FindNick(parameters[0]); - if (target) - return ROUTE_OPT_UCAST(target->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -108,11 +103,11 @@ 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) @@ -127,17 +122,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(271, user->nick, c->first, decomppattern); } } - user->WriteNumeric(272, "%s :End of Silence List",user->nick.c_str()); + user->WriteNumeric(272, "End of Silence List"); return CMD_SUCCESS; } else if (parameters.size() > 0) { // 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 +144,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 +167,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(950, user->nick, InspIRCd::Format("Removed %s %s from silence list", mask.c_str(), decomppattern.c_str())); if (!sl->size()) { ext.unset(user); @@ -185,7 +180,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(952, user->nick, InspIRCd::Format("%s %s does not exist on your silence list", mask.c_str(), decomppattern.c_str())); } else if (action == '+') { @@ -198,29 +193,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(952, 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(952, 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(951, user->nick, InspIRCd::Format("Added %s %s to silence list", mask.c_str(), decomppattern.c_str())); return CMD_SUCCESS; } } @@ -293,6 +288,7 @@ class CommandSilence : public Command class ModuleSilence : public Module { unsigned int maxsilence; + bool ExemptULine; CommandSilence cmdsilence; CommandSVSSilence cmdsvssilence; public: @@ -302,36 +298,29 @@ 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); - - Implementation eventlist[] = { I_OnRehash, I_On005Numeric, I_OnUserPreNotice, I_OnUserPreMessage, I_OnUserPreInvite }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - } + ConfigTag* tag = ServerInstance->Config->ConfValue("silence"); - void OnRehash(User* user) - { - maxsilence = ServerInstance->Config->ConfValue("silence")->getInt("maxentries", 32); + maxsilence = tag->getInt("maxentries", 32); if (!maxsilence) maxsilence = 32; + + ExemptULine = tag->getBool("exemptuline", true); } - void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - // we don't really have a limit... - output = output + " ESILENCE SILENCE=" + ConvToStr(maxsilence); + tokens["ESILENCE"]; + tokens["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 +332,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, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (target_type == TYPE_USER && IS_LOCAL(((User*)dest))) { - return MatchPattern((User*)dest, user, silence_type); + return MatchPattern((User*)dest, user, ((msgtype == MSG_PRIVMSG) ? SILENCE_PRIVATE : SILENCE_NOTICE)); } else if (target_type == TYPE_CHANNEL) { Channel* chan = (Channel*)dest; - if (chan) - { - this->OnBuildExemptList((silence_type == SILENCE_PRIVATE ? MSG_PRIVMSG : MSG_NOTICE), chan, user, status, exempt_list, ""); - } + BuildExemptList(msgtype, chan, user, exempt_list); } 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 +368,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..1bf847604 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, std::vector<std::string>& 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; + 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('d',"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..7c514c49e 100644 --- a/src/modules/m_spanningtree/away.cpp +++ b/src/modules/m_spanningtree/away.cpp @@ -21,32 +21,38 @@ #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, std::vector<std::string>& params) { - User* u = ServerInstance->FindNick(prefix); - if ((!u) || (IS_SERVER(u))) - return true; if (params.size()) { - FOREACH_MOD(I_OnSetAway, OnSetAway(u, params[params.size() - 1])); + FOREACH_MOD(OnSetAway, (u, params.back())); 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(); } else { - FOREACH_MOD(I_OnSetAway, OnSetAway(u, "")); + FOREACH_MOD(OnSetAway, (u, "")); u->awaymsg.clear(); } - Utils->DoOneToAllButSender(prefix,"AWAY",params,u->server); - return true; + return CMD_SUCCESS; +} + +CommandAway::Builder::Builder(User* user) + : CmdBuilder(user, "AWAY") +{ + push_int(user->awaytime).push_last(user->awaymsg); +} + +CommandAway::Builder::Builder(User* user, const std::string& msg) + : CmdBuilder(user, "AWAY") +{ + if (!msg.empty()) + push_int(ServerInstance->Time()).push_last(msg); } diff --git a/src/modules/m_spanningtree/cachetimer.h b/src/modules/m_spanningtree/cachetimer.h index bad1b7419..cffbe3578 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); }; - -#endif diff --git a/src/modules/m_spanningtree/capab.cpp b/src/modules/m_spanningtree/capab.cpp index 0ab815fef..7f9f9edb7 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 irc::stringjoiner(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,15 @@ 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 this key + if (proto_version == 1202) + extra.append(" PROTOCOL="+ConvToStr(ProtocolVersion)); this->WriteLine("CAPAB CAPABILITIES " /* Preprocessor does this one. */ ":NICKMAX="+ConvToStr(ServerInstance->Config->Limits.NickMax)+ @@ -153,18 +177,19 @@ void TreeSocket::SendCapabilities(int phase) " MAXKICK="+ConvToStr(ServerInstance->Config->Limits.MaxKick)+ " MAXGECOS="+ConvToStr(ServerInstance->Config->Limits.MaxGecos)+ " 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"); } @@ -209,7 +234,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 +260,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 +298,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 +319,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 +341,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 +377,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 +399,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else { - capab->ModuleList.push_back(proto_version > 1201 ? ' ' : ','); + capab->ModuleList.push_back(' '); capab->ModuleList.append(params[1]); } } @@ -389,12 +433,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..8eea02915 100644 --- a/src/modules/m_spanningtree/commands.h +++ b/src/modules/m_spanningtree/commands.h @@ -17,126 +17,383 @@ */ -#ifndef M_SPANNINGTREE_COMMANDS_H -#define M_SPANNINGTREE_COMMANDS_H +#pragma once -#include "main.h" +#include "servercommand.h" +#include "commandbuilder.h" +#include "remoteuser.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(const std::vector<std::string>& parameters, User* user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; 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(const std::vector<std::string>& parameters, User* user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -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); + CommandMap(Module* Creator); + CmdResult Handle(const std::vector<std::string>& parameters, User* user); RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -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); + CommandSVSJoin(Module* Creator) : ServerCommand(Creator, "SVSJOIN", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& params); RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -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); + CommandSVSPart(Module* Creator) : ServerCommand(Creator, "SVSPART", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& params); RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -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, std::vector<std::string>& params); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -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, std::vector<std::string>& params); + + 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, std::vector<std::string>& 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, std::vector<std::string>& 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, std::vector<std::string>& params); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { 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, std::vector<std::string>& params); +}; + +class CommandFTopic : public ServerCommand +{ + public: + CommandFTopic(Module* Creator) : ServerCommand(Creator, "FTOPIC", 4, 5) { } + CmdResult Handle(User* user, std::vector<std::string>& params); + + class Builder : public CmdBuilder + { + public: + Builder(Channel* chan); + Builder(User* user, Channel* chan); + }; +}; + +class CommandFHost : public UserOnlyServerCommand<CommandFHost> +{ + public: + CommandFHost(Module* Creator) : UserOnlyServerCommand<CommandFHost>(Creator, "FHOST", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); +}; + +class CommandFIdent : public UserOnlyServerCommand<CommandFIdent> +{ + public: + CommandFIdent(Module* Creator) : UserOnlyServerCommand<CommandFIdent>(Creator, "FIDENT", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); }; -class CommandFMode : public Command + +class CommandFName : public UserOnlyServerCommand<CommandFName> { 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; } + CommandFName(Module* Creator) : UserOnlyServerCommand<CommandFName>(Creator, "FNAME", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); }; -class CommandFTopic : public Command + +class CommandIJoin : public UserOnlyServerCommand<CommandIJoin> +{ + public: + CommandIJoin(Module* Creator) : UserOnlyServerCommand<CommandIJoin>(Creator, "IJOIN", 2) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); +}; + +class CommandResync : public ServerOnlyServerCommand<CommandResync> { 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; } + CommandResync(Module* Creator) : ServerOnlyServerCommand<CommandResync>(Creator, "RESYNC", 1) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_LOCALONLY; } }; -class CommandFHost : public Command + +class SpanningTree::CommandAway : public UserOnlyServerCommand<SpanningTree::CommandAway> { 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; } + CommandAway(Module* Creator) : UserOnlyServerCommand<SpanningTree::CommandAway>(Creator, "AWAY", 0, 2) { } + CmdResult HandleRemote(::RemoteUser* user, std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + Builder(User* user, const std::string& msg); + }; }; -class CommandFIdent : public Command + +class XLine; +class CommandAddLine : public ServerCommand { 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; } + CommandAddLine(Module* Creator) : ServerCommand(Creator, "ADDLINE", 6, 6) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(XLine* xline, User* user = ServerInstance->FakeClient); + }; }; -class CommandFName : public Command + +class CommandDelLine : public ServerCommand { 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; } + CommandDelLine(Module* Creator) : ServerCommand(Creator, "DELLINE", 2, 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); +}; + +class CommandEncap : public ServerCommand +{ + public: + CommandEncap(Module* Creator) : ServerCommand(Creator, "ENCAP", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +class CommandIdle : public UserOnlyServerCommand<CommandIdle> +{ + public: + CommandIdle(Module* Creator) : UserOnlyServerCommand<CommandIdle>(Creator, "IDLE", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { 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, std::vector<std::string>& parameters); +}; + +class SpanningTree::CommandPing : public ServerCommand +{ + public: + CommandPing(Module* Creator) : ServerCommand(Creator, "PING", 1) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { 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, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { 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, std::vector<std::string>& parameters); +}; + +class SpanningTree::CommandServer : public ServerOnlyServerCommand<SpanningTree::CommandServer> +{ + static void HandleExtra(TreeServer* newserver, const std::vector<std::string>& params); + + public: + CommandServer(Module* Creator) : ServerOnlyServerCommand<SpanningTree::CommandServer>(Creator, "SERVER", 3) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& 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, std::vector<std::string>& parameters); +}; + +class CommandSNONotice : public ServerCommand +{ + public: + CommandSNONotice(Module* Creator) : ServerCommand(Creator, "SNONOTICE", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); +}; + +class CommandEndBurst : public ServerOnlyServerCommand<CommandEndBurst> +{ + public: + CommandEndBurst(Module* Creator) : ServerOnlyServerCommand<CommandEndBurst>(Creator, "ENDBURST") { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); +}; + +class CommandSInfo : public ServerOnlyServerCommand<CommandSInfo> +{ + public: + CommandSInfo(Module* Creator) : ServerOnlyServerCommand<CommandSInfo>(Creator, "SINFO", 2) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& 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, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + + 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 +401,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..2436e74f8 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> ...] :gecos + // 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(std::vector<std::string>& 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, std::vector<std::string>& 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; + + parameterlist 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.GetToken(srcstr); + srcstr.erase(0, 1); + + std::string token; + ts.GetToken(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.GetToken(token); + + // Rest of the tokens are the numeric parameters, add them to NUM + while (ts.GetToken(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.GetToken(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.GetToken(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..f790dc885 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, std::vector<std::string>& 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..8059d2a39 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, std::vector<std::string>& 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]; + parameterlist 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 std::vector<std::string>& 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..c292373b3 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, std::vector<std::string>& 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,276 @@ 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) + /* 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::tokenstream 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("TS for %s changed from %lu to %lu", newname.c_str(), (unsigned long) chan->age, (unsigned long) TS)); + + // 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..e6f49c5b9 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, std::vector<std::string>& 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..de72d162a 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, std::vector<std::string>& 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..ad58e52f0 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, std::vector<std::string>& 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..c2dbcf7f5 --- /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, std::vector<std::string>& 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, std::vector<std::string>& 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..632982623 100644 --- a/src/modules/m_spanningtree/link.h +++ b/src/modules/m_spanningtree/link.h @@ -18,20 +18,19 @@ */ -#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; std::string SendPass; std::string RecvPass; std::string Fingerprint; - std::string AllowMask; + std::vector<std::string> AllowMasks; bool HiddenFromStats; std::string Hook; int Timeout; @@ -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 967b577b1..2d76102bc 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/spanningtree.h" -#include "cachetimer.h" #include "resolvers.h" #include "main.h" #include "utils.h" @@ -35,60 +34,67 @@ #include "link.h" #include "treesocket.h" #include "commands.h" -#include "protocolinterface.h" +#include "translate.h" ModuleSpanningTree::ModuleSpanningTree() - : KeepNickTS(false) + : rconnect(this), rsquit(this), map(this) + , commands(this) + , currmembid(0) + , eventprov(this, "event/spanningtree") + , 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 +104,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()); -} - -int ModuleSpanningTree::CountServs() -{ - return Utils->serverlist.size(); + 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())); } void ModuleSpanningTree::HandleLinks(const std::vector<std::string>& 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 +153,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,13 +194,12 @@ 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(),':')) { in6_addr n; @@ -282,15 +209,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 +229,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.sa.sa_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); } } @@ -356,224 +296,130 @@ void ModuleSpanningTree::DoConnectTimeout(time_t curtime) ModResult ModuleSpanningTree::HandleVersion(const std::vector<std::string>& 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) { 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::OnUserMessage(User* user, void* dest, int target_type, const std::string& text, char status, const CUList& exempt_list, MessageType msgtype) { - /* Server origin */ - if (user == NULL) + if (!IS_LOCAL(user)) return; + const char* message_type = (msgtype == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); if (target_type == 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 = (User*) dest; + 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(text); + params.Unicast(d); } } 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)+" PRIVMSG "+cname+" :"+text); - } - } - } + Utils->SendChannelMessage(user->uuid, (Channel*)dest, text, status, exempt_list, message_type); } 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,"PRIVMSG",par); - } + char* target = (char*) dest; + CmdBuilder par(user, message_type); + par.push_back(target); + par.push_last(text); + par.Broadcast(); } } void ModuleSpanningTree::OnBackgroundTimer(time_t curtime) { AutoConnectServers(curtime); - DoPingChecks(curtime); DoConnectTimeout(curtime); } @@ -582,25 +428,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 +441,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,9 +479,7 @@ 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) @@ -645,9 +487,7 @@ void ModuleSpanningTree::OnChangeName(User* user, const std::string &gecos) 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(gecos).Broadcast(); } void ModuleSpanningTree::OnChangeIdent(User* user, const std::string &ident) @@ -655,101 +495,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 +573,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 +609,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 +625,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(), SpanningTreeEventListener, 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 +667,96 @@ 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(ServerInstance->Config->ServerName); - 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) { 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); - } + CommandAway::Builder(user, awaymsg).Broadcast(); return MOD_RES_PASSTHRU; } -void ModuleSpanningTree::OnRequest(Request& request) -{ - if (!strcmp(request.id, "rehash")) - Utils->Rehash(); -} - -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, const std::string& output_mode) { - 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(output_mode); + 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(output_mode); + 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 +768,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 17adc9287..00cbd3dcd 100644 --- a/src/modules/m_spanningtree/main.h +++ b/src/modules/m_spanningtree/main.h @@ -21,11 +21,14 @@ */ -#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 "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 +39,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; @@ -52,47 +54,51 @@ class Autoconnect; */ class ModuleSpanningTree : public Module { - 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); - /** Handle SQUIT */ ModResult HandleSquit(const std::vector<std::string>& parameters, User* user); @@ -101,10 +107,6 @@ class ModuleSpanningTree : public Module */ ModResult HandleRemoteWhois(const std::vector<std::string>& parameters, User* user); - /** Ping all local servers - */ - void DoPingChecks(time_t curtime); - /** Connect a server locally */ void ConnectServer(Link* x, Autoconnect* y = NULL); @@ -129,60 +131,45 @@ class ModuleSpanningTree : public Module */ 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); - /** 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, std::vector<std::string>& parameters, LocalUser *user, bool validated, const std::string &original_line) CXX11_OVERRIDE; + void OnPostCommand(Command*, const std::vector<std::string>& parameters, LocalUser* user, CmdResult result, const std::string& original_line) 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 OnUserMessage(User* user, void* dest, int target_type, const std::string& text, char status, const CUList& exempt_list, MessageType msgtype) 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 OnChangeName(User* user, const std::string &gecos) 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; + ModResult OnSetAway(User* user, const std::string &awaymsg) 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, const std::string& output_mode) 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..47c2f8bc5 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, std::vector<std::string>& 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..00f31d668 --- /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, std::vector<std::string>& params) +{ + ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + user->nick + ": " + params[1]); + return CMD_SUCCESS; +} + +CmdResult CommandEndBurst::HandleServer(TreeServer* server, std::vector<std::string>& params) +{ + server->FinishBurst(); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/netburst.cpp b/src/modules/m_spanningtree/netburst.cpp index 3bce90eda..cdafa9ded 100644 --- a/src/modules/m_spanningtree/netburst.cpp +++ b/src/modules/m_spanningtree/netburst.cpp @@ -21,11 +21,79 @@ #include "inspircd.h" #include "xline.h" +#include "listmode.h" #include "treesocket.h" #include "treeserver.h" -#include "utils.h" #include "main.h" +#include "commands.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 +102,98 @@ */ 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(OnSyncNetwork, (bs.server)); + this->WriteLine(CmdBuilder("ENDBURST")); ServerInstance->SNO->WriteToSnoMask('l',"Finished bursting to \2"+ s->GetName()+"\2."); + + this->burstsent = true; } -/** Recursively send the server tree with distances as hops. +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())); +} + +/** 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 +204,101 @@ 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()); +} - FOREACH_MOD(I_OnSyncChannel,OnSyncChannel(c->second,Utils->Creator,this)); +/** Send channel users, topic, modes and global metadata */ +void TreeSocket::SyncChannel(Channel* chan, BurstState& bs) +{ + SendFJoins(chan); + + // 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(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) +{ + ProtocolInterface::Server& piserver = bs.server; + + 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(OnSyncUser, (user, piserver)); } } - diff --git a/src/modules/m_spanningtree/nick.cpp b/src/modules/m_spanningtree/nick.cpp new file mode 100644 index 000000000..9e290e07f --- /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, std::vector<std::string>& 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..2c8697c9a --- /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, std::vector<std::string>& 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 std::vector<std::string>& 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 std::vector<std::string>& params = numeric.GetParams(); + if (!params.empty()) + { + for (std::vector<std::string>::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..ab531c171 100644 --- a/src/modules/m_spanningtree/opertype.cpp +++ b/src/modules/m_spanningtree/opertype.cpp @@ -26,15 +26,17 @@ /** 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, std::vector<std::string>& 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 { @@ -48,12 +50,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->host.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..a22fa48ac 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,198 @@ */ -/* $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() + ")"; } - 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(), ' '); + + buffer += InspIRCd::Format("%5d [%5.2f%%]", current->UserCount, percent); - if (IS_OPER(user) || !Utils->FlatLinks) - depth = depth + 2; - for (unsigned int q = 0; q < Current->ChildCount(); q++) + 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(const std::vector<std::string>& parameters, User* user) +{ + 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) + { + // 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; + } + else { - // 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); + // This user can't see any depth + max = 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()); - delete[] names; - delete[] stats; + 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); + + 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 std::vector<std::string>& 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..9cec527d3 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) { 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..7f7189854 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) { - 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..878f8af3a 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, std::vector<std::string>& 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..5d97f2af2 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, std::vector<std::string>& 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..64ca72977 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 std::vector<std::string>& parameters, LocalUser* user, CmdResult result, const std::string& original_line) { 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 parameterlist& 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..4733d0071 100644 --- a/src/modules/m_spanningtree/precommand.cpp +++ b/src/modules/m_spanningtree/precommand.cpp @@ -18,18 +18,9 @@ */ -/* $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) { @@ -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..be95845a7 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.usercount = i->second->UserCount; + ps.opercount = i->second->OperCount; ps.gecos = 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 parameterlist& 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 parameterlist& 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..b0609005c 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 parameterlist& params, User* source) CXX11_OVERRIDE; + void BroadcastEncap(const std::string& cmd, const parameterlist& 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..8b8757a07 100644 --- a/src/modules/m_spanningtree/rconnect.cpp +++ b/src/modules/m_spanningtree/rconnect.cpp @@ -19,19 +19,13 @@ #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>"; @@ -39,14 +33,11 @@ CommandRConnect::CommandRConnect (Module* Creator, SpanningTreeUtilities* Util) CmdResult CommandRConnect::Handle (const std::vector<std::string>& parameters, User *user) { - 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? */ @@ -58,6 +49,21 @@ CmdResult CommandRConnect::Handle (const std::vector<std::string>& parameters, U 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; } 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..487db2826 100644 --- a/src/modules/m_spanningtree/rsquit.cpp +++ b/src/modules/m_spanningtree/rsquit.cpp @@ -19,17 +19,14 @@ #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]"; @@ -38,34 +35,26 @@ CommandRSQuit::CommandRSQuit (Module* Creator, SpanningTreeUtilities* Util) CmdResult CommandRSQuit::Handle (const std::vector<std::string>& parameters, User *user) { 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; @@ -75,20 +64,3 @@ RouteDescriptor CommandRSQuit::GetRouting(User* user, const std::vector<std::str { 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..7131b49fe 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, std::vector<std::string>& params) { - if (params.size() < 2) - return true; + User* u = ServerInstance->FindUUID(params[0]); + if (!u) + return CMD_FAILURE; - User* u = ServerInstance->FindNick(params[0]); time_t ts = atol(params[1].c_str()); - if ((u) && (!IS_SERVER(u)) && (u->age == ts)) - { - Utils->DoOneToAllButSender(prefix,"SAVE",params,prefix); - - 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..50f63e117 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, std::vector<std::string>& 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, const std::vector<std::string>& params) +{ + for (std::vector<std::string>::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(ConvToUInt64(val)); + } +} + +Link* TreeSocket::AuthRemote(const parameterlist& 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(parameterlist ¶ms) +{ + 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) @@ -214,58 +202,14 @@ bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid */ bool TreeSocket::Inbound_Server(parameterlist ¶ms) { - 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)) - { - this->SendError("Invalid format server ID: "+sid+"!"); - return false; - } - - for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) + const Link* x = AuthRemote(params); + if (x) { - 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..ef55cd00e --- /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 std::vector<std::string>& 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..ee7766764 --- /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, std::vector<std::string>& parameters) = 0; + virtual RouteDescriptor GetRouting(User* user, const std::vector<std::string>& 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, std::vector<std::string>& parameters) + { + 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, std::vector<std::string>& parameters) + { + 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..0989ea9a5 --- /dev/null +++ b/src/modules/m_spanningtree/sinfo.cpp @@ -0,0 +1,51 @@ +/* + * 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, std::vector<std::string>& 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 == "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..c85e4f412 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, std::vector<std::string>& 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) { - 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..84cf8558c 100644 --- a/src/modules/m_spanningtree/svsnick.cpp +++ b/src/modules/m_spanningtree/svsnick.cpp @@ -21,41 +21,50 @@ #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, std::vector<std::string>& 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; @@ -63,8 +72,5 @@ CmdResult CommandSVSNick::Handle(const std::vector<std::string>& parameters, Use RouteDescriptor CommandSVSNick::GetRouting(User* user, const std::vector<std::string>& 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..c4163ef3d 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, std::vector<std::string>& parameters) { User* u = ServerInstance->FindUUID(parameters[0]); if (!u) @@ -48,8 +42,5 @@ CmdResult CommandSVSPart::Handle(const std::vector<std::string>& parameters, Use RouteDescriptor CommandSVSPart::GetRouting(User* user, const std::vector<std::string>& 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..b29bea134 100644 --- a/src/modules/m_spanningtree/treeserver.cpp +++ b/src/modules/m_spanningtree/treeserver.cpp @@ -21,56 +21,46 @@ #include "inspircd.h" -#include "socket.h" #include "xline.h" #include "main.h" -#include "../spanningtree.h" +#include "modules/spanningtree.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)) + , 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 +114,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(), SpanningTreeEventListener, 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(), SpanningTreeEventListener, 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..f75adf54c 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,111 @@ * 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; + + 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 SetID(const std::string &id); + void SQuitInternal(unsigned int& num_lost_servers); + + /** Remove the reference to this server from the hash maps + */ + 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); - - int QuitUsers(const std::string &reason); + TreeServer(const std::string& Name, const std::string& Desc, const std::string& id, TreeServer* Above, TreeSocket* Sock, bool Hide); - /** 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(); - - /** Get server name - */ - std::string GetName(); + TreeServer* GetRoute() const { return Route; } - /** Get server description (GECOS) + /** Returns true if this server is the tree root (i.e.: us) */ - const std::string& GetDesc(); + bool IsRoot() const { return (this->Parent == NULL); } - /** Get server version string + /** Returns true if this server is locally connected */ - const std::string& GetVersion(); + bool IsLocal() const { return (this->Route == this); } - /** Set time we are next due to ping this server + /** Returns true if the server is awaiting destruction + * @return True if the server is waiting to be culled and deleted, false otherwise */ - void SetNextPingTime(time_t t); + bool IsDead() const { return isdead; } - /** Get the time we are next due to ping this server + /** Get server version string */ - time_t NextPingTime(); + const std::string& GetVersion() const { return VersionString; } - /** Last ping time in milliseconds, used to calculate round trip time + /** Get the full version string of this server + * @return The full version string of this server, including patch version and other info */ - unsigned long LastPingMsec; + const std::string& GetFullVersion() const { return fullversion; } /** Round trip time of last ping */ @@ -129,86 +150,87 @@ 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 - */ - bool AnsweredLastPing(); - - /** Set the server as responding to its last ping + /** Get the TreeSocket pointer for local servers. + * For remote servers, this returns NULL. */ - void SetPingFlag(); + TreeSocket* GetSocket() const { return Socket; } - /** Get the number of users on this server. + /** Get the parent server. + * For the root node, this returns NULL. */ - unsigned int GetUserCount(); + TreeServer* GetParent() const { return Parent; } - /** Increment or decrement the user count by diff. + /** Set the server version string */ - void SetUserCount(int diff); + void SetVersion(const std::string& verstr) { VersionString = verstr; } - /** Gets the numbers of opers on this server. + /** Set the full version string + * @param verstr The version string to set */ - unsigned int GetOperCount(); + void SetFullVersion(const std::string& verstr) { fullversion = 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..3571f2816 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 parameterlist& 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,32 +242,12 @@ 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); - /** 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. @@ -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,61 +270,15 @@ 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); - /** (local) -> SERVER */ bool Outbound_Reply_Server(parameterlist ¶ms); @@ -323,15 +299,12 @@ class TreeSocket : public BufferedSocket /** 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, std::vector<std::string>& params); }; - -#endif - diff --git a/src/modules/m_spanningtree/treesocket1.cpp b/src/modules/m_spanningtree/treesocket1.cpp index c9729cc0f..370c38d2c 100644 --- a/src/modules/m_spanningtree/treesocket1.cpp +++ b/src/modules/m_spanningtree/treesocket1.cpp @@ -21,80 +21,65 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" +#include "iohook.h" #include "main.h" -#include "../spanningtree.h" +#include "modules/spanningtree.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); 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); + for (ListenSocket::IOHookProvList::iterator i = via->iohookprovs.begin(); i != via->iohookprovs.end(); ++i) + { + ListenSocket::IOHookProvRef& iohookprovref = *i; + if (!iohookprovref) + continue; + + 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; + } + } + SendCapabilities(1); Utils->timeoutlist[this] = std::pair<std::string, int>(linkID, 30); } -ServerState TreeSocket::GetLinkState() -{ - return this->LinkState; -} - void TreeSocket::CleanNegotiationInfo() { // connect is good, reset the autoconnect block (if used) @@ -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, std::vector<std::string>& 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..04b850755 100644 --- a/src/modules/m_spanningtree/treesocket2.cpp +++ b/src/modules/m_spanningtree/treesocket2.cpp @@ -23,16 +23,13 @@ #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) @@ -47,10 +44,10 @@ void TreeSocket::Split(const std::string& line, std::string& prefix, std::string if (!tokens.GetToken(prefix)) return; - + if (prefix[0] == ':') { - prefix = prefix.substr(1); + prefix.erase(prefix.begin()); if (prefix.empty()) { @@ -84,7 +81,7 @@ void TreeSocket::ProcessLine(std::string &line) std::string command; parameterlist 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, parameterlist& 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,68 @@ 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") - { - 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") + // Translate commands coming from servers using an older protocol + if (proto_version < ProtocolVersion) { - 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") - { - this->Encap(who, params); - } - else if (command == "NICK") + ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command); + CommandBase* cmdbase = scmd; + Command* cmd = NULL; + if (!scmd) { - 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 + { + res = cmd->Handle(params, who); 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 +359,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 +370,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..91c9f3ca8 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, std::vector<std::string>& 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 */ - 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->ident = params[5]; - _new->fullname = params[params.size() - 1]; + _new->fullname = 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].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->fullname.c_str()); - FOREACH_MOD(I_OnPostConnect,OnPostConnect(_new)); + FOREACH_MOD(OnPostConnect, (_new)); return CMD_SUCCESS; } -CmdResult CommandFHost::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFHost::HandleRemote(RemoteUser* src, std::vector<std::string>& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeDisplayedHost(params[0].c_str()); + src->ChangeDisplayedHost(params[0]); return CMD_SUCCESS; } -CmdResult CommandFIdent::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFIdent::HandleRemote(RemoteUser* src, std::vector<std::string>& 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, std::vector<std::string>& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeName(params[0].c_str()); + src->ChangeName(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->host); + push(user->dhost); + push(user->ident); + push(user->GetIPString()); + push_int(user->signon); + push(user->GetModeLetters(true)); + push_last(user->fullname); +} diff --git a/src/modules/m_spanningtree/utils.cpp b/src/modules/m_spanningtree/utils.cpp index 367a3b921..79dc24e6a 100644 --- a/src/modules/m_spanningtree/utils.cpp +++ b/src/modules/m_spanningtree/utils.cpp @@ -21,18 +21,16 @@ #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") @@ -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,24 +89,33 @@ 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) @@ -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,10 +228,9 @@ 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"); + PingFreq = options->getDuration("serverpingfreq"); if (PingFreq == 0) PingFreq = 60; @@ -339,14 +247,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->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 +267,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 +302,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 +312,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 +320,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 +331,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 +339,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..f262f9a48 100644 --- a/src/modules/m_spanningtree/utils.h +++ b/src/modules/m_spanningtree/utils.h @@ -20,36 +20,34 @@ */ -#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::set<TreeSocket*> TreeSocketSet; typedef std::map<TreeSocket*, std::pair<std::string, int> > TimeoutList; /** Creator module @@ -100,14 +98,6 @@ 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; @@ -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 parameterlist& 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..1a5b68dd9 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, @@ -39,8 +38,8 @@ class AuthQuery : public SQLQuery : SQLQuery(me), uid(u), pendingExt(e), verbose(v) { } - - void OnResult(SQLResult& res) + + void OnResult(SQLResult& res) CXX11_OVERRIDE { User* user = ServerInstance->FindNick(uid); if (!user) @@ -57,7 +56,7 @@ class AuthQuery : public SQLQuery } } - void OnError(SQLerror& error) + void OnError(SQLerror& error) CXX11_OVERRIDE { User* user = ServerInstance->FindNick(uid); if (!user) @@ -79,19 +78,13 @@ class ModuleSQLAuth : public Module bool verbose; public: - ModuleSQLAuth() : pendingExt("sqlauth-wait", this), SQL(this, "SQL") - { - } - - void init() + ModuleSQLAuth() + : pendingExt("sqlauth-wait", ExtensionItem::EXT_USER, this) + , SQL(this, "SQL") { - 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"); @@ -105,7 +98,7 @@ class ModuleSQLAuth : public Module verbose = conf->getBool("verbose"); } - ModResult OnUserRegister(LocalUser* user) + ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { // Note this is their initial (unresolved) connect block ConfigTag* tag = user->MyClass->config; @@ -133,18 +126,21 @@ class ModuleSQLAuth : public Module HashProvider* md5 = ServerInstance->Modules->FindDataService<HashProvider>("hash/md5"); if (md5) - userinfo["md5pass"] = md5->hexsum(user->password); + userinfo["md5pass"] = md5->Generate(user->password); HashProvider* sha256 = ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"); if (sha256) - userinfo["sha256pass"] = sha256->hexsum(user->password); + userinfo["sha256pass"] = sha256->Generate(user->password); + + const std::string certfp = SSLClientCert::GetFingerprint(&user->eh); + userinfo["certfp"] = certfp; SQL->submit(new AuthQuery(this, user->uuid, pendingExt, verbose), freeformquery, userinfo); return MOD_RES_PASSTHRU; } - ModResult OnCheckReady(LocalUser* user) + ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE { switch (pendingExt.get(user)) { @@ -159,7 +155,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..b5f0d6c47 100644 --- a/src/modules/m_sqloper.cpp +++ b/src/modules/m_sqloper.cpp @@ -18,24 +18,8 @@ #include "inspircd.h" -#include "sql.h" -#include "hash.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; -} +#include "modules/sql.h" +#include "modules/hash.h" class OpMeQuery : public SQLQuery { @@ -46,9 +30,9 @@ class OpMeQuery : public SQLQuery { } - void OnResult(SQLResult& res) + void OnResult(SQLResult& res) CXX11_OVERRIDE { - ServerInstance->Logs->Log("m_sqloper",DEBUG, "SQLOPER: result for %s", uid.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "result for %s", uid.c_str()); User* user = ServerInstance->FindNick(uid); if (!user) return; @@ -57,30 +41,17 @@ class OpMeQuery : public SQLQuery SQLEntries row; while (res.GetRow(row)) { -#if 0 - parameterlist 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++) - { - if (!row[i].nul) - items->insert(std::make_pair(cols[i], row[i])); - } -#else if (OperUser(user, row[0], row[1])) return; -#endif } - ServerInstance->Logs->Log("m_sqloper",DEBUG, "SQLOPER: no matches for %s (checked %d rows)", uid.c_str(), res.Rows()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "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(SQLerror& error) CXX11_OVERRIDE { - ServerInstance->Logs->Log("m_sqloper",DEFAULT, "SQLOPER: query failed (%s)", error.Str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "query failed (%s)", error.Str()); fallback(); } @@ -90,7 +61,7 @@ class OpMeQuery : public SQLQuery if (!user) return; - Command* oper_command = ServerInstance->Parser->GetHandler("OPER"); + Command* oper_command = ServerInstance->Parser.GetHandler("OPER"); if (oper_command) { @@ -101,16 +72,16 @@ class OpMeQuery : public SQLQuery } else { - ServerInstance->Logs->Log("m_sqloper",SPARSE, "BUG: WHAT?! Why do we have no OPER command?!"); + ServerInstance->Logs->Log(MODNAME, LOG_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()) + ServerConfig::OperIndex::const_iterator iter = ServerInstance->Config->OperTypes.find(type); + if (iter == ServerInstance->Config->OperTypes.end()) { - ServerInstance->Logs->Log("m_sqloper",DEFAULT, "SQLOPER: bad type '%s' in returned row for oper %s", type.c_str(), username.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "bad type '%s' in returned row for oper %s", type.c_str(), username.c_str()); return false; } OperInfo* ifo = iter->second; @@ -119,7 +90,7 @@ class OpMeQuery : public SQLQuery hostname.append("@").append(user->host); - if (OneOfMatches(hostname.c_str(), user->GetIPString(), pattern)) + if (InspIRCd::MatchMask(pattern, hostname, user->GetIPString())) { /* Opertype and host match, looks like this is it. */ @@ -140,15 +111,7 @@ class ModuleSQLOper : public Module public: ModuleSQLOper() : SQL(this, "SQL") {} - void init() - { - 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 { ConfigTag* tag = ServerInstance->Config->ConfValue("sqloper"); @@ -159,10 +122,10 @@ public: 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 hostname as host, type FROM ircd_opers WHERE username='$username' AND password='$password' AND active=1;"); } - ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) CXX11_OVERRIDE { if (validated && command == "OPER" && parameters.size() >= 2) { @@ -172,7 +135,7 @@ public: /* Query is in progress, it will re-invoke OPER if needed */ return MOD_RES_DENY; } - ServerInstance->Logs->Log("m_sqloper",DEFAULT, "SQLOPER: database not present"); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "database not present"); } return MOD_RES_PASSTHRU; } @@ -184,16 +147,15 @@ public: ParamM userinfo; SQL->PopulateUserInfo(user, userinfo); userinfo["username"] = username; - userinfo["password"] = hash ? hash->hexsum(password) : password; + userinfo["password"] = hash ? hash->Generate(password) : password; SQL->submit(new OpMeQuery(this, user->uuid, username, password), query, userinfo); } - 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..9682e92cf 100644 --- a/src/modules/m_sslinfo.cpp +++ b/src/modules/m_sslinfo.cpp @@ -18,13 +18,15 @@ #include "inspircd.h" -#include "ssl.h" - -/* $ModDesc: Provides SSL metadata, including /WHOIS information and /SSLINFO command */ +#include "modules/ssl.h" 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)); @@ -93,101 +95,93 @@ class CommandSSLInfo : public Command 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(671, "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()); + if ((!operonlyfp || whois.IsSelfWhois() || whois.GetSource()->IsOper()) && !cert->fingerprint.empty()) + whois.SendLine(276, InspIRCd::Format("has client certificate fingerprint %s", 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; - } - } - 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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(491, "This oper login requires an SSL connection."); user->CommandFloodPenalty += 10000; return MOD_RES_DENY; } @@ -195,7 +189,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(491, "This oper login requires a matching SSL certificate fingerprint."); user->CommandFloodPenalty += 10000; return MOD_RES_DENY; } @@ -206,59 +200,63 @@ 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 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()); } else if (myclass->config->getBool("requiressl")) { - ok = (req.cert != NULL); + ok = (cert != NULL); } 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..d3afd00e6 100644 --- a/src/modules/m_sslmodes.cpp +++ b/src/modules/m_sslmodes.cpp @@ -22,38 +22,50 @@ #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 +}; /** Handle channel mode +z */ class SSLMode : public ModeHandler { public: - SSLMode(Module* Creator) : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_CHANNEL) { } + UserCertificateAPI API; + + SSLMode(Module* Creator) + : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_CHANNEL) + , API(Creator) + { + } ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { 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 +75,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; } @@ -85,20 +97,15 @@ class ModuleSSLModes : public Module { } - void init() - { - 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 (!sslm.API) + return MOD_RES_DENY; + + ssl_cert* cert = sslm.API->GetCertificate(user); + if (cert) { // Let them in return MOD_RES_PASSTHRU; @@ -106,7 +113,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 +121,29 @@ class ModuleSSLModes : public Module 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] == 'z') && (mask[1] == ':')) { - UserCertificateRequest req(user, this); - req.Send(); - if (req.cert && InspIRCd::Match(req.cert->GetFingerprint(), mask.substr(2))) + if (!sslm.API) + return MOD_RES_DENY; + + ssl_cert* cert = sslm.API->GetCertificate(user); + if (cert && InspIRCd::Match(cert->GetFingerprint(), mask.substr(2))) return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - ~ModuleSSLModes() + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { + tokens["EXTBAN"].push_back('z'); } - void On005Numeric(std::string &output) - { - ServerInstance->AddExtBanChar('z'); - } - - Version GetVersion() + Version GetVersion() CXX11_OVERRIDE { return Version("Provides channel mode +z to allow for Secure/SSL only channels", 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..b3cf5a263 --- /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(const std::vector<std::string>& parameters, LocalUser* user) + { + 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..6ad32bfc1 100644 --- a/src/modules/m_stripcolor.cpp +++ b/src/modules/m_stripcolor.cpp @@ -20,8 +20,7 @@ #include "inspircd.h" - -/* $ModDesc: Provides channel +S mode (strip ansi color) */ +#include "modules/exemption.h" /** Handles channel mode +S */ @@ -42,32 +41,24 @@ class UserStripColor : public SimpleUserModeHandler class ModuleStripColor : public Module { + CheckExemption::EventProvider exemptionprov; ChannelStripColor csc; UserStripColor usc; public: - ModuleStripColor() : csc(this), usc(this) - { - } - - void init() - { - 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)); - } - - virtual ~ModuleStripColor() + ModuleStripColor() + : exemptionprov(this) + , csc(this) + , usc(this) { } - virtual void On005Numeric(std::string &output) + void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE { - ServerInstance->AddExtBanChar('S'); + tokens["EXTBAN"].push_back('S'); } - virtual ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) + ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE { if (!IS_LOCAL(user)) return MOD_RES_PASSTHRU; @@ -76,17 +67,18 @@ class ModuleStripColor : public Module if (target_type == TYPE_USER) { User* t = (User*)dest; - active = t->IsModeSet('S'); + active = t->IsModeSet(usc); } else if (target_type == TYPE_CHANNEL) { Channel* t = (Channel*)dest; - ModResult res = ServerInstance->OnCheckExemption(user,t,"stripcolor"); + ModResult res; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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) @@ -97,12 +89,25 @@ class ModuleStripColor : public Module 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; + FIRST_MOD_RESULT_CUSTOM(exemptionprov, CheckExemption::EventListener, OnCheckExemption, res, (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..ad6a4d1aa 100644 --- a/src/modules/m_svshold.cpp +++ b/src/modules/m_svshold.cpp @@ -23,8 +23,6 @@ #include "inspircd.h" #include "xline.h" -/* $ModDesc: Implements SVSHOLD. Like Q:Lines, but can only be added/removed by Services. */ - namespace { bool silent; @@ -35,16 +33,12 @@ 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) @@ -56,23 +50,21 @@ public: bool Matches(const std::string &s) { - if (nickname == s) - return true; - return false; + return InspIRCd::Match(s, nickname); } void DisplayExpiry() { 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() { - return nickname.c_str(); + return nickname; } }; @@ -104,7 +96,6 @@ 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) @@ -112,7 +103,7 @@ class CommandSvshold : public Command /* 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 +118,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 +126,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 +141,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()); } } @@ -182,50 +172,46 @@ class ModuleSVSHold : public Module { } - 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 742781747..9a433e154 100644 --- a/src/modules/m_swhois.cpp +++ b/src/modules/m_swhois.cpp @@ -25,27 +25,27 @@ #include "inspircd.h" -/* $ModDesc: Provides the SWHOIS command which allows setting of arbitrary WHOIS lines */ - /** Handle /SWHOIS */ class CommandSwhois : public Command { public: StringExtItem swhois; - CommandSwhois(Module* Creator) : Command(Creator,"SWHOIS", 2,2), swhois("swhois", Creator) + CommandSwhois(Module* Creator) + : Command(Creator, "SWHOIS", 2, 2) + , 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) { 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; } @@ -53,11 +53,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()); @@ -81,34 +81,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) { - ServerInstance->Modules->AddService(cmd); - ServerInstance->Modules->AddService(cmd.swhois); - Implementation eventlist[] = { I_OnWhoisLine, I_OnPostOper }; - 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(320, *swhois); } } @@ -116,7 +110,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; @@ -130,11 +124,7 @@ class ModuleSWhois : public Module ServerInstance->PI->SendMetaData(user, "swhois", swhois); } - ~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 61095d6eb..9890800e4 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,21 +40,30 @@ 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); + 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) @@ -64,61 +71,54 @@ class CommandTban : public Command 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::NoSuchNick(parameters[0])); return CMD_FAILURE; } 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->GetLastParse().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; TimedBanList.push_back(T); // 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 added a timed ban on %s lasting for %ld seconds.", channel->name.c_str(), user->nick.c_str(), mask.c_str(), duration); @@ -131,6 +131,34 @@ class CommandTban : public Command } }; +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) + { + 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; @@ -150,37 +178,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();) @@ -196,37 +203,31 @@ 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; - std::string expiry = "*** Timed ban on " + chan + " expired."; + std::string expiry = "*** Timed ban on " + cr->name + " expired."; cr->WriteAllExcept(ServerInstance->FakeClient, true, '@', empty, "NOTICE %s :%s", cr->name.c_str(), expiry.c_str()); ServerInstance->PI->SendChannelNotice(cr, '@', 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..77ec0e26c 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 @@ -34,14 +32,13 @@ class CommandTline : public Command CmdResult Handle (const std::vector<std::string> ¶meters, User *user) { - 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..8a0712c3e 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: @@ -31,7 +35,7 @@ class CommandSVSTOPIC : public Command CmdResult Handle(const std::vector<std::string> ¶meters, User *user) { - if (!ServerInstance->ULine(user->server)) + if (!user->server->IsULine()) { // Ulines only return CMD_FAILURE; @@ -47,36 +51,17 @@ 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; @@ -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) { } @@ -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..ce9c517f4 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, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) 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..d3a424dff 100644 --- a/src/modules/m_uninvite.cpp +++ b/src/modules/m_uninvite.cpp @@ -20,19 +20,21 @@ */ -/* $ModDesc: Provides the UNINVITE command which lets users un-invite other users from channels (!) */ - #include "inspircd.h" +#include "modules/invite.h" /** 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) @@ -49,11 +51,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::NoSuchNick(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 +65,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,19 +77,26 @@ 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(493, 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); } @@ -96,8 +105,7 @@ class CommandUninvite : public Command RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { - User* u = ServerInstance->FindNick(parameters[0]); - return u ? ROUTE_OPT_UCAST(u->server) : ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } }; @@ -111,20 +119,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..6fa367bff 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 @@ -35,7 +33,7 @@ class CommandUserip : public Command CmdResult Handle (const std::vector<std::string> ¶meters, User *user) { - std::string retbuf = "340 " + user->nick + " :"; + std::string retbuf; int nicks = 0; bool checked_privs = false; bool has_privs = false; @@ -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..53910fdbe 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 @@ -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..6e9a6f9ed 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 @@ -22,524 +19,250 @@ #include "inspircd.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 +}; -/* - * 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->dhost, (unsigned long)target->awaytime, "is away"); + else + user->WriteNumeric(RPL_NOWON, target->nick, target->ident, target->dhost, (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) { - ServerInstance->Parser->CallHandler("WATCH", parameters, u); + // List is full, send error numeric + user->WriteNumeric(ERR_TOOMANYWATCH, nick, "Too many WATCH entries"); + return; } + else if (result == IRCv3::Monitor::Manager::WR_INVALIDNICK) + { + user->WriteNumeric(942, 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->dhost, (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); - - wl->erase(n); - } + void HandleStats(LocalUser* user) + { + user->CommandFloodPenalty += ListPenalty; - if (wl->empty()) - { - ext.unset(user); - } + // 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())); - 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()); } - - return CMD_SUCCESS; + out.Flush(); + user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH S"); } - CmdResult add_watch(User* user, const char* nick) + public: + unsigned int maxwatch; + + CommandWatch(Module* mod, IRCv3::Monitor::Manager& managerref) + : SplitCommand(mod, "WATCH") + , manager(managerref) { - if (!ServerInstance->IsNick(nick, ServerInstance->Config->Limits.NickMax)) - { - user->WriteNumeric(942, "%s %s :Invalid nickname",user->nick.c_str(),nick); - return CMD_FAILURE; - } + allow_empty_last_param = false; + syntax = "[<C|L|S|l|+<nick1>|-<nick>>]"; + } - watchlist* wl = ext.get(user); - if (!wl) + CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) + { + if (parameters.empty()) { - wl = new watchlist(); - ext.set(user, wl); + HandleList(user, false); + return CMD_SUCCESS; } - if (wl->size() >= MAX_WATCH) - { - user->WriteNumeric(512, "%s %s :Too many WATCH entries", user->nick.c_str(), nick); - return CMD_FAILURE; - } + bool watch_l_done = false; + bool watch_s_done = false; - watchlist::iterator n = wl->find(nick); - if (n == wl->end()) + for (std::vector<std::string>::const_iterator i = parameters.begin(); i != parameters.end(); ++i) { - /* 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()) - { - /* People are watching this user, add myself */ - x->second.push_back(user); - } - else + const std::string& token = *i; + char subcmd = toupper(token[0]); + if (subcmd == '+') { - std::deque<User*> newlist; - newlist.push_back(user); - (*(whos_watching_me))[nick] = newlist; + HandlePlus(user, token.substr(1)); } - - User* target = ServerInstance->FindNick(nick); - if ((target) && (target->registered == REG_ALL)) + else if (subcmd == '-') { - (*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); - } + HandleMinus(user, token.substr(1)); } - else + else if (subcmd == 'C') { - (*wl)[nick].clear(); - user->WriteNumeric(605, "%s %s * * 0 :is offline",user->nick.c_str(), nick); + manager.UnwatchAll(user); } - } - - 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 == 'L') && (!watch_l_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()); - } + 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')); } - 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++) + else if ((subcmd == 'S') && (!watch_s_done)) { - 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 { - 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->dhost).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() + : 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->getInt("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(); - } - } - } - - 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()); - } - } - } + LocalUser* localuser = IS_LOCAL(user); + if (localuser) + manager.UnwatchAll(localuser); + Offline(user, user->nick); } - virtual void On005Numeric(std::string &output) + ModResult OnSetAway(User* user, const std::string& awaymsg) CXX11_OVERRIDE { - // we don't really have a limit... - output = output + " WATCH=" + ConvToStr(maxwatch); + if (awaymsg.empty()) + SendAlert(user, user->nick, RPL_NOTAWAY, "is no longer away", ServerInstance->Time()); + else + SendAlert(user, user->nick, RPL_GONEAWAY, awaymsg.c_str(), user->awaytime); + + return MOD_RES_PASSTHRU; } - 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..399b0b017 --- /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; + WebSocketHookProvider hookprov; + + public: + ModuleWebSocket() + : hash(this, "hash/sha1") + , hookprov(this) + { + sha1 = &hash; + } + + void OnCleanup(int target_type, void* item) CXX11_OVERRIDE + { + if (target_type != TYPE_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 2237b0d08..d220027fe 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('a', "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(), - ServerInstance->Config->ServerName.c_str(), (unsigned long)line->set_time, (unsigned long)line->duration, line->reason.c_str()); + stream << "LINE " << line->type << " " << line->Displayable() << " " + << ServerInstance->Config->ServerName << " " << 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('a', "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('a', "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,42 +145,23 @@ 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('a', "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; @@ -208,18 +172,14 @@ class ModuleXLineDB : public Module 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()); + 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('a', "database: I got a database version (%s) I don't understand", command_p[1].c_str()); return false; } @@ -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..f18c9c86c 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 + " :" INSPIRCD_SYSTEM " [" INSPIRCD_SOCKETENGINE_NAME "," + Config->sid + "]"; + 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,135 @@ 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') { - // 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++) + 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'; + 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::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["ELIST"] = "MU"; + tokens["KICKLEN"] = ConvToStr(ServerInstance->Config->Limits.MaxKick); + 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["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(); + + // If this token has a value then append a '=' char after the name and then the value itself + if (!it->second.empty()) + token.append(1, '=').append(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..fd6a2709a 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,42 @@ 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 +111,44 @@ 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) +{ + std::string log = desc; + log.append(": ").append(msg); + ServerInstance->Logs->Log("snomask", LOG_DEFAULT, log); + + std::string finalmsg = "*** "; + finalmsg.append(log); + /* 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..91c7cdeca 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -22,59 +22,6 @@ #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) -{ - 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 - { - 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 - { - this->Logs->Log("SOCKET",DEBUG,"New socket binding for %d without listen: %s:%d", sockfd, addr, port); - return true; - } - } -} int InspIRCd::BindPorts(FailedPortList &failed_ports) { @@ -89,7 +36,7 @@ int InspIRCd::BindPorts(FailedPortList &failed_ports) 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."); + this->Logs->Log("SOCKET", LOG_DEFAULT, "Using 4in6 (::ffff:) isn't recommended. You should bind IPv4 addresses directly instead."); irc::portparser portrange(porttag, false); int portno = -1; @@ -98,14 +45,15 @@ int InspIRCd::BindPorts(FailedPortList &failed_ports) irc::sockets::sockaddrs bindspec; if (!irc::sockets::aptosa(Addr, portno, bindspec)) continue; - std::string bind_readable = bindspec.str(); bool skip = false; for (std::vector<ListenSocket*>::iterator n = old_ports.begin(); n != old_ports.end(); ++n) { - if ((**n).bind_desc == bind_readable) + if ((**n).bind_sa == bindspec) { (*n)->bind_tag = tag; // Replace tag, we know addr and port match, but other info (type, ssl) may not + (*n)->ResetIOHookProvider(); + skip = true; old_ports.erase(n); break; @@ -122,7 +70,7 @@ int InspIRCd::BindPorts(FailedPortList &failed_ports) } else { - failed_ports.push_back(std::make_pair(bind_readable, strerror(errno))); + failed_ports.push_back(std::make_pair(bindspec, errno)); delete ll; } } @@ -136,12 +84,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 @@ -197,46 +145,37 @@ std::string irc::sockets::sockaddrs::addr() const char addrv[INET6_ADDRSTRLEN+1]; if (sa.sa_family == AF_INET) { - if (!inet_ntop(AF_INET, &in4.sin_addr, addrv, sizeof(addrv))) + if (!inet_ntop(AF_INET, (void*)&in4.sin_addr, addrv, sizeof(addrv))) return ""; return addrv; } else if (sa.sa_family == AF_INET6) { - if (!inet_ntop(AF_INET6, &in6.sin6_addr, addrv, sizeof(addrv))) + if (!inet_ntop(AF_INET6, (void*)&in6.sin6_addr, addrv, sizeof(addrv))) return ""; return addrv; } return ""; } -bool irc::sockets::satoap(const irc::sockets::sockaddrs& sa, std::string& addr, int &port) -{ - port = sa.port(); - addr = sa.addr(); - return !addr.empty(); -} - 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)); + char ipaddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, (void*)&in4.sin_addr, ipaddr, sizeof(ipaddr)); + return InspIRCd::Format("%s:%u", ipaddr, ntohs(in4.sin_port)); } - else if (sa.sa_family == AF_INET6) + + if (sa.sa_family == AF_INET6) { - 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)); + char ipaddr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, (void*)&in6.sin6_addr, ipaddr, sizeof(ipaddr)); + return InspIRCd::Format("[%s]:%u", ipaddr, ntohs(in6.sin6_port)); } - else - return "<unknown>"; - return std::string(buffer); + + // This should never happen. + return "<unknown>"; } int irc::sockets::sockaddrs::sa_size() const @@ -367,4 +306,3 @@ bool irc::sockets::cidr_mask::match(const irc::sockets::sockaddrs& addr) const irc::sockets::cidr_mask tmp(addr, length); return tmp == *this; } - diff --git a/src/socketengine.cpp b/src/socketengine.cpp index 4a9a2ef10..3735e7530 100644 --- a/src/socketengine.cpp +++ b/src/socketengine.cpp @@ -23,6 +23,25 @@ #include "inspircd.h" + +/** 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; + +int SocketEngine::MAX_DESCRIPTORS; + +/** Socket engine statistics: count of various events, bandwidth usage + */ +SocketEngine::Statistics SocketEngine::stats; + EventHandler::EventHandler() { fd = -1; @@ -34,20 +53,12 @@ void EventHandler::SetFd(int FD) this->fd = FD; } -SocketEngine::SocketEngine() -{ - TotalEvents = WriteEvents = ReadEvents = ErrorEvents = 0; - lastempty = ServerInstance->Time(); - indata = outdata = 0; -} - -SocketEngine::~SocketEngine() +void EventHandler::OnEventHandlerWrite() { } -void SocketEngine::SetEventMask(EventHandler* eh, int mask) +void EventHandler::OnEventHandlerError(int errornum) { - eh->event_mask = mask; } void SocketEngine::ChangeEventMask(EventHandler* eh, int change) @@ -60,7 +71,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 +99,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 +144,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 +155,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,35 +203,57 @@ 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 nbSent = sendto(fd->GetFd(), (const char*)buf, len, flags, to, tolen); - if (nbSent > 0) - this->UpdateStats(0, nbSent); + stats.UpdateWriteCounters(nbSent); return nbSent; } +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 sockaddr *serv_addr, socklen_t addrlen) { int ret = connect(fd->GetFd(), serv_addr, addrlen); @@ -231,24 +284,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..c442e340d 100644 --- a/src/socketengines/socketengine_epoll.cpp +++ b/src/socketengines/socketengine_epoll.cpp @@ -18,79 +18,54 @@ */ -#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; - + // MAX_DESCRIPTORS is mainly used for display purposes, no problem if getrlimit() fails 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); + // 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."); + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Could not initialize socket engine: %s", strerror(errno)); + ServerInstance->Logs->Log("SOCKET", LOG_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()]; - - 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 +91,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 +133,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 +214,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..9db902314 100644 --- a/src/socketengines/socketengine_kqueue.cpp +++ b/src/socketengines/socketengine_kqueue.cpp @@ -24,39 +24,27 @@ #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]; @@ -69,21 +57,13 @@ KQueueEngine::KQueueEngine() mib[1] = KERN_MAXFILES; #endif len = sizeof(MAX_DESCRIPTORS); + // MAX_DESCRIPTORS is mainly used for display purposes, no problem if the sysctl() below fails 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*)); + 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. @@ -93,176 +73,146 @@ 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."); + ServerInstance->Logs->Log("SOCKET", LOG_DEFAULT, "ERROR: Could not initialize socket engine. Your kernel probably does not have the proper features."); + ServerInstance->Logs->Log("SOCKET", LOG_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; } -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); + ke = GetChangeKE(); + EV_SET(ke, eh->GetFd(), EVFILT_READ, EV_DELETE, 0, 0, NULL); - if (j < 0) - { - ServerInstance->Logs->Log("SOCKET",DEFAULT,"Failed to remove fd: %d %s", - fd, strerror(errno)); - } + SocketEngine::DelFdRef(eh); - CurrentSetSize--; - ref[fd] = NULL; - - 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..59991d80d 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> @@ -20,65 +21,26 @@ */ -#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() + std::vector<int> fd_mappings(16); +} + +void SocketEngine::Init() { - CurrentSetSize = 0; struct rlimit limits; if (!getrlimit(RLIMIT_NOFILE, &limits)) { @@ -86,23 +48,17 @@ PollEngine::PollEngine() } 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); + // MAX_DESCRIPTORS is mainly used for display purposes, it's not a problem that getrlimit() failed + MAX_DESCRIPTORS = -1; } +} - ref = new EventHandler* [GetMaxFds()]; - events = new struct pollfd[GetMaxFds()]; - - 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 +71,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) +void SocketEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) { - 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) -{ - 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 +148,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 index f7c547d45..68fa70e3b 100644 --- a/src/socketengines/socketengine_ports.cpp +++ b/src/socketengines/socketengine_ports.cpp @@ -20,87 +20,54 @@ #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> +#include <ulimit.h> /** A specialisation of the SocketEngine class, designed to use solaris 10 I/O completion ports */ -class PortsEngine : public SocketEngine +namespace { -private: - /** These are used by epoll() to hold socket events + /** These are used by ports to hold socket events */ - port_event_t* events; + std::vector<port_event_t> events(16); 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() +/** Initialize ports engine + */ +void SocketEngine::Init() { - 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); - } + // MAX_DESCRIPTORS is mainly used for display purposes, no problem if ulimit() fails and returns a negative number + MAX_DESCRIPTORS = ulimit(4, 0); + 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."); + ServerInstance->Logs->Log("SOCKET", LOG_SPARSE, "ERROR: Could not initialize socket engine: %s", strerror(errno)); + ServerInstance->Logs->Log("SOCKET", LOG_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*)); +/** Shutdown the ports engine + */ +void SocketEngine::Deinit() +{ + SocketEngine::Close(EngineHandle); } -PortsEngine::~PortsEngine() +void SocketEngine::RecoverFromFork() { - this->Close(EngineHandle); - delete[] ref; - delete[] events; } static int mask_to_events(int event_mask) @@ -113,45 +80,44 @@ static int mask_to_events(int event_mask) return rv; } -bool PortsEngine::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) return false; - if (ref[fd]) + if (!SocketEngine::AddFdRef(eh)) return false; - ref[fd] = eh; - SocketEngine::SetEventMask(eh, event_mask); + eh->SetEventMask(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++; + ServerInstance->Logs->Log("SOCKET", LOG_DEBUG, "New file descriptor: %d", fd); + ResizeDouble(events); + return true; } -void PortsEngine::OnSetEvent(EventHandler* eh, int old_mask, int new_mask) +void SocketEngine::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) +void SocketEngine::DelFd(EventHandler* eh) { int fd = eh->GetFd(); - if ((fd < 0) || (fd > GetMaxFds() - 1)) + if (fd < 0) return; port_dissociate(EngineHandle, PORT_SOURCE_FD, fd); - 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); } -int PortsEngine::DispatchEvents() +int SocketEngine::DispatchEvents() { struct timespec poll_time; @@ -159,62 +125,49 @@ int PortsEngine::DispatchEvents() 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); + int ret = port_getn(EngineHandle, &events[0], events.size(), &nget, &poll_time); ServerInstance->UpdateTime(); // first handle an error condition if (ret == -1) return -1; - TotalEvents += nget; + stats.TotalEvents += nget; unsigned int i; for (i = 0; i < nget; i++) { - switch (this->events[i].portev_source) + port_event_t& ev = events[i]; + + if (ev.portev_source != PORT_SOURCE_FD) + continue; + + // Copy these in case the vector gets resized and ev invalidated + const int fd = ev.portev_object; + const int portev_events = ev.portev_events; + EventHandler* eh = static_cast<EventHandler*>(ev.portev_user); + if (eh->GetFd() < 0) + continue; + + int mask = eh->GetEventMask(); + if (portev_events & POLLWRNORM) + mask &= ~(FD_WRITE_WILL_BLOCK | FD_WANT_FAST_WRITE | FD_WANT_SINGLE_WRITE); + if (portev_events & POLLRDNORM) + mask &= ~FD_READ_WILL_BLOCK; + // reinsert port for next time around, pretending to be one-shot for writes + eh->SetEventMask(mask); + port_associate(EngineHandle, PORT_SOURCE_FD, fd, mask_to_events(mask), eh); + if (portev_events & POLLRDNORM) { - 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; + eh->OnEventHandlerRead(); + if (eh != GetRef(fd)) + continue; + } + if (portev_events & POLLWRNORM) + { + eh->OnEventHandlerWrite(); } } 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..42f634db1 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,56 @@ /** 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() + int MaxFD = 0; +} + +void SocketEngine::Init() { MAX_DESCRIPTORS = FD_SETSIZE; - CurrentSetSize = 0; - - ref = new EventHandler* [GetMaxFds()]; - memset(ref, 0, GetMaxFds() * sizeof(EventHandler*)); FD_ZERO(&ReadSet); FD_ZERO(&WriteSet); FD_ZERO(&ErrSet); - MaxFD = 0; } -SelectEngine::~SelectEngine() +void SocketEngine::Deinit() +{ +} + +void SocketEngine::RecoverFromFork() { - delete[] ref; } -bool SelectEngine::AddFd(EventHandler* eh, int event_mask) +bool SocketEngine::AddFd(EventHandler* eh, int event_mask) { int fd = eh->GetFd(); if ((fd < 0) || (fd > GetMaxFds() - 1)) return false; - if (ref[fd]) + if (!SocketEngine::AddFdRef(eh)) return false; - ref[fd] = eh; - - 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)) return; - CurrentSetSize--; - ref[fd] = NULL; + SocketEngine::DelFdRef(eh); FD_CLR(fd, &ReadSet); FD_CLR(fd, &WriteSet); @@ -106,10 +84,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 +108,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 +119,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; + + EventHandler* ev = GetRef(i); + if (!ev) + continue; - if (has_error) - { - ErrorEvents++; + 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; - 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..0b0d8bac3 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(time_t 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..12ec36ec7 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -22,83 +22,93 @@ #include "inspircd.h" #include "xline.h" -#include "bancache.h" +#include "iohook.h" + +namespace +{ + class WriteCommonQuit : public User::ForEachNeighborHandler + { + std::string line; + std::string operline; + + void Execute(LocalUser* user) CXX11_OVERRIDE + { + user->Write(user->IsOper() ? operline : line); + } + + public: + WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg) + : line(":" + user->GetFullHost() + " QUIT :") + , operline(line) + { + line += msg; + operline += opermsg; + 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 +119,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 +150,220 @@ 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()); + user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->host.c_str(), operreason ? operreason->c_str() : quitreason.c_str()); 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 (!clientlist.erase(user->nick)) + ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Nick not found in clientlist, cannot remove: " + 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); + uuidlist.erase(user->uuid); + user->PurgeEmptyChannels(); } - -void UserManager::AddLocalClone(User *user) +void UserManager::AddClone(User* user) { - local_clones[user->GetCIDRMask()]++; -} - -void UserManager::AddGlobalClone(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) -{ - clonemap::iterator x = global_clones.find(user->GetCIDRMask()); - if (x != global_clones.end()) - return x->second; - else - return 0; -} - -unsigned long UserManager::LocalCloneCount(User *user) +const UserManager::CloneCounts& UserManager::GetCloneCounts(User* user) const { - clonemap::iterator x = local_clones.find(user->GetCIDRMask()); - if (x != local_clones.end()) - return x->second; + CloneMap::const_iterator it = clonemap.find(user->GetCIDRMask()); + if (it != clonemap.end()) + return it->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 how many users are opered */ -unsigned int UserManager::OperCount() -{ - return this->all_opers.size(); -} - -/* return how many users are unregistered */ -unsigned int UserManager::UnregisteredUserCount() -{ - return this->unregistered_count; -} - -/* return how many local registered users there are */ -unsigned int UserManager::LocalUserCount() -{ - /* Doesnt count unregistered clients */ - return (this->local_count - this->UnregisteredUserCount()); + return zeroclonecounts; } void UserManager::ServerNoticeAll(const char* text, ...) { - if (!text) - return; - - char textbuffer[MAXBUF]; - char formatbuffer[MAXBUF]; - va_list argsPtr; - va_start (argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); + std::string message; + VAFORMAT(message, text, text); + message = "NOTICE $" + ServerInstance->Config->ServerName + " :" + message; - 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::const_iterator i = local_users.begin(); i != local_users.end(); ++i) { User* t = *i; - t->WriteServ(std::string(formatbuffer)); + t->WriteServ(message); } } -void UserManager::ServerPrivmsgAll(const char* text, ...) +/* 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) { - if (!text) - return; + ModResult res; + FIRST_MOD_RESULT(OnCheckReady, res, (user)); + return (res == MOD_RES_PASSTHRU); +} - char textbuffer[MAXBUF]; - char formatbuffer[MAXBUF]; - va_list argsPtr; - va_start (argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); +/** + * 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() +{ + for (LocalList::iterator i = local_users.begin(); i != local_users.end(); ) + { + // 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; - snprintf(formatbuffer,MAXBUF,"PRIVMSG $%s :%s", ServerInstance->Config->ServerName.c_str(), textbuffer); + 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(); + } - for (LocalUserList::const_iterator i = local_users.begin(); i != local_users.end(); i++) - { - User* t = *i; - t->WriteServ(std::string(formatbuffer)); + 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; + } + + curr->Write("PING :" + ServerInstance->Config->ServerName); + 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; + } + + 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 4dbb73a1f..4945cbb21 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -24,111 +24,7 @@ #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++; - } -} bool User::IsNoticeMaskSet(unsigned char sm) { @@ -137,94 +33,76 @@ bool User::IsNoticeMaskSet(unsigned char sm) return (snomasks[sm-65]); } -void User::SetNoticeMask(unsigned char sm, bool value) -{ - if (!isalpha(sm)) - return; - snomasks[sm-65] = value; -} - -const char* User::FormatNoticeMasks() -{ - 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) +bool User::IsModeSet(unsigned char m) const { - if (!isalpha(m)) - return false; - return (modes[m-65]); + ModeHandler* mh = ServerInstance->Modes->FindMode(m, MODETYPE_USER); + return (mh && modes[mh->GetId()]); } -void User::SetMode(unsigned char m, bool value) +std::string User::GetModeLetters(bool includeparams) const { - 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, int 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) + , 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)); @@ -233,8 +111,6 @@ LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::so 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 +118,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 + "@" + host; return this->cached_makehost; } @@ -262,18 +128,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 +138,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 + "@" + dhost; 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 + "@" + host; 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,7 +184,7 @@ 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; } @@ -424,10 +204,10 @@ 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; } @@ -441,7 +221,8 @@ bool LocalUser::HasPrivPermission(const std::string &privstr, bool noisy) } 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; } @@ -467,7 +248,7 @@ void UserIOHandler::OnDataReady() while (user->CommandFloodPenalty < penaltymax && getSendQSize() < sendqmax) { std::string line; - line.reserve(MAXBUF); + line.reserve(ServerInstance->Config->Limits.MaxLine); std::string::size_type qpos = 0; while (qpos < recvq.length()) { @@ -482,21 +263,21 @@ void UserIOHandler::OnDataReady() case '\n': goto eol_found; } - if (line.length() < MAXBUF - 2) + if (line.length() < ServerInstance->Config->Limits.MaxLine - 2) line.push_back(c); } // if we got here, the recvq ran out before we found a newline return; eol_found: // 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(line, user); if (user->quitting) return; } @@ -531,7 +312,6 @@ CullResult User::cull() { if (!quitting) ServerInstance->Users->QuitUser(this, "Culled without QuitUser"); - PurgeEmptyChannels(); if (client_sa.sa.sa_family != AF_UNSPEC) ServerInstance->Users->RemoveCloneCounts(this); @@ -541,18 +321,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 +329,20 @@ 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)); + this->WriteCommand("MODE", "+o"); + FOREACH_MOD(OnOper, (this, info->name)); std::string opername; if (info->oper_block) @@ -585,24 +353,24 @@ 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(), host.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->Logs->Log("OPER", LOG_DEFAULT, "%s opered as type: %s", GetFullRealHost().c_str(), oper->name.c_str()); ServerInstance->Users->all_opers.push_back(this); // Expand permissions from config for faster lookup if (IS_LOCAL(this)) oper->init(); - FOREACH_MOD(I_OnPostOper,OnPostOper(this, oper->name, opername)); + FOREACH_MOD(OnPostOper, (this, oper->name, opername)); } void OperInfo::init() @@ -660,7 +428,7 @@ void OperInfo::init() void User::UnOper() { - if (!IS_OPER(this)) + if (!this->IsOper()) return; /* @@ -672,44 +440,28 @@ 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); - - 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); + // Remove the user from the oper list + stdalgo::vector::swaperase(ServerInstance->Users->all_opers, this); - this->modes[UM_OPERATOR] = 0; -} - -/* 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); } /* * Check class restrictions */ -void LocalUser::CheckClass() +void LocalUser::CheckClass(bool clone_count) { ConnectClass* a = this->MyClass; @@ -723,25 +475,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 }; @@ -764,7 +520,7 @@ bool User::CheckLines(bool doZline) void LocalUser::FullConnect() { - ServerInstance->stats->statsConnects++; + ServerInstance->stats.Connects++; this->idle_lastmsg = ServerInstance->Time(); /* @@ -781,19 +537,14 @@ 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__); + this->WriteNumeric(RPL_WELCOME, InspIRCd::Format("Welcome to the %s IRC Network %s", ServerInstance->Config->Network.c_str(), GetFullRealHost().c_str())); + this->WriteNumeric(RPL_YOURHOSTIS, InspIRCd::Format("Your host is %s, running version %s", ServerInstance->Config->ServerName.c_str(), INSPIRCD_BRANCH)); + this->WriteNumeric(RPL_SERVERCREATED, InspIRCd::TimeString(ServerInstance->startup_time, "This server was created %H:%M:%S %b %d %Y")); - 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()); + const TR1NS::array<std::string, 3>& modelist = ServerInstance->Modes->GetModeListFor004Numeric(); + this->WriteNumeric(RPL_SERVERVERSION, ServerInstance->Config->ServerName, INSPIRCD_BRANCH, modelist[0], modelist[1], modelist[2]); - ServerInstance->Config->Send005(this); - this->WriteNumeric(RPL_YOURUUID, "%s %s :your unique ID", this->nick.c_str(), this->uuid.c_str()); + ServerInstance->ISupport.SendTo(this); /* Now registered */ if (ServerInstance->Users->unregistered_count) @@ -801,17 +552,17 @@ void LocalUser::FullConnect() /* Trigger MOTD and LUSERS output, give modules a chance too */ ModResult MOD_RESULT; - std::string command("MOTD"); + std::string command("LUSERS"); 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); + ServerInstance->Parser.CallHandler(command, parameters, this); MOD_RESULT = MOD_RES_PASSTHRU; - command = "LUSERS"; + command = "MOTD"; FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, this, true, command)); if (!MOD_RESULT) - ServerInstance->Parser->CallHandler(command, parameters, this); + 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()); @@ -820,16 +571,16 @@ void LocalUser::FullConnect() * 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)); 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->fullname.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; } @@ -844,73 +595,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 @@ -919,29 +623,23 @@ 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) @@ -950,15 +648,25 @@ bool User::ChangeNick(const std::string& newnick, bool force) 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; } +void LocalUser::OverruleNick() +{ + this->WriteFrom(this, "NICK %s", this->uuid.c_str()); + 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() { switch (this->server_sa.sa.sa_family) @@ -971,18 +679,17 @@ int LocalUser::GetServerPort() return 0; } -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; } irc::sockets::cidr_mask User::GetCIDRMask() @@ -1032,7 +739,7 @@ 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)); } } @@ -1048,23 +755,23 @@ void User::Write(const char *text, ...) void LocalUser::Write(const std::string& text) { - if (!ServerInstance->SE->BoundsCheckFd(&eh)) + if (!SocketEngine::BoundsCheckFd(&eh)) return; - if (text.length() > MAXBUF - 2) + if (text.length() > ServerInstance->Config->Limits.MaxLine - 2) { // this should happen rarely or never. Crop the string at 512 and try again. - std::string try_again = text.substr(0, MAXBUF - 2); + std::string try_again(text, 0, ServerInstance->Config->Limits.MaxLine - 2); Write(try_again); return; } - ServerInstance->Logs->Log("USEROUTPUT", RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str()); + ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str()); eh.AddWriteBuf(text); eh.AddWriteBuf(wide_newline); - ServerInstance->stats->statsSent += text.length() + 2; + ServerInstance->stats.Sent += text.length() + 2; this->bytes_out += text.length() + 2; this->cmds_out++; } @@ -1073,14 +780,9 @@ void LocalUser::Write(const std::string& text) */ void LocalUser::Write(const char *text, ...) { - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->Write(std::string(textbuffer)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->Write(textbuffer); } void User::WriteServ(const std::string& text) @@ -1093,50 +795,49 @@ void User::WriteServ(const std::string& text) */ void User::WriteServ(const char* text, ...) { - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); - - this->WriteServ(std::string(textbuffer)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteServ(textbuffer); } - -void User::WriteNumeric(unsigned int numeric, const char* text, ...) +void User::WriteCommand(const char* command, const std::string& text) { - va_list argsPtr; - char textbuffer[MAXBUF]; - - va_start(argsPtr, text); - vsnprintf(textbuffer, MAXBUF, text, argsPtr); - va_end(argsPtr); + this->WriteServ(command + (this->registered & REG_NICK ? " " + this->nick : " *") + " " + text); +} - this->WriteNumeric(numeric, std::string(textbuffer)); +namespace +{ + std::string BuildNumeric(const std::string& source, User* targetuser, unsigned int num, const std::vector<std::string>& params) + { + const char* const target = (targetuser->registered & REG_NICK ? targetuser->nick.c_str() : "*"); + std::string raw = InspIRCd::Format(":%s %03u %s", source.c_str(), num, target); + if (!params.empty()) + { + for (std::vector<std::string>::const_iterator i = params.begin(); i != params.end()-1; ++i) + raw.append(1, ' ').append(*i); + raw.append(" :").append(params.back()); + } + return raw; + } } -void User::WriteNumeric(unsigned int numeric, const std::string &text) +void User::WriteNumeric(const Numeric::Numeric& numeric) { - 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)); + const std::string& servername = (numeric.GetServer() ? numeric.GetServer()->GetName() : ServerInstance->Config->ServerName); + this->Write(BuildNumeric(servername, this, numeric.GetNumeric(), numeric.GetParams())); } 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)); + const std::string message = ":" + user->GetFullHost() + " " + text; + this->Write(message); } @@ -1144,196 +845,110 @@ void User::WriteFrom(User *user, const std::string &text) 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)); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + this->WriteFrom(user, textbuffer); } - -/* 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 + { + const std::string& msg; - va_start(argsPtr, text); - vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr); - va_end(argsPtr); + void Execute(LocalUser* user) CXX11_OVERRIDE + { + user->Write(msg); + } - this->WriteCommonRaw(std::string(textbuffer), true); + public: + WriteCommonRawHandler(const std::string& message) + : msg(message) + { + } + }; } -void User::WriteCommonExcept(const char* text, ...) +void User::WriteCommon(const char* text, ...) { - 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); + std::string textbuffer; + VAFORMAT(textbuffer, text, text); + textbuffer = ":" + this->GetFullHost() + " " + textbuffer; + this->WriteCommonRaw(textbuffer, true); } void User::WriteCommonRaw(const std::string &line, bool include_self) { - if (this->registered != REG_ALL || quitting) - return; - - LocalUser::already_sent_id++; - - UserChanList include_c(chans); - std::map<User*,bool> exceptions; - - exceptions[this] = include_self; - - FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions)); - - for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i) - { - LocalUser* u = IS_LOCAL(i->first); - if (u && !u->quitting) - { - u->already_sent = LocalUser::already_sent_id; - if (i->second) - u->Write(line); - } - } - 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); - } - } - } + WriteCommonRawHandler handler(line); + ForEachNeighbor(handler, include_self); } -void User::WriteCommonQuit(const std::string &normal_text, const std::string &oper_text) +void User::ForEachNeighbor(ForEachNeighborHandler& handler, bool include_self) { - char tb1[MAXBUF]; - char tb2[MAXBUF]; - - if (this->registered != REG_ALL) - return; + // 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. - 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; + // 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 = uniq_id; - if (i->second) - u->Write(IS_OPER(u) ? out2 : out1); + // 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) + + // Now consider the real neighbors + for (IncludeChanList::const_iterator i = include_chans.begin(); i != include_chans.end(); ++i) { - const UserMembList* ulist = (*v)->GetUsers(); - for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++) + Channel* chan = (*i)->chan; + const Channel::MemberMap& userlist = chan->GetUsers(); + for (Channel::MemberMap::const_iterator j = userlist.begin(); j != userlist.end(); ++j) { - 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) -{ - Write(line); -} - -void RemoteUser::SendText(const std::string& line) -{ - ServerInstance->PI->PushToClient(this, line); -} - -void FakeUser::SendText(const std::string& line) +void User::WriteRemoteNumeric(const Numeric::Numeric& numeric) { -} - -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,22 +965,19 @@ 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::ChangeName(const std::string& gecos) { if (!this->fullname.compare(gecos)) return true; @@ -1376,85 +988,14 @@ bool User::ChangeName(const char* gecos) FIRST_MOD_RESULT(OnChangeLocalUserGECOS, MOD_RESULT, (IS_LOCAL(this),gecos)); if (MOD_RESULT == MOD_RES_DENY) return false; - FOREACH_MOD(I_OnChangeName,OnChangeName(this,gecos)); + FOREACH_MOD(OnChangeName, (this,gecos)); } this->fullname.assign(gecos, 0, ServerInstance->Config->Limits.MaxGecos); 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) return true; @@ -1467,106 +1008,30 @@ bool User::ChangeDisplayedHost(const char* shost) return false; } - FOREACH_MOD(I_OnChangeHost, OnChangeHost(this,shost)); - - std::string quitstr = ":" + GetFullHost() + " QUIT :Changing host"; - - /* Fix by Om: User::dhost is 65 long, this was truncating some long hosts */ - this->dhost.assign(shost, 0, 64); + FOREACH_MOD(OnChangeHost, (this,shost)); + this->dhost.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->dhost, "is now your displayed host"); return true; } -bool User::ChangeIdent(const char* newident) +bool User::ChangeIdent(const std::string& newident) { if (this->ident == newident) return true; - FOREACH_MOD(I_OnChangeIdent, OnChangeIdent(this,newident)); - - std::string quitstr = ":" + GetFullHost() + " QUIT :Changing ident"; + FOREACH_MOD(OnChangeIdent, (this,newident)); this->ident.assign(newident, 0, ServerInstance->Config->Limits.IdentMax); - this->InvalidateCache(); - this->DoHostCycle(quitstr); - return true; } -void User::SendAll(const char* command, const char* text, ...) -{ - 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; - - 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(); - } - - line.append(cl.substr(start, pos - start + 1)); - } - - if (line.length() != prefix.str().length()) - { - ServerInstance->SendWhoisLine(this, dest, 319, "%s", line.c_str()); - } -} - /* * Sets a user's connection class. * If the class name is provided, it will be used. Otherwise, the class will be guessed using host/ip/ident/etc. @@ -1578,27 +1043,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 +1071,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; } @@ -1622,7 +1087,7 @@ void LocalUser::SetClass(const std::string &explicit_name) if (!InspIRCd::MatchCIDR(this->GetIPString(), c->GetHost(), NULL) && !InspIRCd::MatchCIDR(this->host, 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 +1097,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,27 +1136,13 @@ 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); } @@ -1702,20 +1153,21 @@ const std::string& FakeUser::GetFullHost() { if (!ServerInstance->Config->HideWhoisServer.empty()) return ServerInstance->Config->HideWhoisServer; - return server; + return server->GetName(); } const std::string& FakeUser::GetFullRealHost() { if (!ServerInstance->Config->HideWhoisServer.empty()) return ServerInstance->Config->HideWhoisServer; - return server; + 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) { } @@ -1725,7 +1177,7 @@ ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask, cons 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) + limit(parent.limit), resolvehostnames(parent.resolvehostnames), ports(parent.ports) { } @@ -1748,4 +1200,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 2025a4810..830f6d3dd 100755 --- a/src/version.sh +++ b/src/version.sh @@ -1,2 +1,2 @@ #!/bin/sh -echo "InspIRCd-2.0.24" +echo "InspIRCd-3.0.0-a1" 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..6711f953a 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) +{ + std::stringstream masklist(masks); + std::string mask; + while (masklist >> 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 0506005ad..257af9ca7 100644 --- a/src/xline.cpp +++ b/src/xline.cpp @@ -23,7 +23,6 @@ #include "inspircd.h" #include "xline.h" -#include "bancache.h" /** An XLineFactory specialized to generate GLine* pointers */ @@ -156,9 +155,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 +278,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 +286,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 +308,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 +402,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 +411,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 +421,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 +441,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 +462,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; } @@ -526,30 +522,28 @@ bool XLine::IsBurstable() 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)) @@ -571,7 +565,8 @@ 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)) @@ -593,7 +588,8 @@ void GLine::Apply(User* u) bool ELine::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)) @@ -610,7 +606,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)) @@ -636,7 +633,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); } @@ -674,67 +671,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() -{ - 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)); -} - -void QLine::DisplayExpiry() -{ - 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)); -} - -void ZLine::DisplayExpiry() -{ - 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)); -} - -void KLine::DisplayExpiry() -{ - 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)); -} - -void GLine::DisplayExpiry() +void XLine::DisplayExpiry() { - 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)); + 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)); } -const char* ELine::Displayable() +const std::string& ELine::Displayable() { - return matchtext.c_str(); + return matchtext; } -const char* KLine::Displayable() +const std::string& KLine::Displayable() { - return matchtext.c_str(); + return matchtext; } -const char* GLine::Displayable() +const std::string& GLine::Displayable() { - return matchtext.c_str(); + return matchtext; } -const char* ZLine::Displayable() +const std::string& ZLine::Displayable() { - return ipaddr.c_str(); + return ipaddr; } -const char* QLine::Displayable() +const std::string& QLine::Displayable() { - return nick.c_str(); + return nick; } bool KLine::IsBurstable() |