]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_safelist.cpp
325972d767aab44a539ddd08bf9d4f3a5d32b8a2
[user/henk/code/inspircd.git] / src / modules / m_safelist.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15
16 /** Holds a users m_safelist state
17  */
18 class ListData : public classbase
19 {
20  public:
21         long list_start;
22         long list_position;
23         bool list_ended;
24         const std::string glob;
25         int minusers;
26         int maxusers;
27
28         ListData() : list_start(0), list_position(0), list_ended(false) {};
29         ListData(long pos, time_t t, const std::string &pattern, int mi, int ma) : list_start(t), list_position(pos), list_ended(false), glob(pattern), minusers(mi), maxusers(ma) {};
30 };
31
32 /* $ModDesc: A module overriding /list, and making it safe - stop those sendq problems. */
33
34 class ModuleSafeList : public Module
35 {
36         time_t ThrottleSecs;
37         size_t ServerNameSize;
38         int global_listing;
39         int LimitList;
40         SimpleExtItem<ListData> listData;
41         LocalIntExt listTime;
42  public:
43         ModuleSafeList(InspIRCd* Me) : Module(Me), listData("safelist_data", this), listTime("safelist_last", this)
44         {
45                 OnRehash(NULL);
46                 Extensible::Register(&listData);
47                 Extensible::Register(&listTime);
48                 Implementation eventlist[] = { I_OnBufferFlushed, I_OnPreCommand, I_On005Numeric, I_OnRehash };
49                 ServerInstance->Modules->Attach(eventlist, this, 4);
50         }
51
52         ~ModuleSafeList()
53         {
54         }
55
56         void OnRehash(User* user)
57         {
58                 ConfigReader MyConf(ServerInstance);
59                 ThrottleSecs = MyConf.ReadInteger("safelist", "throttle", "60", 0, true);
60                 LimitList = MyConf.ReadInteger("safelist", "maxlisters", "50", 0, true);
61                 ServerNameSize = strlen(ServerInstance->Config->ServerName) + 4;
62                 global_listing = 0;
63         }
64
65         Version GetVersion()
66         {
67                 return Version("A module overriding /list, and making it safe - stop those sendq problems.",VF_VENDOR,API_VERSION);
68         }
69
70
71         /*
72          * OnPreCommand()
73          *   Intercept the LIST command.
74          */
75         ModResult OnPreCommand(std::string &command, std::vector<std::string> &parameters, User *user, bool validated, const std::string &original_line)
76         {
77                 /* If the command doesnt appear to be valid, we dont want to mess with it. */
78                 if (!validated)
79                         return MOD_RES_PASSTHRU;
80
81                 if (command == "LIST")
82                 {
83                         return this->HandleList(parameters, user);
84                 }
85                 return MOD_RES_PASSTHRU;
86         }
87
88         /*
89          * HandleList()
90          *   Handle (override) the LIST command.
91          */
92         ModResult HandleList(const std::vector<std::string> &parameters, User* user)
93         {
94                 int pcnt = parameters.size();
95                 int minusers = 0, maxusers = 0;
96
97                 if (global_listing >= LimitList && !IS_OPER(user))
98                 {
99                         user->WriteServ("NOTICE %s :*** Server load is currently too heavy. Please try again later.", user->nick.c_str());
100                         user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str());
101                         user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str());
102                         return MOD_RES_DENY;
103                 }
104
105                 /* First, let's check if the user is currently /list'ing */
106                 ListData *ld = listData.get(user);
107
108                 if (ld)
109                 {
110                         /* user is already /list'ing, we don't want to do shit. */
111                         return MOD_RES_DENY;
112                 }
113
114                 /* Work around mIRC suckyness. YOU SUCK, KHALED! */
115                 if (pcnt == 1)
116                 {
117                         if (parameters[0][0] == '<')
118                         {
119                                 maxusers = atoi(parameters[0].c_str()+1);
120                                 ServerInstance->Logs->Log("m_safelist",DEBUG,"Max users: %d", maxusers);
121                                 pcnt = 0;
122                         }
123                         else if (parameters[0][0] == '>')
124                         {
125                                 minusers = atoi(parameters[0].c_str()+1);
126                                 ServerInstance->Logs->Log("m_safelist",DEBUG,"Min users: %d", minusers);
127                                 pcnt = 0;
128                         }
129                 }
130
131                 time_t last_list_time = listTime.get(user);
132                 if (last_list_time && ServerInstance->Time() < last_list_time + ThrottleSecs)
133                 {
134                         user->WriteServ("NOTICE %s :*** Woah there, slow down a little, you can't /LIST so often!",user->nick.c_str());
135                         user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str());
136                         user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str());
137                         return MOD_RES_DENY;
138                 }
139
140                 /*
141                  * start at channel 0! ;)
142                  */
143                 ld = new ListData(0,ServerInstance->Time(), (pcnt && (parameters[0][0] != '<' && parameters[0][0] != '>')) ? parameters[0] : "*", minusers, maxusers);
144                 listData.set(user, ld);
145                 listTime.set(user, ServerInstance->Time());
146
147                 user->WriteNumeric(321, "%s Channel :Users Name",user->nick.c_str());
148
149                 global_listing++;
150
151                 return MOD_RES_DENY;
152         }
153
154         void OnBufferFlushed(User* user)
155         {
156                 char buffer[MAXBUF];
157                 ListData* ld = listData.get(user);
158                 if (ld)
159                 {
160                         Channel* chan = NULL;
161                         unsigned long amount_sent = 0;
162                         do
163                         {
164                                 chan = ServerInstance->GetChannelIndex(ld->list_position);
165                                 bool is_special = (chan && (chan->HasUser(user) || user->HasPrivPermission("channels/auspex")));
166                                 long users = chan ? chan->GetUserCounter() : 0;
167
168                                 bool too_few = (ld->minusers && (users <= ld->minusers));
169                                 bool too_many = (ld->maxusers && (users >= ld->maxusers));
170
171                                 if (chan && (too_many || too_few))
172                                 {
173                                         ld->list_position++;
174                                         continue;
175                                 }
176
177                                 if (chan)
178                                 {
179                                         bool display = (InspIRCd::Match(chan->name, ld->glob) || (!chan->topic.empty() && InspIRCd::Match(chan->topic, ld->glob)));
180
181                                         if (!users || !display)
182                                         {
183                                                 ld->list_position++;
184                                                 continue;
185                                         }
186
187                                         /* +s, not in chan / not got channels/auspex */
188                                         if (chan->IsModeSet('s') && !is_special)
189                                         {
190                                                 ld->list_position++;
191                                                 continue;
192                                         }
193
194                                         if (chan->IsModeSet('p') && !is_special)
195                                         {
196                                                 /* Channel is +p and user is outside/not privileged */
197                                                 int counter = snprintf(buffer, MAXBUF, "322 %s * %ld :", user->nick.c_str(), users);
198                                                 amount_sent += counter + ServerNameSize;
199                                                 user->WriteServ(std::string(buffer));
200                                         }
201                                         else
202                                         {
203                                                 /* User is in the channel/privileged, channel is not +s */
204                                                 int counter = snprintf(buffer, MAXBUF, "322 %s %s %ld :[+%s] %s", user->nick.c_str(), chan->name.c_str(), users, chan->ChanModes(is_special), chan->topic.c_str());
205                                                 amount_sent += counter + ServerNameSize;
206                                                 user->WriteServ(std::string(buffer));
207                                         }
208                                 }
209                                 else
210                                 {
211                                         if (!ld->list_ended)
212                                         {
213                                                 ld->list_ended = true;
214                                                 user->WriteNumeric(323, "%s :End of channel list.",user->nick.c_str());
215                                         }
216                                 }
217
218                                 ld->list_position++;
219                         }
220                         while ((chan != NULL) && (amount_sent < (user->MyClass->GetSendqMax() / 4)));
221                         if (ld->list_ended)
222                         {
223                                 listData.unset(user);
224                                 global_listing--;
225                         }
226                 }
227         }
228
229         void On005Numeric(std::string &output)
230         {
231                 output.append(" SAFELIST");
232         }
233
234 };
235
236 MODULE_INIT(ModuleSafeList)