From a09109f61286bcd50ec5b8aaf08b98dd9a3985c3 Mon Sep 17 00:00:00 2001 From: om Date: Sun, 9 Jul 2006 11:59:07 +0000 Subject: Okay..updates to u_listmode, general cleanups and add some virtual methods which subclasses can override to alter behaviour..all looking much nicer and more flexible now. Update m_chanfilter to use u_listmode, demonstrates most or all of the new features of it and looks a hell of a lot prettier :) git-svn-id: http://svn.inspircd.org/repository/trunk/inspircd@4201 e03df62e-2008-0410-955e-edbf42e46eb7 --- include/u_listmode.h | 72 ++++++++++++++--- src/modules/extra/m_pgsql.cpp | 161 +++++++++++++++++++++++-------------- src/modules/extra/m_sqlv2.h | 27 +++++-- src/modules/m_chanfilter.cpp | 181 +++++++++++++----------------------------- 4 files changed, 238 insertions(+), 203 deletions(-) diff --git a/include/u_listmode.h b/include/u_listmode.h index f7d74a2c7..d30da1e5e 100644 --- a/include/u_listmode.h +++ b/include/u_listmode.h @@ -126,7 +126,7 @@ class ListModeBase : public ModeHandler { // Make one el = new modelist; - channel->Extend(infokey, (char*)el); + channel->Extend(infokey, el); } // Clean the mask up @@ -137,8 +137,10 @@ class ListModeBase : public ModeHandler { if(parameter == it->mask) { + /* Give a subclass a chance to error about this */ + TellAlreadyOnList(source, channel, parameter); + // it does, deny the change - parameter = ""; return MODEACTION_DENY; } } @@ -153,22 +155,44 @@ class ListModeBase : public ModeHandler maxsize = el->size(); if (maxsize < it->limit) { - // And now add the mask onto the list... - ListItem e; - e.mask = parameter; - e.nick = source->nick; - e.time = stringtime(); + /* 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; + el->push_back(e); + return MODEACTION_ALLOW; + } + else + { + /* If they deny it they have the job of giving an error message */ + return MODEACTION_DENY; + } } } } - // List is full - WriteServ(source->fd, "478 %s %s %s :Channel ban/ignore list is full", source->nick, channel->name, parameter.c_str()); + /* List is full, give subclass a chance to send a custom message */ + if(!TellListTooLong(source, channel, parameter)) + { + WriteServ(source->fd, "478 %s %s %s :Channel ban/ignore list is full", source->nick, channel->name, parameter.c_str()); + } + parameter = ""; - return MODEACTION_DENY; + return MODEACTION_DENY; } else { @@ -187,6 +211,11 @@ class ListModeBase : public ModeHandler } return MODEACTION_ALLOW; } + else + { + /* Tried to remove something that wasn't set */ + TellNotSet(source, channel, parameter); + } } parameter = ""; return MODEACTION_DENY; @@ -244,6 +273,25 @@ class ListModeBase : public ModeHandler } } } + + virtual bool ValidateParam(userrec* source, chanrec* channel, std::string ¶meter) + { + return true; + } + + virtual bool TellListTooLong(userrec* source, chanrec* channel, std::string ¶meter) + { + return false; + } + + virtual void TellAlreadyOnList(userrec* source, chanrec* channel, std::string ¶meter) + { + } + + virtual void TellNotSet(userrec* source, chanrec* channel, std::string ¶meter) + { + + } }; #endif diff --git a/src/modules/extra/m_pgsql.cpp b/src/modules/extra/m_pgsql.cpp index c44c66bc8..14e32ac36 100644 --- a/src/modules/extra/m_pgsql.cpp +++ b/src/modules/extra/m_pgsql.cpp @@ -57,14 +57,6 @@ typedef std::map ConnMap; */ enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE }; -inline std::string pop_front_r(std::deque &d) -{ - std::string r = d.front(); - d.pop_front(); - return r; - -} - /** QueryQueue, a queue of queries waiting to be executed. * This maintains two queues internally, one for 'priority' * queries and one for less important ones. Each queue has @@ -74,21 +66,38 @@ inline std::string pop_front_r(std::deque &d) * queries in the priority queue they will be executed first, * 'unimportant' queries will only be executed when the * priority queue is empty. + * + * These are lists of SQLresult so we can, from the moment the + * SQLrequest is recieved, be beginning to construct the result + * object. The copy in the deque can then be submitted in-situ + * and finally deleted from this queue. No copies of the SQLresult :) + * + * Because we work on the SQLresult in-situ, we need a way of accessing the + * result we are currently processing, QueryQueue::front(), but that call + * needs to always return the same element until that element is removed + * from the queue, this is what the 'which' variable is. New queries are + * always added to the back of one of the two queues, but if when front() + * is first called then the priority queue is empty then front() will return + * a query from the normal queue, but if a query is then added to the priority + * queue then front() must continue to return the front of the *normal* queue + * until pop() is called. */ class QueryQueue { private: - std::deque priority; /* The priority queue */ - std::deque normal; /* The 'normal' queue */ + std::deque priority; /* The priority queue */ + std::deque normal; /* The 'normal' queue */ + enum { PRI, NOR, NON } which; /* Which queue the currently active element is at the front of */ public: QueryQueue() + : which(NON) { } - void push_back(const std::string &q, bool pri = false) + void push(const Query &q, bool pri = false) { log(DEBUG, "QueryQueue::push_back(): Adding %s query to queue: %s", ((pri) ? "priority" : "non-priority"), q.c_str()); @@ -98,21 +107,47 @@ public: normal.push_back(q); } - inline std::string pop_front() + void pop() { - std::string res; - - if(priority.size()) + if((which == PRI) && priority.size()) { - return pop_front_r(priority); + priority.pop_front(); } - else if(normal.size()) + else if((which == NOR) && normal.size()) { - return pop_front_r(normal); + normal.pop_front(); } - else + + /* Silently do nothing if there was no element to pop() */ + } + + SQLresult& front() + { + switch(which) { - return ""; + case PRI: + return priority.front(); + case NOR: + return normal.front(); + default: + if(priority.size()) + { + which = PRI; + return priority.front(); + } + + if(normal.size()) + { + which = NOR; + return normal.front(); + } + + /* This will probably result in a segfault, + * but the caller should have checked totalsize() + * first so..meh - moron :p + */ + + return priority.front(); } } @@ -120,6 +155,11 @@ public: { return std::make_pair(priority.size(), normal.size()); } + + int totalsize() + { + return priority.size() + normal.size(); + } }; /** SQLConn represents one SQL session. @@ -141,14 +181,16 @@ private: bool ssl; /* If we should require SSL */ PGconn* sql; /* PgSQL database connection handle */ SQLstatus status; /* PgSQL database connection status */ + bool qinprog;/* If there is currently a query in progress */ + QueryQueue queue; /* Queue of queries waiting to be executed on this connection */ + Query query; /* The currently active query on this connection */ public: - QueryQueue queue; /* Queue of queries waiting to be executed on this connection */ /* This class should only ever be created inside this module, using this constructor, so we don't have to worry about the default ones */ SQLConn(Server* srv, const std::string &h, unsigned int p, const std::string &d, const std::string &u, const std::string &pwd, bool s) - : InspSocket::InspSocket(), Srv(srv), dbhost(h), dbport(p), dbname(d), dbuser(u), dbpass(pwd), ssl(s), sql(NULL), status(CWRITE) + : InspSocket::InspSocket(), Srv(srv), dbhost(h), dbport(p), dbname(d), dbuser(u), dbpass(pwd), ssl(s), sql(NULL), status(CWRITE), qinprog(false) { log(DEBUG, "Creating new PgSQL connection to database %s on %s:%u (%s/%s)", dbname.c_str(), dbhost.c_str(), dbport, dbuser.c_str(), dbpass.c_str()); @@ -191,7 +233,7 @@ public: ~SQLConn() { - + Close(); } bool DoResolve() @@ -280,11 +322,14 @@ public: virtual void Close() { + log(DEBUG,"SQLConn::Close"); + + if(this->fd > 01) + socket_ref[this->fd] = NULL; this->fd = -1; this->state = I_ERROR; this->OnError(I_ERR_SOCKET); this->ClosePending = true; - log(DEBUG,"SQLConn::Close"); if(sql) { @@ -303,20 +348,18 @@ public: log(DEBUG, "PGconnectPoll: PGRES_POLLING_WRITING"); WantWrite(); status = CWRITE; - DoPoll(); - break; + return DoPoll(); case PGRES_POLLING_READING: log(DEBUG, "PGconnectPoll: PGRES_POLLING_READING"); status = CREAD; break; case PGRES_POLLING_FAILED: log(DEBUG, "PGconnectPoll: PGRES_POLLING_FAILED: %s", PQerrorMessage(sql)); - Close(); return false; case PGRES_POLLING_OK: log(DEBUG, "PGconnectPoll: PGRES_POLLING_OK"); status = WWRITE; - break; + return DoConnectedPoll() default: log(DEBUG, "PGconnectPoll: wtf?"); break; @@ -325,8 +368,15 @@ public: return true; } - bool ProcessData() + bool DoConnectedPoll() { + if(!qinprog && queue.totalsize()) + { + /* There's no query currently in progress, and there's queries in the queue. */ + query = queue.pop_front(); + DoQuery(); + } + if(PQconsumeInput(sql)) { log(DEBUG, "PQconsumeInput succeeded"); @@ -353,6 +403,8 @@ public: PQclear(result); } + + qinprog = false; } return true; @@ -420,13 +472,15 @@ public: bool DoEvent() { + bool ret; + if((status == CREAD) || (status == CWRITE)) { - DoPoll(); + ret = DoPoll(); } else { - ProcessData(); + ret = DoConnectedPoll(); } switch(PQflush(sql)) @@ -443,7 +497,7 @@ public: break; } - return true; + return ret; } std::string MkInfoStr() @@ -482,39 +536,30 @@ public: return "Err...what, erm..BUG!"; } - bool Query(const std::string &query) + SQLerror Query(const Query &query, bool pri) { + queue.push_back(query, pri); + if((status == WREAD) || (status == WWRITE)) { - if(PQsendQuery(sql, query.c_str())) + if(!qinprog) { - log(DEBUG, "Dispatched query: %s", query.c_str()); - return true; - } - else - { - log(DEBUG, "Failed to dispatch query: %s", PQerrorMessage(sql)); - return false; + if(PQsendQuery(sql, query.c_str())) + { + log(DEBUG, "Dispatched query: %s", query.c_str()); + qinprog = true; + return SQLerror(); + } + else + { + log(DEBUG, "Failed to dispatch query: %s", PQerrorMessage(sql)); + return SQLerror(QSEND_FAIL, PQerrorMessage(sql)); + } } } log(DEBUG, "Can't query until connection is complete"); - return false; - } - - virtual void OnClose() - { - /* Close PgSQL connection */ - } - - virtual void OnError(InspSocketError e) - { - /* Unsure if we need this, we should be reading/writing via the PgSQL API rather than the insp one... */ - } - - virtual void OnTimeout() - { - /* Unused, I think */ + return SQLerror(BAD_CONN, "Can't query until connection is complete"); } }; @@ -584,7 +629,7 @@ public: if((iter = connections.find(req->dbid)) != connections.end()) { /* Execute query */ - iter->second->queue.push_back(req->query, req->pri); + req->error = iter->second->Query(Query(req->query, req->GetSource(), this), req->pri); return SQLSUCCESS; } diff --git a/src/modules/extra/m_sqlv2.h b/src/modules/extra/m_sqlv2.h index 521a95640..5faa76cc2 100644 --- a/src/modules/extra/m_sqlv2.h +++ b/src/modules/extra/m_sqlv2.h @@ -8,19 +8,15 @@ #include #include "modules.h" -enum SQLerrorNum { BAD_DBID }; +enum SQLerrorNum { NO_ERROR, BAD_DBID, BAD_CONN, QSEND_FAIL }; class SQLerror { SQLerrorNum id; + std::string str; public: - - SQLerror() - { - } - - SQLerror(SQLerrorNum i) - : id(i) + SQLerror(SQLerrorNum i = NO_ERROR, const std::string &s = "") + : id(i), str(s) { } @@ -29,12 +25,26 @@ public: id = i; } + void Str(const std::string &s) + { + str = s; + } + const char* Str() { + if(str.length()) + return str.c_str(); + switch(id) { + case NO_ERROR: + return "No error"; case BAD_DBID: return "Invalid database ID"; + case BAD_CONN: + return "Invalid connection"; + case QSEND_FAIL: + return "Sending query failed"; default: return "Unknown error"; } @@ -57,6 +67,7 @@ public: class SQLresult : public Request { + public: SQLresult(Module* s, Module* d) : Request(SQLRESID, s, d) diff --git a/src/modules/m_chanfilter.cpp b/src/modules/m_chanfilter.cpp index b9fe2225f..cfb1d351e 100644 --- a/src/modules/m_chanfilter.cpp +++ b/src/modules/m_chanfilter.cpp @@ -24,33 +24,60 @@ using namespace std; #include "modules.h" #include "helperfuncs.h" #include "hashcomp.h" +#include "u_listmode.h" /* $ModDesc: Provides channel-specific censor lists (like mode +G but varies from channel to channel) */ -typedef std::vector SpamList; +class ChanFilter : public ListModeBase +{ + public: + ChanFilter(Server* serv) : ListModeBase(serv, 'g', "End of channel spamfilter list", "941", "940", "chanfilter") { } + + virtual bool ValidateParam(userrec* user, chanrec* chan, std::string &word) + { + if (word.length() > 35) + { + WriteServ(user->fd, "935 %s %s %s :word is too long for censor list",user->nick, chan->name,word.c_str()); + return false; + } + + return true; + } + + virtual bool TellListTooLong(userrec* user, chanrec* chan, std::string &word) + { + WriteServ(user->fd,"939 %s %s %s :Channel spamfilter list is full",user->nick, chan->name, word.c_str()); + return true; + } + + virtual void TellAlreadyOnList(userrec* user, chanrec* chan, std::string &word) + { + WriteServ(user->fd,"937 %s %s :The word %s is already on the spamfilter list",user->nick, chan->name,word.c_str()); + } + + virtual void TellNotSet(userrec* user, chanrec* chan, std::string &word) + { + WriteServ(user->fd,"938 %s %s :No such spamfilter word is set",user->nick, chan->name); + } +}; class ModuleChanFilter : public Module { Server *Srv; - ConfigReader *Conf; - long MaxEntries; + ChanFilter* cf; public: ModuleChanFilter(Server* Me) - : Module::Module(Me) + : Module::Module(Me), Srv(Me) { - Srv = Me; - Conf = new ConfigReader; - Srv->AddExtendedListMode('g'); - MaxEntries = Conf->ReadInteger("chanfilter","maxsize",0,true); - if (MaxEntries == 0) - MaxEntries = 32; + cf = new ChanFilter(Srv); + Srv->AddMode(cf, 'g'); } void Implements(char* List) { - List[I_On005Numeric] = List[I_OnUserPart] = List[I_OnRehash] = List[I_OnUserPreMessage] = List[I_OnUserPreNotice] = List[I_OnExtendedMode] = List[I_OnSendList] = List[I_OnSyncChannel] = 1; + List[I_OnCleanup] = List[I_On005Numeric] = List[I_OnChannelDelete] = List[I_OnRehash] = List[I_OnUserPreMessage] = List[I_OnUserPreNotice] = List[I_OnSyncChannel] = 1; } virtual void On005Numeric(std::string &output) @@ -58,42 +85,30 @@ class ModuleChanFilter : public Module InsertMode(output,"g",1); } - virtual void OnUserPart(userrec* user, chanrec* channel, const std::string &partreason) + virtual void OnChannelDelete(chanrec* chan) { - // when the last user parts, delete the list - if (Srv->CountUsers(channel) == 1) - { - SpamList* spamlist = (SpamList*)channel->GetExt("spam_list"); - if (spamlist) - { - channel->Shrink("spam_list"); - DELETE(spamlist); - } - } + cf->DoChannelDelete(chan); } virtual void OnRehash(const std::string ¶meter) { - DELETE(Conf); - Conf = new ConfigReader; - // re-read our config options on a rehash - MaxEntries = Conf->ReadInteger("chanfilter","maxsize",0,true); + cf->DoRehash(); } virtual int ProcessMessages(userrec* user,chanrec* chan,std::string &text) { - // Create a copy of the string in irc::string irc::string line = text.c_str(); - SpamList* spamlist = (SpamList*)chan->GetExt("spam_list"); - if (spamlist) + modelist* list = (modelist*)chan->GetExt(cf->GetInfoKey()); + + if (list) { - for (SpamList::iterator i = spamlist->begin(); i != spamlist->end(); i++) + for (modelist::iterator i = list->begin(); i != list->end(); i++) { - if (line.find(*i) != std::string::npos) + if (line.find(i->mask.c_str()) != std::string::npos) { - WriteServ(user->fd,"936 %s %s %s :Your message contained a censored word, and was blocked",user->nick, chan->name, i->c_str()); + WriteServ(user->fd,"936 %s %s %s :Your message contained a censored word, and was blocked",user->nick, chan->name, i->mask.c_str()); return 1; } } @@ -110,112 +125,29 @@ class ModuleChanFilter : public Module else return 0; } - virtual int OnUserPreNotice(userrec* user,void* dest,int target_type, std::string &text, char status) + virtual void OnCleanup(int target_type, void* item) { - return OnUserPreMessage(user,dest,target_type,text,status); + cf->DoCleanup(target_type, item); } - virtual int OnExtendedMode(userrec* user, void* target, char modechar, int type, bool mode_on, string_list ¶ms) - { - if ((modechar == 'g') && (type == MT_CHANNEL)) - { - chanrec* chan = (chanrec*)target; - - irc::string word = params[0].c_str(); - - if (word == "") - return -1; - - if (mode_on) - { - SpamList* spamlist = (SpamList*)chan->GetExt("spam_list"); - if (!spamlist) - { - spamlist = new SpamList; - chan->Extend("spam_list",(char*)spamlist); - } - if (spamlist->size() < (unsigned)MaxEntries) - { - if (word.length() > 35) - { - WriteServ(user->fd,"935 %s %s %s :word is too long for censor list",user->nick, chan->name,word.c_str()); - return -1; - } - for (SpamList::iterator i = spamlist->begin(); i != spamlist->end(); i++) - { - if (*i == word) - { - WriteServ(user->fd,"937 %s %s :The word %s is already on the spamfilter list",user->nick, chan->name,word.c_str()); - return -1; - } - } - spamlist->push_back(word); - return 1; - } - WriteServ(user->fd,"939 %s %s :Channel spamfilter list is full",user->nick, chan->name); - return -1; - } - else - { - SpamList* spamlist = (SpamList*)chan->GetExt("spam_list"); - if (spamlist) - { - for (SpamList::iterator i = spamlist->begin(); i != spamlist->end(); i++) - { - if (*i == word) - { - spamlist->erase(i); - return 1; - } - } - } - WriteServ(user->fd,"938 %s %s :No such spamfilter word is set",user->nick, chan->name); - return -1; - } - return -1; - } - return 0; - } - - virtual void OnSendList(userrec* user, chanrec* channel, char mode) + virtual int OnUserPreNotice(userrec* user,void* dest,int target_type, std::string &text, char status) { - if (mode == 'g') - { - SpamList* spamlist = (SpamList*)channel->GetExt("spam_list"); - if (spamlist) - { - for (SpamList::iterator i = spamlist->begin(); i != spamlist->end(); i++) - { - WriteServ(user->fd,"941 %s %s %s",user->nick, channel->name,i->c_str()); - } - } - WriteServ(user->fd,"940 %s %s :End of channel spamfilter list",user->nick, channel->name); - } + return OnUserPreMessage(user,dest,target_type,text,status); } - virtual ~ModuleChanFilter() + virtual void OnSyncChannel(chanrec* chan, Module* proto, void* opaque) { - DELETE(Conf); + cf->DoSyncChannel(chan, proto, opaque); } - + virtual Version GetVersion() { - return Version(1,0,0,0,VF_STATIC|VF_VENDOR); + return Version(1,0,0,1,VF_STATIC|VF_VENDOR); } - virtual void OnSyncChannel(chanrec* chan, Module* proto, void* opaque) + virtual ~ModuleChanFilter() { - SpamList* spamlist = (SpamList*)chan->GetExt("spam_list"); - string_list commands; - if (spamlist) - { - for (SpamList::iterator i = spamlist->begin(); i != spamlist->end(); i++) - { - proto->ProtoSendMode(opaque,TYPE_CHANNEL,chan,"+g "+std::string(i->c_str())); - } - } } - }; @@ -242,4 +174,3 @@ extern "C" void * init_module( void ) { return new ModuleChanFilterFactory; } - -- cgit v1.2.3