/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon * +------------------------------------+ * * InspIRCd: (C) 2002-2010 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 /* $ModDesc: Allows for opered clients to join channels without being seen, similar to unreal 3.1 +I mode */ /* $ModDepends: core 2.0 */ class InvisibleMode : public ModeHandler { public: InvisibleMode(Module* Creator) : ModeHandler(Creator, "invis-oper", 'Q', PARAM_NONE, MODETYPE_USER) { oper = true; } ~InvisibleMode() { } ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { if (dest->IsModeSet('Q') != adding) { dest->SetMode('Q', adding); /* Fix for bug #379 reported by stealth. On +/-Q make m_watch think the user has signed on/off */ Module* m = ServerInstance->Modules->Find("m_watch.so"); /* This must come before setting/unsetting the handler */ if (m && adding) m->OnUserQuit(dest, "Connection closed", "Connection closed"); /* User appears to vanish or appear from nowhere */ for (UCListIter f = dest->chans.begin(); f != dest->chans.end(); f++) { const UserMembList *ulist = (*f)->GetUsers(); char tb[MAXBUF]; snprintf(tb,MAXBUF,":%s %s %s", dest->GetFullHost().c_str(), adding ? "PART" : "JOIN", (*f)->name.c_str()); std::string out = tb; Membership* memb = (**f).GetUser(dest); std::string ms = memb->modes; for(unsigned int i=0; i < memb->modes.length(); i++) ms.append(" ").append(dest->nick); for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) { /* User only appears to vanish for non-opers */ if (IS_LOCAL(i->first) && !IS_OPER(i->first)) { i->first->Write(out); if (!ms.empty() && !adding) i->first->WriteServ("MODE %s +%s", (**f).name.c_str(), ms.c_str()); } } } ServerInstance->SNO->WriteToSnoMask('a', "\2NOTICE\2: Oper %s has become %svisible (%cQ)", dest->GetFullHost().c_str(), adding ? "in" : "", adding ? '+' : '-'); return MODEACTION_ALLOW; } else { return MODEACTION_DENY; } } }; class ModuleInvisible : public Module { private: InvisibleMode qm; bool hidejoin; bool hidelist; bool hidemode; bool hidewho; bool hidemsg; bool hidewhois; bool hidestats; bool evade; bool safety; bool override_active; public: ModuleInvisible() : qm(this), override_active(false) { } void init() { ServerInstance->Modules->AddService(qm); /* Yeah i know people can take this out. I'm not about to obfuscate code just to be a pain in the ass. */ ServerInstance->Users->ServerNoticeAll("*** m_invisible.so has just been loaded on this network. For more information, please visit http://inspircd.org/wiki/Modules/invisible"); Implementation eventlist[] = { I_OnUserPreMessage, I_OnUserPreNotice, I_OnUserJoin, I_OnBuildNeighborList, I_OnSendWhoLine, I_OnNamesListItem, I_OnRehash, I_OnUserPreNotice, I_OnWhoisLine, I_OnRawMode, I_OnPreMode, I_OnUserPreNick, I_OnUserPart, I_OnStats, I_OnUserPreKick }; ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); OnRehash(NULL); } void OnRehash(User*) { ConfigTag* tag = ServerInstance->Config->ConfValue("invisible"); hidejoin = tag->getBool("join", true); hidelist = tag->getBool("list", true); hidewho = tag->getBool("who", true); hidemsg = tag->getBool("msg", true); hidemode = tag->getBool("mode", true); hidewhois = tag->getBool("whois", true); hidestats = tag->getBool("stats", true); evade = tag->getBool("evade", true); safety = tag->getBool("safety", false); } Version GetVersion() { return Version("Allows opers to join channels invisibly", VF_COMMON); } static void BuildExcept(Membership* memb, CUList& excepts) { const UserMembList* users = memb->chan->GetUsers(); for(UserMembCIter i = users->begin(); i != users->end(); i++) if (memb->user != i->first && IS_LOCAL(i->first) && !i->first->HasPrivPermission("invisible/see")) excepts.insert(i->first); } void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) { if (hidejoin && memb->user->IsModeSet('Q')) { BuildExcept(memb, excepts); ServerInstance->SNO->WriteToSnoMask('a', "\2NOTICE\2: Oper %s has joined %s invisibly (+Q)", memb->user->GetFullHost().c_str(), memb->chan->name.c_str()); } } void OnUserPart(Membership* memb, std::string& partmessage, CUList& except_list) { if (memb->user->IsModeSet('Q')) BuildExcept(memb, except_list); } void OnBuildNeighborList(User* source, UserChanList &include, std::map &exceptions) { if (hidewho && source->IsModeSet('Q')) include.clear(); } ModResult OnUserPreKick(User* source, Membership* memb, const std::string& reason) { if (!hidemode || !IS_LOCAL(source) || !memb->user->IsModeSet('Q')) return MOD_RES_PASSTHRU; source->WriteNumeric(401, "%s %s :No such nick/channel", source->nick.c_str(), memb->user->nick.c_str()); return MOD_RES_DENY; } ModResult OnStats(char symbol, User* user, std::vector& results) { if (!hidestats || symbol != 'P') return MOD_RES_PASSTHRU; // Steal some logic from cmd_stats.cpp unsigned int idx = 0; std::string sn(ServerInstance->Config->ServerName); for (std::list::const_iterator i = ServerInstance->Users->all_opers.begin(); i != ServerInstance->Users->all_opers.end(); ++i) { User* oper = *i; if (!ServerInstance->ULine(oper->server) && !oper->IsModeSet('Q')) { 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)"); return MOD_RES_DENY; } ModResult OnUserPreNick(User* user, const std::string& newnick) { User* nick = ServerInstance->FindNick(newnick); if (evade && nick && IS_LOCAL(user) && nick->IsModeSet('Q') && nick != user) // Hahaha you'll never catch me! nick->ChangeNick(nick->uuid); return MOD_RES_PASSTHRU; } ModResult OnRawMode(User* user, Channel* chan, const char mode, const std::string& param, bool adding, int pcnt) { if (!hidemode || override_active || !chan | !IS_LOCAL(user)) return MOD_RES_PASSTHRU; User* nick = ServerInstance->FindNick(param); ModeHandler* mh = ServerInstance->Modes->FindMode(mode, MODETYPE_CHANNEL); if (!nick || !mh->GetPrefixRank()) return MOD_RES_PASSTHRU; if (nick->IsModeSet('Q')) { user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), param.c_str()); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } ModResult OnPreMode(User* source, User* dest, Channel* channel, const std::vector& parameters) { if (override_active || !IS_LOCAL(source)) return MOD_RES_PASSTHRU; if (safety && channel && source->IsModeSet('Q')) { if (parameters.size() < 2 || parameters[1] != "!") { source->WriteServ("NOTICE %s :*** Blocked message. You are hidden and safety is on. Place ' ! ' between channel and modes to override.", source->nick.c_str()); return MOD_RES_DENY; } std::vector params = parameters; params.erase(params.begin() + 1); override_active = true; ServerInstance->Modes->Process(params, source); override_active = false; return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } ModResult OnWhoisLine(User* user, User* dest, int& numeric, std::string& text) { if (!hidewhois || (!IS_OPER(user) && !dest->IsModeSet('Q'))) return MOD_RES_PASSTHRU; if (numeric == 318) return MOD_RES_ALLOW; return MOD_RES_DENY; } /* No privmsg response when hiding - submitted by Eric at neowin */ ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list) { if (safety && IS_LOCAL(user) && user->IsModeSet('Q')) { // Don't block messages to other opers if (target_type == TYPE_USER && IS_OPER(static_cast(dest))) return MOD_RES_PASSTHRU; if (text[0] != '!') { user->WriteServ("NOTICE %s :*** Blocked message. You are hidden and safety is on. Prefix message with ! to override.", user->nick.c_str()); return MOD_RES_DENY; } text = text.substr(1); } else if ((target_type == TYPE_USER) && (IS_LOCAL(user))) { User* target = (User*)dest; if(hidemsg && target->IsModeSet('Q') && !IS_OPER(user)) { user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), target->nick.c_str()); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } 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); } void OnSendWhoLine(User* source, const std::vector& params, User* user, std::string& line) { if (hidewho && user->IsModeSet('Q') && !IS_OPER(source)) line.clear(); } void OnNamesListItem(User* issuer, Membership* memb, std::string &prefixes, std::string &nick) { if (hidelist && memb->user->IsModeSet('Q') && !IS_OPER(issuer)) nick.clear(); } }; MODULE_INIT(ModuleInvisible)