/* +------------------------------------+ * | 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" static dynamic_reference accounts("account"); static dynamic_reference db("accountdb"); typedef std::map NickMap; static NickMap nickinfo; struct NickTSItem { irc::string nick; time_t ts; NickTSItem(const irc::string& newnick, time_t newTS) : nick(newnick), ts(newTS) { } }; typedef std::vector NicksOwned; class NicksOwnedExtItem : public TSGenericExtItem { bool CheckCollision(irc::string accountname, irc::string nick, time_t ts) { AccountDBEntry* owner = db->GetAccount(nick, false); if(owner) { if(owner->ts > ts) { db->RemoveAccount(false, owner); return false; } return true; } NickMap::iterator i = nickinfo.find(nick); if(i == nickinfo.end()) return false; AccountDBEntry* entry = i->second; NicksOwned* ext = get_value(entry); if(ext) for(NicksOwned::iterator iter = ext->begin(); iter != ext->end(); ++iter) if(iter->nick == nick) { if(iter->ts < ts || (iter->ts == ts && entry->name < accountname)) return true; ext->erase(iter); nickinfo.erase(i); return false; } throw ModuleException("An entry in nickinfo is incorrect"); } protected: virtual std::string value_serialize(SerializeFormat format, const NicksOwned* value) const { std::ostringstream str; if(format == FORMAT_USER) { for(NicksOwned::const_iterator i = value->begin(); i != value->end(); ++i) { str << i->nick << " "; } std::string retval = str.str(); return retval.empty() ? "-none-" : retval; } for(NicksOwned::const_iterator i = value->begin(); i != value->end(); ++i) { str << i->nick << "," << i->ts << " "; } return str.str(); } private: virtual NicksOwned* value_unserialize(SerializeFormat, const std::string&) { throw ModuleException("Called NicksOwnedExtItem::value_unserialize"); } virtual void value_resolve_conflict(NicksOwned*, NicksOwned*) { throw ModuleException("Called NicksOwnedExtItem::value_resolve_conflict"); } public: NicksOwnedExtItem(const std::string& Key, Module* parent) : TSGenericExtItem(Key, NicksOwned(), parent) { } virtual void unserialize(SerializeFormat format, Extensible* container, const std::string& value) { std::string::size_type delim = value.find_first_of(' '); time_t ts = atol(value.substr(0, delim).c_str()); std::string item; if(delim != std::string::npos) item = value.substr(delim + 1); value_pair* p = get(container); if(!p || ts > p->first || (ts == p->first && item < value_serialize(format, p->second))) { if(p) for(NicksOwned::const_iterator i = p->second->begin(); i != p->second->end(); ++i) nickinfo.erase(i->nick); std::string token; irc::string nick; time_t nickTS; irc::spacesepstream sep(item); std::vector > newinfo; NicksOwned* ptr = new NicksOwned; while(sep.GetToken(token)) { delim = token.find_first_of(','); if(delim == std::string::npos) continue; nick = token.substr(0, delim); nickTS = atol(token.substr(delim + 1).c_str()); if(!CheckCollision(static_cast(container)->name, nick, nickTS)) { newinfo.push_back(std::make_pair(nick, static_cast(container))); ptr->push_back(NickTSItem(nick, nickTS)); } } set(container, ts, ptr); nickinfo.insert(newinfo.begin(), newinfo.end()); } } virtual void free(void* item) { if(!item) return; NicksOwned* value = static_cast(item)->second; for(NicksOwned::const_iterator i = value->begin(); i != value->end(); ++i) nickinfo.erase(i->nick); this->TSGenericExtItem::free(item); } }; static NicksOwnedExtItem* nicks_ext; static void RemoveNick(const irc::string& nick) { NickMap::iterator i = nickinfo.find(nick); NicksOwned* ext = nicks_ext->get_value(i->second); if(ext) for(NicksOwned::iterator iter = ext->begin(); iter != ext->end(); ++iter) if(iter->nick == nick) { ext->erase(iter); nickinfo.erase(i); return; } throw ModuleException("An entry in nickinfo is incorrect"); } class CommandAddnick : public Command { public: unsigned int limit; CommandAddnick(Module* parent) : Command(parent, "ADDNICK", 0, 0) { } CmdResult Handle(const std::vector& parameters, User* user) { AccountDBEntry* entry; if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false))) { user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str()); return CMD_FAILURE; } if (!ServerInstance->IsNick(user->nick.c_str(), ServerInstance->Config->Limits.NickMax)) { user->WriteServ("NOTICE %s :You may not register your UID", user->nick.c_str()); return CMD_FAILURE; } if(db->GetAccount(user->nick, true)) { user->WriteServ("NOTICE %s :Nick %s is already registered", user->nick.c_str(), user->nick.c_str()); return CMD_FAILURE; } NicksOwnedExtItem::value_pair* p = nicks_ext->get(entry); if(!p) { NicksOwned* value = new NicksOwned; value->push_back(NickTSItem(user->nick, ServerInstance->Time())); nicks_ext->set(entry, value); } else if(limit && p->second->size() >= limit) { user->WriteServ("NOTICE %s :You already have the maximum number of nicks registered", user->nick.c_str()); return CMD_FAILURE; } else { p->first = ServerInstance->Time(); p->second->push_back(NickTSItem(user->nick, ServerInstance->Time())); } nickinfo.insert(std::make_pair(user->nick, entry)); db->SendUpdate(entry, "Nicks_owned"); ServerInstance->SNO->WriteGlobalSno('u', "%s used ADDNICK to register their nick to %s", user->nick.c_str(), entry->name.c_str()); user->WriteServ("NOTICE %s :Nick %s has been registered to account %s", user->nick.c_str(), user->nick.c_str(), entry->name.c_str()); return CMD_SUCCESS; } }; class CommandDelnick : public Command { public: CommandDelnick(Module* parent) : Command(parent, "DELNICK", 0, 1) { syntax = "[nick]"; } CmdResult Handle(const std::vector& parameters, User* user) { AccountDBEntry* entry; irc::string nick = parameters.size() ? parameters[0] : user->nick; if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false))) { user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str()); return CMD_FAILURE; } AccountDBEntry* owner = db->GetAccount(nick, false); NickMap::iterator iter = nickinfo.find(nick); if(owner == entry) { user->WriteServ("NOTICE %s :Nick %s is your primary nick and may not be deleted", user->nick.c_str(), nick.c_str()); return CMD_FAILURE; } else if(owner || iter == nickinfo.end() || iter->second != entry) { user->WriteServ("NOTICE %s :Nick %s is not registered to you", user->nick.c_str(), nick.c_str()); return CMD_FAILURE; } NicksOwnedExtItem::value_pair* p = nicks_ext->get(entry); if(p) for(NicksOwned::iterator i = p->second->begin(); i != p->second->end(); ++i) if(i->nick == nick) { p->first = ServerInstance->Time(); p->second->erase(i); nickinfo.erase(iter); db->SendUpdate(entry, "Nicks_owned"); ServerInstance->SNO->WriteGlobalSno('u', "%s used DELNICK to unregister nick %s from %s", user->nick.c_str(), nick.c_str(), entry->name.c_str()); user->WriteServ("NOTICE %s :Nick %s has been unregistered", user->nick.c_str(), nick.c_str()); return CMD_SUCCESS; } throw ModuleException("An entry in nickinfo is incorrect"); } }; class CommandFdelnick : public Command { public: CommandFdelnick(Module* Creator) : Command(Creator,"FDELNICK", 2, 2) { flags_needed = 'o'; syntax = " "; } CmdResult Handle (const std::vector& parameters, User *user) { AccountDBEntry* entry = db->GetAccount(parameters[0], false); if(!entry) { user->WriteServ("NOTICE %s :No such account", user->nick.c_str()); return CMD_FAILURE; } if(entry->name == parameters[1]) { user->WriteServ("NOTICE %s :Nick %s is %s's primary nick and may not be deleted", user->nick.c_str(), entry->name.c_str(), entry->name.c_str()); return CMD_FAILURE; } NickMap::iterator iter = nickinfo.find(parameters[1]); if(iter == nickinfo.end() || iter->second != entry) { user->WriteServ("NOTICE %s :Nick %s is not registered to them", user->nick.c_str(), parameters[1].c_str()); return CMD_FAILURE; } NicksOwnedExtItem::value_pair* p = nicks_ext->get(entry); if(p) for(NicksOwned::iterator i = p->second->begin(); i != p->second->end(); ++i) if(i->nick == parameters[1]) { p->first = ServerInstance->Time(); p->second->erase(i); nickinfo.erase(iter); db->SendUpdate(entry, "Nicks_owned"); ServerInstance->SNO->WriteGlobalSno('a', "%s used FDELNICK to unregister nick %s from account %s", user->nick.c_str(), parameters[1].c_str(), entry->name.c_str()); user->WriteServ("NOTICE %s :Nick %s has been unregistered from account %s", user->nick.c_str(), parameters[1].c_str(), entry->name.c_str()); return CMD_SUCCESS; } throw ModuleException("An entry in nickinfo is incorrect"); } }; /** Handle /SETENFORCE */ class CommandSetenforce : public Command { public: TSBoolExtItem enforce; CommandSetenforce(Module* Creator) : Command(Creator,"SETENFORCE", 1, 1), enforce("Automatically_enforce_nicks", false, true, Creator) { syntax = "OFF|ON"; } CmdResult Handle (const std::vector& parameters, User *user) { AccountDBEntry* entry; if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false))) { user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str()); return CMD_FAILURE; } bool newsetting; if(irc::string(parameters[0]) == "ON") newsetting = true; else if(irc::string(parameters[0]) == "OFF") newsetting = false; else { user->WriteServ("NOTICE %s :Unknown setting", user->nick.c_str()); return CMD_FAILURE; } enforce.set(entry, newsetting); db->SendUpdate(entry, "Automatically_enforce_nicks"); user->WriteServ("NOTICE %s :Nick enforcement for account %s %s successfully", user->nick.c_str(), entry->name.c_str(), newsetting ? "enabled" : "disabled"); return CMD_SUCCESS; } }; /** Handle /ENFORCE */ class CommandEnforce : public Command { public: CommandEnforce(Module* Creator) : Command(Creator,"ENFORCE", 1, 1) { syntax = ""; } CmdResult Handle (const std::vector& parameters, User *user) { if(IS_LOCAL(user)) { AccountDBEntry* entry; if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false))) { user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str()); return CMD_FAILURE; } NickMap::iterator iter = nickinfo.find(parameters[0]); if(entry->name != parameters[0] && (iter == nickinfo.end() || iter->second != entry)) { user->WriteServ("NOTICE %s :Nick %s is not registered to you", user->nick.c_str(), parameters[0].c_str()); return CMD_FAILURE; } } User* target = ServerInstance->FindNick(parameters[0]); if(!target) { user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str()); return CMD_FAILURE; } if(IS_LOCAL(user)) user->WriteServ("NOTICE %s :Nick %s enforced successfully", user->nick.c_str(), parameters[0].c_str()); if(IS_LOCAL(target)) { target->WriteNumeric(433, "%s %s :Nickname overruled: ENFORCE command used", target->nick.c_str(), target->nick.c_str()); target->ChangeNick(target->uuid, true); if(user->registered != REG_ALL) user->registered &= ~REG_NICK; } return CMD_SUCCESS; } RouteDescriptor GetRouting(User* user, const std::vector& parameters) { User* dest = ServerInstance->FindNick(parameters[0]); if (dest) return ROUTE_OPT_UCAST(dest->server); return ROUTE_LOCALONLY; } }; class ModuleAccountNickOwnership : public Module { public: NicksOwnedExtItem nicks; CommandAddnick cmd_addnick; CommandDelnick cmd_delnick; CommandFdelnick cmd_fdelnick; CommandSetenforce cmd_setenforce; CommandEnforce cmd_enforce; ModuleAccountNickOwnership() : nicks("Nicks_owned", this), cmd_addnick(this), cmd_delnick(this), cmd_fdelnick(this), cmd_setenforce(this), cmd_enforce(this) { nicks_ext = &nicks; } void init() { ServerInstance->Modules->AddService(nicks); ServerInstance->Modules->AddService(cmd_addnick); ServerInstance->Modules->AddService(cmd_delnick); ServerInstance->Modules->AddService(cmd_fdelnick); ServerInstance->Modules->AddService(cmd_setenforce); ServerInstance->Modules->AddService(cmd_setenforce.enforce); ServerInstance->Modules->AddService(cmd_enforce); Implementation eventlist[] = { I_OnUserPreNick, I_OnCheckReady, I_OnUserConnect, I_OnEvent }; ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); } void ReadConfig(ConfigReadStatus&) { ConfigTag *tag = ServerInstance->Config->GetTag ("nickownership"); cmd_addnick.limit = tag->getInt ("limit", 5); } ModResult OnUserPreNick(User* user, const std::string& nick) { if (ServerInstance->NICKForced.get(user)) return MOD_RES_PASSTHRU; // check the new nick AccountDBEntry* owner = db->GetAccount(nick, true); if(!owner || (accounts && accounts->GetAccountName(user) == owner->name)) return MOD_RES_PASSTHRU; bool* enforce = cmd_setenforce.enforce.get_value(owner); if (!enforce || !*enforce) { if(user->registered == REG_ALL) user->WriteServ("NOTICE %s :Nick %s is registered to the account '%s'", user->nick.c_str(), nick.c_str(), owner->name.c_str()); return MOD_RES_PASSTHRU; } else if (user->registered == REG_ALL) { user->WriteNumeric(433, "%s %s :You must be identified to the account '%s' to use this nick", user->nick.c_str(), nick.c_str(), owner->name.c_str()); return MOD_RES_DENY; } else { // allow through to give things like SASL or SQLauth time to return before denying // sending a 437 here makes both irssi and xchat auto-select a different nick, so we'll send a NOTICE instead user->WriteServ("NOTICE %s :Nick %s requires you to identify to the account '%s'", user->nick.c_str(), nick.c_str(), owner->name.c_str()); return MOD_RES_PASSTHRU; } } ModResult OnCheckReady(LocalUser* user) { AccountDBEntry* owner = db->GetAccount(user->nick, true); if(!owner) return MOD_RES_PASSTHRU; bool* enforce = cmd_setenforce.enforce.get_value(owner); if (enforce && *enforce && (!accounts || owner->name != accounts->GetAccountName(user))) { user->WriteNumeric(433, "%s %s :Nickname overruled: requires login to the account '%s'", user->nick.c_str(), user->nick.c_str(), owner->name.c_str()); user->ChangeNick(user->uuid, true); user->registered &= ~REG_NICK; return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } virtual void OnUserConnect(LocalUser* user) { AccountDBEntry* owner = db->GetAccount(user->nick, true); if(!owner) return; bool* enforce = cmd_setenforce.enforce.get_value(owner); if ((!enforce || !*enforce) && (!accounts || owner->name != accounts->GetAccountName(user))) user->WriteServ("NOTICE %s :Nick %s is registered to the account '%s'", user->nick.c_str(), user->nick.c_str(), owner->name.c_str()); } void OnEvent(Event& event) { if(event.id == "get_account_by_alias"){ GetAccountByAliasEvent& e = static_cast(event); if(e.entry) return; // Some other module already populated the event NickMap::iterator iter = nickinfo.find(e.account); if(iter != nickinfo.end()) { e.entry = iter->second; NicksOwned* ext = nicks.get_value(iter->second); if(ext) for(NicksOwned::const_iterator i = ext->begin(); i != ext->end(); ++i) if(i->nick == e.account) { e.alias_ts = i->ts; e.RemoveAliasImpl = &RemoveNick; return; } throw ModuleException("An entry in nickinfo is incorrect"); } } } void Prioritize() { ServerInstance->Modules->SetPriority(this, I_ModuleInit, PRIORITY_AFTER, ServerInstance->Modules->Find("m_account.so")); // need to be after all modules that might deny the ready check ServerInstance->Modules->SetPriority(this, I_OnCheckReady, PRIORITY_LAST); } Version GetVersion() { return Version("Nick registration tracking", VF_VENDOR | VF_OPTCOMMON); } }; MODULE_INIT(ModuleAccountNickOwnership)