/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2011 InspIRCd Development Team * See: http://wiki.inspircd.org/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include "account.h" /* $ModDesc: Provides channel mode +r for channel registration */ static dynamic_reference db("accountdb"); /* Some function for checking if the selected account is a registrant for a selected channel, for writing less code later */ static bool IsRegistrant (ModeHandler *mh, Channel *chan, const irc::string &account) { irc::string token; irc::commasepstream registrantnames(chan->GetModeParameter (mh)); while (registrantnames.GetToken (token)) { if (token == account) return true; } return false; } /* The last_activity extension item: last part from the channel, used to implement expiry */ class ChanExpiryExtItem : public ExtensionItem { public: ChanExpiryExtItem (Module *owner) : ExtensionItem (EXTENSIBLE_CHANNEL, "last_activity", owner) { } /* a function to get pointer to the timestamp for a given channel */ time_t get (const Extensible *container) const { return reinterpret_cast (get_raw (container)); } void set (Extensible *container, time_t value) { set_raw (container, reinterpret_cast (value)); } void unset (Extensible *container) { unset_raw (container); } void free (void *value) { } /* task of this function is to make the given string into the time_t and to put it in the extension item for the given container */ void unserialize (SerializeFormat format, Extensible *container, const std::string &value) { /* independently of the format, the string is always the timestamp */ /* if nothing happens, we are sure that no one will normally send the metadata except netburst */ /* if this will be a netburst, we are sure no one will unset it, it just won't be send at all */ /* so string can't be empty, and thus, the extension item's value can be not found, but can't be 0 */ /* get the null terminated string from the std::string class, convert it to the integer/time_t */ time_t newtime = atol (value.c_str ( )); /* if newtime is less or equal than the current time set then don't change */ if (newtime <= get (container)) return; /* if not */ set (container, newtime); } std::string serialize (SerializeFormat format, const Extensible *container, void *item) const { /* we got a value, if it is null then return empty string to deny sending metadata */ /* if it is not null, it's a pointer to time_t, so cast it, convert to string and return */ if (!item) return ""; time_t v = reinterpret_cast (item); return ConvToStr (v); } }; /* class for handling +r mode */ class RegisterModeHandler : public ParamChannelModeHandler { public: ChanExpiryExtItem last_activity; dynamic_reference account; bool verbose; int chanlimit; RegisterModeHandler(Module *me) : ParamChannelModeHandler(me, "registered", 'r'), last_activity(me), account("account"), verbose(true), chanlimit(10) { fixed_letter = false; } void OnParameterMissing (User *user, User*, Channel *chan, std::string ¶m) { // put the user's own account name in the parameter, if missing if (account) param = account->GetAccountName(user); } /* make access checks */ void AccessCheck(ModePermissionData& perm) { irc::string acctname = account ? account->GetAccountName(perm.source) : ""; if (perm.mc.adding) { if (perm.chan->IsModeSet(this)) { /* changing registration: must be a registrant */ if (acctname.empty()) { // not logged in: different error message perm.ErrorNumeric(ERR_CHANOPRIVSNEEDED, "%s :You must be logged in to an account to reregister a channel", perm.chan->name.c_str()); perm.result = MOD_RES_DENY; return; } if (!IsRegistrant (this, perm.chan, acctname)) { // no matching account name perm.ErrorNumeric(ERR_CHANOPRIVSNEEDED, "%s :Only a registrant of a channel may reregister it", perm.chan->name.c_str()); perm.result = MOD_RES_DENY; return; } } if (perm.source->HasPrivPermission("channels/set-registration", false)) return; /* new functionality, you can't register any channel if you don't have the oper privilege and chanlimit == 0 */ if (chanlimit == 0) { perm.ErrorNumeric (ERR_NOPRIVILEGES, ":Permission denied - only IRC operators may register channels"); perm.result = MOD_RES_DENY; return; } /* calculate how many channels the user has registered himself */ int chans = 0; for (chan_hash::const_iterator it = ServerInstance->chanlist->begin ( ); it != ServerInstance->chanlist->end ( ); it++) { if (it->second != perm.chan && it->second->IsModeSet (this)) { irc::commasepstream registrantnames (it->second->GetModeParameter (this)); irc::string token; registrantnames.GetToken (token); if (token == acctname) chans++; } } /* check for limit */ if (chans >= chanlimit) { perm.ErrorNumeric(ERR_CHANOPRIVSNEEDED, "%s :You can't register more than %i channels under one account", perm.chan->name.c_str(), chans); perm.result = MOD_RES_DENY; return; } /* otherwise, you can only set it to your own account name */ irc::commasepstream registrantnames(perm.mc.value); irc::string registrantname; /* if the account name was not given, is empty or is not equal to the given parameter, deny */ if (acctname.empty() || !registrantnames.GetToken(registrantname) || acctname != registrantname) { perm.ErrorNumeric(ERR_CHANOPRIVSNEEDED, "%s :The first account in the %sregistrant list must be your own", perm.chan->name.c_str(), perm.chan->IsModeSet(this) ? "new " : ""); perm.result = MOD_RES_DENY; } } else { /* removing a mode: must be a registrant */ if (acctname.empty()) { // not logged in: different error message perm.ErrorNumeric(ERR_CHANOPRIVSNEEDED, "%s :You must be logged in to an account to unregister a channel", perm.chan->name.c_str()); perm.result = MOD_RES_DENY; return; } if (IsRegistrant (this, perm.chan, acctname)) return; // no matching account name perm.ErrorNumeric(ERR_CHANOPRIVSNEEDED, "%s :Only a registrant of a channel may unregister it", perm.chan->name.c_str()); perm.result = MOD_RES_DENY; } } ModeAction OnModeChange (User *source, User*, Channel *chan, std::string ¶m, bool adding) { if (adding) { std::string now = chan->GetModeParameter(this), token; if(!IS_SERVER(source) && db) { irc::commasepstream registrantnames(param); std::vector registrants; while (registrantnames.GetToken (token)) if (db->GetAccount(token, false)) registrants.push_back(token); if(registrants.empty()) return MODEACTION_DENY; param.clear(); for(std::vector::iterator i = registrants.begin(); i != registrants.end(); ++i) param.append(*i).push_back(','); param = param.substr(0, param.size() - 1); } if (param == now) return MODEACTION_DENY; if (!now.empty()) { ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(source) || source == ServerInstance->FakeClient ? 'r' : 'R', "%s changed registration of %s from %s to %s", source->GetFullHost().c_str(), chan->name.c_str(), now.c_str(), param.c_str()); if (verbose) chan->WriteChannel(ServerInstance->FakeClient, "NOTICE %s :This channel has been reregistered (%s -> %s)", chan->name.c_str(), now.c_str(), param.c_str()); } /* servers are expected to notice their reasons themselves */ else if (!IS_SERVER(source)) { /* first, send a message to all ircops with snomask +r set */ ServerInstance->SNO->WriteToSnoMask (IS_LOCAL (source) ? 'r' : 'R', "%s registered channel %s to account %s", source->GetFullHost ( ).c_str ( ), chan->name.c_str ( ), param.c_str ( )); /* now, send similar to channel */ if (verbose) chan->WriteChannel (ServerInstance->FakeClient, "NOTICE %s :This channel has been registered", chan->name.c_str ( )); } last_activity.set(chan, ServerInstance->Time()); chan->SetModeParam (this, param); } else { /* servers are expected to notice their reasons themselves */ if (!IS_SERVER(source)) { /* it is set, so first send a server notice to all ircops using +r snomask that this channel has been unregistered */ ServerInstance->SNO->WriteToSnoMask (IS_LOCAL (source) ? 'r' : 'R', "channel %s unregistered by %s", chan->name.c_str ( ), source->GetFullHost ( ).c_str ( )); /* then send this important message to the channel as the notice */ if (verbose) chan->WriteChannel (ServerInstance->FakeClient, "NOTICE %s :This channel has been unregistered", chan->name.c_str ( )); } chan->SetModeParam(this, ""); last_activity.unset(chan); /* now, if usercount is 0, delete the channel */ if (!chan->GetUserCounter()) chan->DelUser(ServerInstance->FakeClient); } return MODEACTION_ALLOW; } void set_prefixrequired (ModeHandler *mh) { levelrequired = mh->GetPrefixRank(); } }; class ChannelRegistrationModule : public Module { private: RegisterModeHandler mh; time_t expiretime; /* check if the channel given as a parameter expired */ bool Expired (Channel *chan) { // can't expire if +r isn't set if (!chan->IsModeSet(&mh)) return false; // won't expire if +P is set if (chan->IsModeSet("permanent")) return false; // is it too old? if (ServerInstance->Time() - expiretime >= mh.last_activity.get(chan)) return true; return false; } public: /* module constructor, for initializing stuff */ ChannelRegistrationModule() : mh(this) { } /* get module version and flags. */ Version GetVersion() { return Version("Provides channel mode +r for channel registration", VF_VENDOR); } void init ( ) { ServerInstance->SNO->EnableSnomask ('r', "CHANREGISTER"); Implementation eventlist[] = { I_OnCheckJoin, I_OnPermissionCheck, I_OnChannelPreDelete, I_OnUserQuit, I_OnUserPart, I_OnUserKick, I_OnGarbageCollect }; ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); ServerInstance->Modules->AddService(mh); ServerInstance->Modules->AddService(mh.last_activity); } /* rehash event */ void ReadConfig(ConfigReadStatus& status) { ConfigTag *tag = ServerInstance->Config->GetTag ("chanregister"); /* expiration is in user-readable time format */ expiretime = ServerInstance->Duration (tag->getString ("expiretime", "21d")); mh.verbose = tag->getBool ("verbose", true); mh.chanlimit = tag->getInt ("limit", 10); std::string prefixmode = tag->getString("prefix", "op"); ModeHandler *prefixmodehandler = ServerInstance->Modes->FindMode(prefixmode); if (prefixmodehandler) mh.set_prefixrequired (prefixmodehandler); else status.ReportError(tag, "The given prefix level was not found"); } /* OnCheckJoin - this is an event for checking permissions of some user to join some channel, it is used to allow joining by registrants even when banned */ void OnCheckJoin (ChannelPermissionData &joindata) { /* if channel is null, it's not something that is done on channels, or it is but channel is being created, so no registrant in it */ if (!joindata.chan) return; /* return if mode is not set and channel is unregistered */ if (!joindata.chan->IsModeSet (&mh)) return; /* get user's account name */ irc::string acctname = mh.account ? mh.account->GetAccountName(joindata.source) : ""; /* if account is not found or empty, we can be really sure that we really aren't registrant of any channel */ if (acctname.empty()) return; /* it is set and we have parameter, get it */ if (IsRegistrant (&mh, joindata.chan, acctname)) { /* if registrantname and account name are the same, override */ joindata.result = MOD_RES_ALLOW; return; } } /* check permissions */ void OnPermissionCheck (PermissionData &perm) { /* if channel is null, proceed normally */ if (!perm.chan) return; /* if not, but name of permission is join, proceed normally because it was checked */ if (perm.name == "join") return; /* if permission is implicit, proceed normally, as registrants may not want these permissions */ if (perm.implicit) return; /* if +r mode is being changed, return: we keep the normal checks here */ if (perm.name == "mode/registered") return; /* if anything is ok, but mode +r is unset, return and check perms normally */ if (!perm.chan->IsModeSet (&mh)) return; /* if set, get the registrant account name */ /* get account name of the current user */ irc::string acctname = mh.account ? mh.account->GetAccountName(perm.source) : ""; /* if user is not logged in then return */ if (acctname.empty()) return; if (IsRegistrant (&mh, perm.chan, acctname)) { /* if ok, then set result to allow if registrant name matches account name */ perm.result = MOD_RES_ALLOW; return; } } /* called before channel is being deleted */ ModResult OnChannelPreDelete (Channel *chan) { /* return 1 to prevent channel deletion if channel is registered */ if (chan->IsModeSet(&mh)) return MOD_RES_DENY; /* in other case, return 0, to allow channel deletion */ return MOD_RES_PASSTHRU; } /* when someone unsets +r, OnMode event won't mark the database as requiring saving, but it requires to be saved, and because of this, this event handler is needed */ /* this is called when some user parts a channel and is used to record the time user parted as the last activity time */ /* this is done only if channel is registered, and is used only when it's empty */ void OnUserPart (Membership *memb, std::string &msg, CUList &except_list) { /* we have some membership, let's set this last part time now */ if (memb->chan->IsModeSet (&mh)) mh.last_activity.set (memb->chan, ServerInstance->Time ( )); } /* called when the user is being kicked, it's also used for setting last activity time */ void OnUserKick (User *source, Membership *memb, const std::string &reason, CUList &except_list) { /* the same */ if (memb->chan->IsModeSet (&mh)) mh.last_activity.set (memb->chan, ServerInstance->Time ( )); } /* if user quits, set last activity time for each channel he was in that are registered */ void OnUserQuit (User *user, const std::string &msg, const std::string &opermsg) { /* iterate through a channel list */ for (UCListIter it = user->chans.begin ( ); it != user->chans.end ( ); it++) { /* for each membership, check if channel is registered, if it is, set last activity time for it */ if (it->chan->IsModeSet (&mh)) mh.last_activity.set (it->chan, ServerInstance->Time ( )); } } void Prioritize() { ServerInstance->Modules->SetPriority(this, I_OnGarbageCollect, PRIORITY_AFTER, ServerInstance->Modules->Find("m_account_register.so")); } /* called once a hour to expire expired channels */ void OnGarbageCollect ( ) { chan_hash::const_iterator it = ServerInstance->chanlist->begin(); std::vector registrants; bool listChanged; while (it != ServerInstance->chanlist->end()) { Channel* c = it++->second; /* check if the channel was expired and is empty at the same time */ if (c->GetUserCounter() == 0 && Expired(c)) { /* send a notice */ ServerInstance->SNO->WriteGlobalSno('r', "Channel %s was expired", c->name.c_str()); /* send mode -r */ irc::modestacker ms; ms.push(irc::modechange(mh.id, "", false)); ServerInstance->SendMode(ServerInstance->FakeClient, c, ms, true); } if(!c->IsModeSet(&mh)) continue; if(!db) // if we're using a services packages for nick registration rather than the account system, continue; // then we can't check whether registrants exist anymore, so we just go to the next channel irc::string token; irc::commasepstream registrantnames(c->GetModeParameter (&mh)); registrants.clear(); listChanged = false; registrantnames.GetToken(token); if(db->GetAccount(token, false)) registrants.push_back(token); else { /* send a notice */ ServerInstance->SNO->WriteGlobalSno('r', "Channel %s was deregistered because the primary registrant account was missing", c->name.c_str()); /* send mode -r */ irc::modestacker ms; ms.push(irc::modechange(mh.id, "", false)); ServerInstance->SendMode(ServerInstance->FakeClient, c, ms, true); continue; } while (registrantnames.GetToken (token)) { if (db->GetAccount(token, false)) registrants.push_back(token); else listChanged = true; } if(listChanged) { std::string newvalue; for(std::vector::iterator i = registrants.begin(); i != registrants.end(); ++i) newvalue.append(*i).push_back(','); irc::modestacker ms; ms.push(irc::modechange(mh.id, newvalue.substr(0, newvalue.length() - 1), true)); ServerInstance->SendMode(ServerInstance->FakeClient, c, ms, true); } } } }; /* register the module */ MODULE_INIT(ChannelRegistrationModule)