]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_chanprotect.cpp
fff16217852c5a0ae1b5913dda8d231cd77c82ec
[user/henk/code/inspircd.git] / src / modules / m_chanprotect.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd is copyright (C) 2002-2006 ChatSpike-Dev.
6  *                     E-mail:
7  *              <brain@chatspike.net>
8  *                <Craig@chatspike.net>
9  *     
10  * Written by Craig Edwards, Craig McLure, and others.
11  * This program is free but copyrighted software; see
12  *          the file COPYING for details.
13  *
14  * ---------------------------------------------------
15  */
16
17 #include "users.h"
18 #include "channels.h"
19 #include "modules.h"
20 #include "inspircd.h"
21
22 /* $ModDesc: Provides channel modes +a and +q */
23
24 #define PROTECT_VALUE 40000
25 #define FOUNDER_VALUE 50000
26
27 const char* fakevalue = "on";
28
29 /* When this is set to true, no restrictions apply to setting or
30  * removal of +qa. This is used while unloading so that the server
31  * can freely clear all of its users of the modes.
32  */
33 bool unload_kludge = false;
34
35 /** Handles basic operation of +qa channel modes
36  */
37 class FounderProtectBase
38 {
39  private:
40         InspIRCd* MyInstance;
41         std::string extend;
42         std::string type;
43         int list;
44         int end;
45         char* dummyptr;
46  public:
47         FounderProtectBase(InspIRCd* Instance, const std::string &ext, const std::string &mtype, int l, int e) : MyInstance(Instance), extend(ext), type(mtype), list(l), end(e)
48         {
49         }
50
51         ModePair ModeSet(userrec* source, userrec* dest, chanrec* channel, const std::string &parameter)
52         {
53                 userrec* x = MyInstance->FindNick(parameter);
54                 if (x)
55                 {
56                         if (!channel->HasUser(x))
57                         {
58                                 return std::make_pair(false, parameter);
59                         }
60                         else
61                         {
62                                 std::string item = extend+std::string(channel->name);
63                                 if (x->GetExt(item,dummyptr))
64                                 {
65                                         return std::make_pair(true, x->nick);
66                                 }
67                                 else
68                                 {
69                                         return std::make_pair(false, parameter);
70                                 }
71                         }
72                 }
73                 return std::make_pair(false, parameter);
74         }
75
76         void RemoveMode(chanrec* channel, char mc)
77         {
78                 unload_kludge = true;
79                 CUList* cl = channel->GetUsers();
80                 std::string item = extend + std::string(channel->name);
81                 const char* mode_junk[MAXMODES+1];
82                 userrec* n = new userrec(MyInstance);
83                 n->SetFd(FD_MAGIC_NUMBER);
84                 mode_junk[0] = channel->name;
85                 irc::modestacker modestack(false);
86                 std::deque<std::string> stackresult;                            
87                 for (CUList::iterator i = cl->begin(); i != cl->end(); i++)
88                 {
89                         if (i->second->GetExt(item, dummyptr))
90                         {
91                                 modestack.Push(mc, i->second->nick);
92                         }
93                 }
94
95                 while (modestack.GetStackedLine(stackresult))
96                 {
97                         for (size_t j = 0; j < stackresult.size(); j++)
98                         {
99                                 mode_junk[j+1] = stackresult[j].c_str();
100                         }
101                         MyInstance->SendMode(mode_junk, stackresult.size() + 1, n);
102                 }
103                 
104                 delete n;
105                 unload_kludge = false;
106         }
107
108         void DisplayList(userrec* user, chanrec* channel)
109         {
110                 CUList* cl = channel->GetUsers();
111                 std::string item = extend+std::string(channel->name);
112                 for (CUList::iterator i = cl->begin(); i != cl->end(); i++)
113                 {
114                         if (i->second->GetExt(item, dummyptr))
115                         {
116                                 user->WriteServ("%d %s %s %s", list, user->nick, channel->name,i->second->nick);
117                         }
118                 }
119                 user->WriteServ("%d %s %s :End of channel %s list", end, user->nick, channel->name, type.c_str());
120         }
121
122         userrec* FindAndVerify(std::string &parameter, chanrec* channel)
123         {
124                 userrec* theuser = MyInstance->FindNick(parameter);
125                 if ((!theuser) || (!channel->HasUser(theuser)))
126                 {
127                         parameter = "";
128                         return NULL;
129                 }
130                 return theuser;
131         }
132
133         ModeAction HandleChange(userrec* source, userrec* theuser, bool adding, chanrec* channel, std::string &parameter)
134         {
135                 std::string item = extend+std::string(channel->name);
136
137                 if (adding)
138                 {
139                         if (!theuser->GetExt(item, dummyptr))
140                         {
141                                 theuser->Extend(item, fakevalue);
142                                 parameter = theuser->nick;
143                                 return MODEACTION_ALLOW;
144                         }
145                 }
146                 else
147                 {
148                         if (theuser->GetExt(item, dummyptr))
149                         {
150                                 theuser->Shrink(item);
151                                 parameter = theuser->nick;
152                                 return MODEACTION_ALLOW;
153                         }
154                 }
155                 return MODEACTION_DENY;
156         }
157 };
158
159 /** Abstraction of FounderProtectBase for channel mode +q
160  */
161 class ChanFounder : public ModeHandler, public FounderProtectBase
162 {
163         char* dummyptr;
164  public:
165         ChanFounder(InspIRCd* Instance, bool using_prefixes)
166                 : ModeHandler(Instance, 'q', 1, 1, true, MODETYPE_CHANNEL, false, using_prefixes ? '~' : 0),
167                   FounderProtectBase(Instance, "cm_founder_", "founder", 386, 387) { }
168
169         unsigned int GetPrefixRank()
170         {
171                 return FOUNDER_VALUE;
172         }
173
174         ModePair ModeSet(userrec* source, userrec* dest, chanrec* channel, const std::string &parameter)
175         {
176                 return FounderProtectBase::ModeSet(source, dest, channel, parameter);
177         }
178
179         void RemoveMode(chanrec* channel)
180         {
181                 FounderProtectBase::RemoveMode(channel, this->GetModeChar());
182         }
183
184         void RemoveMode(userrec* user)
185         {
186         }
187
188         ModeAction OnModeChange(userrec* source, userrec* dest, chanrec* channel, std::string &parameter, bool adding)
189         {
190                 userrec* theuser = FounderProtectBase::FindAndVerify(parameter, channel);
191
192                 if (!theuser)
193                 {
194                         return MODEACTION_DENY;
195                 }
196
197                  // source is a server, or ulined, we'll let them +-q the user.
198                 if ((unload_kludge) || (ServerInstance->ULine(source->nick)) || (ServerInstance->ULine(source->server)) || (!*source->server) || (!IS_LOCAL(source)))
199                 {
200                         return FounderProtectBase::HandleChange(source, theuser, adding, channel, parameter);
201                 }
202                 else
203                 {
204                         // whoops, someones being naughty!
205                         source->WriteServ("468 %s %s :Only servers may set channel mode +q",source->nick, channel->name);
206                         parameter = "";
207                         return MODEACTION_DENY;
208                 }
209         }
210
211         void DisplayList(userrec* user, chanrec* channel)
212         {
213                 FounderProtectBase::DisplayList(user,channel);
214         }
215 };
216
217 /** Abstraction of FounderProtectBase for channel mode +a
218  */
219 class ChanProtect : public ModeHandler, public FounderProtectBase
220 {
221         char* dummyptr;
222  public:
223         ChanProtect(InspIRCd* Instance, bool using_prefixes)
224                 : ModeHandler(Instance, 'a', 1, 1, true, MODETYPE_CHANNEL, false, using_prefixes ? '&' : 0),
225                   FounderProtectBase(Instance,"cm_protect_","protected user", 388, 389) { }
226
227         unsigned int GetPrefixRank()
228         {
229                 return PROTECT_VALUE;
230         }
231
232         ModePair ModeSet(userrec* source, userrec* dest, chanrec* channel, const std::string &parameter)
233         {
234                 return FounderProtectBase::ModeSet(source, dest, channel, parameter);
235         }
236
237         void RemoveMode(chanrec* channel)
238         {
239                 FounderProtectBase::RemoveMode(channel, this->GetModeChar());
240         }
241
242         void RemoveMode(userrec* user)
243         {
244         }
245
246         ModeAction OnModeChange(userrec* source, userrec* dest, chanrec* channel, std::string &parameter, bool adding)
247         {
248                 userrec* theuser = FounderProtectBase::FindAndVerify(parameter, channel);
249
250                 if (!theuser)
251                         return MODEACTION_DENY;
252
253                 std::string founder = "cm_founder_"+std::string(channel->name);
254
255                 // source has +q, is a server, or ulined, we'll let them +-a the user.
256                 if ((unload_kludge) || (ServerInstance->ULine(source->nick)) || (ServerInstance->ULine(source->server)) || (!*source->server) || (source->GetExt(founder,dummyptr)) || (!IS_LOCAL(source)))
257                 {
258                         return FounderProtectBase::HandleChange(source, theuser, adding, channel, parameter);
259                 }
260                 else
261                 {
262                         // bzzzt, wrong answer!
263                         source->WriteServ("482 %s %s :You are not a channel founder",source->nick, channel->name);
264                         return MODEACTION_DENY;
265                 }
266         }
267
268         virtual void DisplayList(userrec* user, chanrec* channel)
269         {
270                 FounderProtectBase::DisplayList(user, channel);
271         }
272
273 };
274
275 class ModuleChanProtect : public Module
276 {
277         
278         bool FirstInGetsFounder;
279         bool QAPrefixes;
280         ChanProtect* cp;
281         ChanFounder* cf;
282         char* dummyptr;
283         
284  public:
285  
286         ModuleChanProtect(InspIRCd* Me) : Module::Module(Me)
287         {       
288                 /* Load config stuff */
289                 OnRehash("");
290
291                 /* Initialise module variables */
292
293                 cp = new ChanProtect(ServerInstance,QAPrefixes);
294                 cf = new ChanFounder(ServerInstance,QAPrefixes);
295
296                 ServerInstance->AddMode(cp, 'a');
297                 ServerInstance->AddMode(cf, 'q');
298         }
299
300         void Implements(char* List)
301         {
302                 List[I_OnUserKick] = List[I_OnUserPart] = List[I_OnRehash] = List[I_OnUserJoin] = List[I_OnAccessCheck] = List[I_OnSyncChannel] = 1;
303         }
304
305         virtual void OnUserKick(userrec* source, userrec* user, chanrec* chan, const std::string &reason)
306         {
307                 // FIX: when someone gets kicked from a channel we must remove their Extensibles!
308                 user->Shrink("cm_founder_"+std::string(chan->name));
309                 user->Shrink("cm_protect_"+std::string(chan->name));
310         }
311
312         virtual void OnUserPart(userrec* user, chanrec* channel, const std::string &partreason)
313         {
314                 // FIX: when someone parts a channel we must remove their Extensibles!
315                 user->Shrink("cm_founder_"+std::string(channel->name));
316                 user->Shrink("cm_protect_"+std::string(channel->name));
317         }
318
319         virtual void OnRehash(const std::string &parameter)
320         {
321                 /* Create a configreader class and read our flag,
322                  * in old versions this was heap-allocated and the
323                  * object was kept between rehashes...now we just
324                  * stack-allocate it locally.
325                  */
326                 ConfigReader Conf(ServerInstance);
327                 
328                 FirstInGetsFounder = Conf.ReadFlag("options","noservices",0);
329                 QAPrefixes = Conf.ReadFlag("options","qaprefixes",0);
330         }
331         
332         virtual void OnUserJoin(userrec* user, chanrec* channel)
333         {
334                 // if the user is the first user into the channel, mark them as the founder, but only if
335                 // the config option for it is set
336                 if (FirstInGetsFounder)
337                 {
338                         if (channel->GetUserCounter() == 1)
339                         {
340                                 // we're using Extensible::Extend to add data into user objects.
341                                 // this way is best as it adds data thats accessible to other modules
342                                 // (so long as you document your code properly) without breaking anything
343                                 // because its encapsulated neatly in a map.
344
345                                 // Change requested by katsklaw... when the first in is set to get founder,
346                                 // to make it clearer that +q has been given, send that one user the +q notice
347                                 // so that their client's syncronization and their sanity are left intact.
348                                 user->WriteServ("MODE %s +q %s",channel->name,user->nick);
349                                 if (user->Extend("cm_founder_"+std::string(channel->name),fakevalue))
350                                 {
351                                         ServerInstance->Log(DEBUG,"Marked user "+std::string(user->nick)+" as founder for "+std::string(channel->name));
352                                 }
353                         }
354                 }
355         }
356         
357         virtual int OnAccessCheck(userrec* source,userrec* dest,chanrec* channel,int access_type)
358         {
359                 // here we perform access checks, this is the important bit that actually stops kicking/deopping
360                 // etc of protected users. There are many types of access check, we're going to handle
361                 // a relatively small number of them relevent to our module using a switch statement.
362         
363                 ServerInstance->Log(DEBUG,"chanprotect OnAccessCheck %d",access_type);
364                 // don't allow action if:
365                 // (A) Theyre founder (no matter what)
366                 // (B) Theyre protected, and you're not
367                 // always allow the action if:
368                 // (A) The source is ulined
369                 
370                 
371                 // firstly, if a ulined nick, or a server, is setting the mode, then allow them to set the mode
372                 // without any access checks, we're not worthy :p
373                 if ((ServerInstance->ULine(source->nick)) || (ServerInstance->ULine(source->server)) || (!*source->server))
374                 {
375                         ServerInstance->Log(DEBUG,"chanprotect OnAccessCheck returns ALLOW");
376                         return ACR_ALLOW;
377                 }
378
379                 std::string founder = "cm_founder_"+std::string(channel->name);
380                 std::string protect = "cm_protect_"+std::string(channel->name);
381
382                 switch (access_type)
383                 {
384                         // a user has been deopped. Do we let them? hmmm...
385                         case AC_DEOP:
386                                 ServerInstance->Log(DEBUG,"OnAccessCheck AC_DEOP");
387                                 if (dest->GetExt(founder,dummyptr))
388                                 {
389                                         ServerInstance->Log(DEBUG,"Has %s",founder.c_str());
390                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't deop "+std::string(dest->nick)+" as they're a channel founder");
391                                         return ACR_DENY;
392                                 }
393                                 else
394                                 {
395                                         ServerInstance->Log(DEBUG,"Doesnt have %s",founder.c_str());
396                                 }
397                                 if ((dest->GetExt(protect,dummyptr)) && (!source->GetExt(protect,dummyptr)))
398                                 {
399                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't deop "+std::string(dest->nick)+" as they're protected (+a)");
400                                         return ACR_DENY;
401                                 }
402                         break;
403
404                         // a user is being kicked. do we chop off the end of the army boot?
405                         case AC_KICK:
406                                 ServerInstance->Log(DEBUG,"OnAccessCheck AC_KICK");
407                                 if (dest->GetExt(founder,dummyptr))
408                                 {
409                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't kick "+std::string(dest->nick)+" as they're a channel founder");
410                                         return ACR_DENY;
411                                 }
412                                 if ((dest->GetExt(protect,dummyptr)) && (!source->GetExt(protect,dummyptr)))
413                                 {
414                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't kick "+std::string(dest->nick)+" as they're protected (+a)");
415                                         return ACR_DENY;
416                                 }
417                         break;
418
419                         // a user is being dehalfopped. Yes, we do disallow -h of a +ha user
420                         case AC_DEHALFOP:
421                                 if (dest->GetExt(founder,dummyptr))
422                                 {
423                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't de-halfop "+std::string(dest->nick)+" as they're a channel founder");
424                                         return ACR_DENY;
425                                 }
426                                 if ((dest->GetExt(protect,dummyptr)) && (!source->GetExt(protect,dummyptr)))
427                                 {
428                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't de-halfop "+std::string(dest->nick)+" as they're protected (+a)");
429                                         return ACR_DENY;
430                                 }
431                         break;
432
433                         // same with devoice.
434                         case AC_DEVOICE:
435                                 if (dest->GetExt(founder,dummyptr))
436                                 {
437                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't devoice "+std::string(dest->nick)+" as they're a channel founder");
438                                         return ACR_DENY;
439                                 }
440                                 if ((dest->GetExt(protect,dummyptr)) && (!source->GetExt(protect,dummyptr)))
441                                 {
442                                         source->WriteServ("484 "+std::string(source->nick)+" "+std::string(channel->name)+" :Can't devoice "+std::string(dest->nick)+" as they're protected (+a)");
443                                         return ACR_DENY;
444                                 }
445                         break;
446                 }
447                 
448                 // we dont know what this access check is, or dont care. just carry on, nothing to see here.
449                 ServerInstance->Log(DEBUG,"chanprotect OnAccessCheck returns DEFAULT");
450                 return ACR_DEFAULT;
451         }
452         
453         virtual ~ModuleChanProtect()
454         {
455                 ServerInstance->Modes->DelMode(cp);
456                 ServerInstance->Modes->DelMode(cf);
457                 DELETE(cp);
458                 DELETE(cf);
459         }
460         
461         virtual Version GetVersion()
462         {
463                 return Version(1, 1, 0, 0, VF_COMMON | VF_VENDOR, API_VERSION);
464         }
465         
466         virtual void OnSyncChannel(chanrec* chan, Module* proto, void* opaque)
467         {
468                 /* NOTE: If +qa prefix is on, this is propogated by the channel join,
469                  * so we dont need to propogate it manually
470                  */
471                 if (!QAPrefixes)
472                 {
473                         // this is called when the server is linking into a net and wants to sync channel data.
474                         // we should send our mode changes for the channel here to ensure that other servers
475                         // know whos +q/+a on the channel.
476                         CUList* cl = chan->GetUsers();
477                         string_list commands;
478                         std::string founder = "cm_founder_"+std::string(chan->name);
479                         std::string protect = "cm_protect_"+std::string(chan->name);
480                         irc::modestacker modestack(true);
481                         std::deque<std::string> stackresult;
482                         for (CUList::iterator i = cl->begin(); i != cl->end(); i++)
483                         {
484                                 if (i->second->GetExt(founder,dummyptr))
485                                 {
486                                         modestack.Push('q',i->second->nick);
487                                 }
488                                 if (i->second->GetExt(protect,dummyptr))
489                                 {
490                                         modestack.Push('a',i->second->nick);
491                                 }
492                         }
493                         while (modestack.GetStackedLine(stackresult))
494                         {
495                                 irc::stringjoiner mode_join(" ", stackresult, 0, stackresult.size() - 1);
496                                 std::string line = mode_join.GetJoined();
497                                 proto->ProtoSendMode(opaque,TYPE_CHANNEL,chan, line);
498                         }
499                 }
500         }
501
502 };
503
504
505 class ModuleChanProtectFactory : public ModuleFactory
506 {
507  public:
508         ModuleChanProtectFactory()
509         {
510         }
511         
512         ~ModuleChanProtectFactory()
513         {
514         }
515         
516         virtual Module * CreateModule(InspIRCd* Me)
517         {
518                 return new ModuleChanProtect(Me);
519         }
520         
521 };
522
523
524 extern "C" void * init_module( void )
525 {
526         return new ModuleChanProtectFactory;
527 }