diff options
author | peavey <peavey@e03df62e-2008-0410-955e-edbf42e46eb7> | 2006-11-26 05:49:49 +0000 |
---|---|---|
committer | peavey <peavey@e03df62e-2008-0410-955e-edbf42e46eb7> | 2006-11-26 05:49:49 +0000 |
commit | 4745f13b70c627ab3e66a8d23d78991be2b9c3a7 (patch) | |
tree | f087c3b2af8f86d21e13d52d94a4a422e2da66bb | |
parent | ed6b9c90fe69d9745e55450addaa009ecf1d94be (diff) |
Improved drop-in replacement for cmd /SILENCE. This first draft features server side blocking of private messages, channel messages, notices, invites based on the silence hostmask. Also you can make excludes to your SILENCE rules. See source for examples. Flames, comments, edits are welcomed.
git-svn-id: http://svn.inspircd.org/repository/trunk/inspircd@5800 e03df62e-2008-0410-955e-edbf42e46eb7
-rw-r--r-- | src/modules/m_silence_ext.cpp | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/src/modules/m_silence_ext.cpp b/src/modules/m_silence_ext.cpp new file mode 100644 index 000000000..50810f7d7 --- /dev/null +++ b/src/modules/m_silence_ext.cpp @@ -0,0 +1,494 @@ +/* +------------------------------------+ + * | Inspire Internet Relay Chat Daemon | + * +------------------------------------+ + * + * InspIRCd is copyright (C) 2002-2006 ChatSpike-Dev. + * E-mail: + * <brain@chatspike.net> + * <Craig@chatspike.net> + * + * Written by Craig Edwards, Craig McLure, and others. + * This program is free but copyrighted software; see + * the file COPYING for details. + * + * --------------------------------------------------- + */ + +using namespace std; + +#include <stdio.h> +#include <string> +#include <vector> +#include <stdarg.h> +#include "users.h" +#include "channels.h" +#include "modules.h" +#include "hashcomp.h" +#include "inspircd.h" +#include "wildcard.h" + +/* $ModDesc: Provides support for the /SILENCE command */ + +/* Improved drop-in replacement for the /SILENCE command + * syntax: /SILENCE [+|-]<mask> <p|c|i|n|a|x> as in <private|channel|invites|notices|all|exclude> + * + * example that blocks all except private messages + * /SILENCE +*!*@* a + * /SILENCE +*!*@* px + * + * example that blocks all invites except from channel services + * /SILENCE +*!*@* i + * /SILENCE +chanserv!services@chatters.net ix + * + * example that blocks some bad dude from private, notice and inviting you + * /SILENCE +*!kiddie@lamerz.net pin + * + * TODO: possibly have add and remove check for existing host and only modify flags according to + * what's been changed instead of having to remove first, then add if you want to change + * an entry. + */ + +// pair of hostmask and flags +typedef std::pair<std::string, int> silenceset; + +// deque list of pairs +typedef std::deque<silenceset> silencelist; + +// intmasks for flags +static int SILENCE_PRIVATE = 0x0001; /* p private messages */ +static int SILENCE_CHANNEL = 0x0002; /* c channel messages */ +static int SILENCE_INVITE = 0x0004; /* i invites */ +static int SILENCE_NOTICE = 0x0008; /* n notices */ +static int SILENCE_ALL = 0x0010; /* a all, (pcin) */ +static int SILENCE_EXCLUDE = 0x0020; /* x exclude this pattern */ + + +class cmd_silence : public command_t +{ + public: + cmd_silence (InspIRCd* Instance) : command_t(Instance,"SILENCE", 0, 0) + { + this->source = "m_silence_ext.so"; + syntax = "{[+|-]<mask> <p|c|i|n|a|x>}"; + } + + CmdResult Handle (const char** parameters, int pcnt, userrec *user) + { + if (!pcnt) + { + // no parameters, show the current silence list. + // Use Extensible::GetExt to fetch the silence list + silencelist* sl; + user->GetExt("silence_list", sl); + // if the user has a silence list associated with their user record, show it + if (sl) + { + for (silencelist::const_iterator c = sl->begin(); c != sl->end(); c++) + { + user->WriteServ("271 %s %s %s %s",user->nick, user->nick,c->first.c_str(), DecompPattern(c->second).c_str()); + } + } + user->WriteServ("272 %s :End of Silence List",user->nick); + + return CMD_SUCCESS; + } + else if (pcnt > 0) + { + // one or more parameters, add or delete entry from the list (only the first parameter is used) + std::string mask = parameters[0] + 1; + char action = *parameters[0]; + // Default is private and notice so clients do not break + int pattern = CompilePattern("pn"); + + // if pattern supplied, use it + if (pcnt > 1) { + pattern = CompilePattern(parameters[1]); + } + + if (!mask.length()) + { + // 'SILENCE +' or 'SILENCE -', assume *!*@* + mask = "*!*@*"; + } + + ModeParser::CleanMask(mask); + + if (action == '-') + { + // fetch their silence list + silencelist* sl; + user->GetExt("silence_list", sl); + // does it contain any entries and does it exist? + if (sl) + { + if (sl->size()) + { + silencelist::iterator i,safei; + for (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) + { + safei = i; + --i; + sl->erase(safei); + user->WriteServ("950 %s %s :Removed %s %s from silence list",user->nick, user->nick, mask.c_str(), DecompPattern(pattern).c_str()); + break; + } + } + } + else + { + // tidy up -- if a user's list is empty, theres no use having it + // hanging around in the user record. + DELETE(sl); + user->Shrink("silence_list"); + } + } + } + else if (action == '+') + { + // fetch the user's current silence list + silencelist* sl; + user->GetExt("silence_list", sl); + // what, they dont have one??? WE'RE ALL GONNA DIE! ...no, we just create an empty one. + if (!sl) + { + sl = new silencelist; + user->Extend("silence_list", sl); + } + for (silencelist::iterator n = sl->begin(); n != sl->end(); n++) + { + irc::string listitem = n->first.c_str(); + if (listitem == mask && n->second == pattern) + { + user->WriteServ("952 %s %s :%s %s is already on your silence list",user->nick, user->nick, mask.c_str(), DecompPattern(pattern).c_str()); + return CMD_SUCCESS; + } + } + if (((pattern & SILENCE_EXCLUDE) > 0)) + { + sl->push_front(silenceset(mask,pattern)); + } + else + { + sl->push_back(silenceset(mask,pattern)); + } + user->WriteServ("951 %s %s :Added %s %s to silence list",user->nick, user->nick, mask.c_str(), DecompPattern(pattern).c_str()); + return CMD_SUCCESS; + } + } + return CMD_SUCCESS; + } + + /* turn the nice human readable pattern into a mask */ + int CompilePattern(const char* pattern) + { + int p = 0; + for (uint n = 0; n < strlen(pattern); n++) + { + switch (pattern[n]) + { + case 'p': + p |= SILENCE_PRIVATE; + break; + case 'c': + p |= SILENCE_CHANNEL; + break; + case 'i': + p |= SILENCE_INVITE; + break; + case 'n': + p |= SILENCE_NOTICE; + break; + case 'a': + p |= SILENCE_ALL; + break; + case 'x': + p |= SILENCE_EXCLUDE; + break; + default: + break; + } + } + return p; + } + + /* turn the mask into a nice human readable format */ + std::string DecompPattern (const int pattern) + { + std::string out = ""; + if ((pattern & SILENCE_PRIVATE) > 0) + out += ",private"; + if ((pattern & SILENCE_CHANNEL) > 0) + out += ",channel"; + if ((pattern & SILENCE_INVITE) > 0) + out += ",invites"; + if ((pattern & SILENCE_NOTICE) > 0) + out += ",notices"; + if ((pattern & SILENCE_ALL) > 0) + out = ",all"; + if ((pattern & SILENCE_EXCLUDE) > 0) + out += ",exclude"; + return "<" + out.substr(1) + ">"; + } + +}; + +class ModuleSilence : public Module +{ + + cmd_silence* mycommand; + public: + + ModuleSilence(InspIRCd* Me) + : Module::Module(Me) + { + + mycommand = new cmd_silence(ServerInstance); + ServerInstance->AddCommand(mycommand); + } + + void Implements(char* List) + { + List[I_OnUserQuit] = List[I_On005Numeric] = List[I_OnUserPreNotice] = List[I_OnUserPreMessage] = 1; + List[I_OnUserPreInvite] = 1; + List[I_OnPreCommand] = 1; + } + + virtual void OnUserQuit(userrec* user, const std::string &reason) + { + // when the user quits tidy up any silence list they might have just to keep things tidy + // and to prevent a HONKING BIG MEMORY LEAK! + silencelist* sl; + user->GetExt("silence_list", sl); + if (sl) + { + DELETE(sl); + user->Shrink("silence_list"); + } + } + + virtual void On005Numeric(std::string &output) + { + // we don't really have a limit... + output = output + " SILENCE=999"; + } + + virtual int OnUserPreMessage(userrec* user,void* dest,int target_type, std::string &text, char status) + { + if (target_type == TYPE_USER) + { + return MatchPattern((userrec*)dest, user, SILENCE_PRIVATE); + } + return 0; + } + + virtual int OnUserPreNotice(userrec* user,void* dest,int target_type, std::string &text, char status) + { + return MatchPattern((userrec*)dest, user, SILENCE_NOTICE); + } + + virtual int OnUserPreInvite(userrec* source,userrec* dest,chanrec* channel) + { + return MatchPattern(dest, source, SILENCE_INVITE); + } + + int MatchPattern(userrec* dest, userrec* source, int pattern) + { + silencelist* sl; + dest->GetExt("silence_list", sl); + if (sl) + { + for (silencelist::const_iterator c = sl->begin(); c != sl->end(); c++) + { + if ((match(source->GetFullHost(), c->first.c_str())) && ( ((c->second & pattern) > 0)) || ((c->second & SILENCE_ALL) > 0)) + { + if (((c->second & SILENCE_EXCLUDE) > 0)) + { + return 0; + } + else { + return 1; + } + } + } + } + return 0; + } + + virtual int OnPreCommand(const std::string &command, const char** parameters, int pcnt, userrec *user, bool validated, const std::string &original_line) + { + /* Implement the part of cmd_privmsg.cpp that handles *channel* messages, if cmd_privmsg.cpp + * is changed this probably needs updating too. Also implement the actual write to the users + * on the channel. This code is from channels.cpp, and should also be changed if channels.cpp + * updates it's corresponding code + */ + if ((validated) && (command == "PRIVMSG")) + { + char status = 0; + if ((*parameters[0] == '@') || (*parameters[0] == '%') || (*parameters[0] == '+')) + { + status = *parameters[0]; + parameters[0]++; + } + if (parameters[0][0] == '#') + { + chanrec *chan; + user->idle_lastmsg = ServerInstance->Time(); + chan = ServerInstance->FindChan(parameters[0]); + if (chan) + { + if (IS_LOCAL(user)) + { + if ((chan->modes[CM_NOEXTERNAL]) && (!chan->HasUser(user))) + { + user->WriteServ("404 %s %s :Cannot send to channel (no external messages)", user->nick, chan->name); + return CMD_FAILURE; + } + if ((chan->modes[CM_MODERATED]) && (chan->GetStatus(user) < STATUS_VOICE)) + { + user->WriteServ("404 %s %s :Cannot send to channel (+m)", user->nick, chan->name); + return CMD_FAILURE; + } + } + int MOD_RESULT = 0; + + std::string temp = parameters[1]; + FOREACH_RESULT(I_OnUserPreMessage,OnUserPreMessage(user,chan,TYPE_CHANNEL,temp,status)); + if (MOD_RESULT) { + return CMD_FAILURE; + } + parameters[1] = temp.c_str(); + + if (temp == "") + { + user->WriteServ("412 %s No text to send", user->nick); + return CMD_FAILURE; + } + + /* This next call into channel.cpp is the one that gets replaced by our modified method + * chan->WriteAllExceptSender(user, false, status, "PRIVMSG %s :%s", chan->name, parameters[1]); + */ + WriteAllExceptSenderAndSilenced(chan, user, false, status, "PRIVMSG %s :%s", chan->name, parameters[1]); + + FOREACH_MOD(I_OnUserMessage,OnUserMessage(user,chan,TYPE_CHANNEL,parameters[1],status)); + return 1; + } + else + { + /* no such nick/channel */ + user->WriteServ("401 %s %s :No such nick/channel",user->nick, parameters[0]); + return 1; + } + return 1; + } + else + { + command_t* privmsg_command = ServerInstance->Parser->GetHandler("PRIVMSG"); + if (privmsg_command) + { + privmsg_command->Handle(parameters, pcnt, user); + return 1; + } + else + { + ServerInstance->Log(DEBUG, "Could not find PRIVMSG Command!"); + } + } + } + return 0; + } + + /* Taken from channels.cpp and slightly modified, see OnPreCommand above*/ + void WriteAllExceptSenderAndSilenced(chanrec* chan, userrec* user, bool serversource, char status, char* text, ...) + { + char textbuffer[MAXBUF]; + va_list argsPtr; + + if (!text) + return; + + va_start(argsPtr, text); + vsnprintf(textbuffer, MAXBUF, text, argsPtr); + va_end(argsPtr); + + this->WriteAllExceptSenderAndSilenced(chan, user, serversource, status, std::string(textbuffer)); + } + + /* Taken from channels.cpp and slightly modified, see OnPreCommand above*/ + void WriteAllExceptSenderAndSilenced(chanrec* chan, userrec* user, bool serversource, char status, const std::string& text) + { + CUList *ulist; + + switch (status) + { + case '@': + ulist = chan->GetOppedUsers(); + break; + case '%': + ulist = chan->GetHalfoppedUsers(); + break; + case '+': + ulist = chan->GetVoicedUsers(); + break; + default: + ulist = chan->GetUsers(); + break; + } + + for (CUList::iterator i = ulist->begin(); i != ulist->end(); i++) + { + if ((IS_LOCAL(i->second)) && (user != i->second)) + { + if (serversource) + { + i->second->WriteServ(text); + } + else + { + if (MatchPattern(i->second, user, SILENCE_CHANNEL) == 0) + { + i->second->WriteFrom(user,text); + } + } + } + } + } + + virtual ~ModuleSilence() + { + } + + virtual Version GetVersion() + { + return Version(1,1,0,1,VF_VENDOR,API_VERSION); + } +}; + + +class ModuleSilenceFactory : public ModuleFactory +{ + public: + ModuleSilenceFactory() + { + } + + ~ModuleSilenceFactory() + { + } + + virtual Module * CreateModule(InspIRCd* Me) + { + return new ModuleSilence(Me); + } + +}; + + +extern "C" void * init_module( void ) +{ + return new ModuleSilenceFactory; +} + |