]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/coremods/core_whowas.cpp
Update copyright headers.
[user/henk/code/inspircd.git] / src / coremods / core_whowas.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013, 2017-2018, 2020 Sadie Powell <sadie@witchery.services>
5  *   Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
6  *   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
7  *   Copyright (C) 2010 Craig Edwards <brain@inspircd.org>
8  *   Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
9  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
10  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
11  *   Copyright (C) 2006-2007 Dennis Friis <peavey@inspircd.org>
12  *
13  * This file is part of InspIRCd.  InspIRCd is free software: you can
14  * redistribute it and/or modify it under the terms of the GNU General Public
15  * License as published by the Free Software Foundation, version 2.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25
26
27 #include "inspircd.h"
28 #include "modules/stats.h"
29
30 enum
31 {
32         // From RFC 1459.
33         RPL_WHOWASUSER = 314,
34         RPL_ENDOFWHOWAS = 369,
35
36         // InspIRCd-specific.
37         RPL_WHOWASIP = 652
38 };
39
40 namespace WhoWas
41 {
42         /** One entry for a nick. There may be multiple entries for a nick. */
43         struct Entry
44         {
45                 /** Real host */
46                 const std::string host;
47
48                 /** Displayed host */
49                 const std::string dhost;
50
51                 /** Ident */
52                 const std::string ident;
53
54                 /** Server name */
55                 const std::string server;
56
57                 /** Real name */
58                 const std::string real;
59
60                 /** Signon time */
61                 const time_t signon;
62
63                 /** Initialize this Entry with a user */
64                 Entry(User* user);
65         };
66
67         /** Everything known about one nick */
68         struct Nick : public insp::intrusive_list_node<Nick>
69         {
70                 /** A group of users related by nickname */
71                 typedef std::deque<Entry*> List;
72
73                 /** Container where each element has information about one occurrence of this nick */
74                 List entries;
75
76                 /** Time this nick was added to the database */
77                 const time_t addtime;
78
79                 /** Nickname whose information is stored in this class */
80                 const std::string nick;
81
82                 /** Constructor to initialize fields */
83                 Nick(const std::string& nickname);
84
85                 /** Destructor, deallocates all elements in the entries container */
86                 ~Nick();
87         };
88
89         class Manager
90         {
91          public:
92                 struct Stats
93                 {
94                         /** Number of currently existing WhoWas::Entry objects */
95                         size_t entrycount;
96                 };
97
98                 /** Add a user to the whowas database. Called when a user quits.
99                  * @param user The user to add to the database
100                  */
101                 void Add(User* user);
102
103                 /** Retrieves statistics about the whowas database
104                  * @return Whowas statistics as a WhoWas::Manager::Stats struct
105                  */
106                 Stats GetStats() const;
107
108                 /** Expires old entries */
109                 void Maintain();
110
111                 /** Updates the current configuration which may result in the database being pruned if the
112                  * new values are lower than the current ones.
113                  * @param NewGroupSize Maximum number of nicks allowed in the database. In case there are this many nicks
114                  * in the database and one more is added, the oldest one is removed (FIFO).
115                  * @param NewMaxGroups Maximum number of entries per nick
116                  * @param NewMaxKeep Seconds how long each nick should be kept
117                  */
118                 void UpdateConfig(unsigned int NewGroupSize, unsigned int NewMaxGroups, unsigned int NewMaxKeep);
119
120                 /** Retrieves all data known about a given nick
121                  * @param nick Nickname to find, case insensitive (IRC casemapping)
122                  * @return A pointer to a WhoWas::Nick if the nick was found, NULL otherwise
123                  */
124                 const Nick* FindNick(const std::string& nick) const;
125
126                 /** Returns true if WHOWAS is enabled according to the current configuration
127                  * @return True if WHOWAS is enabled according to the configuration, false if WHOWAS is disabled
128                  */
129                 bool IsEnabled() const;
130
131                 /** Constructor */
132                 Manager();
133
134                 /** Destructor */
135                 ~Manager();
136
137          private:
138                 /** Order in which the users were added into the map, used to remove oldest nick */
139                 typedef insp::intrusive_list_tail<Nick> FIFO;
140
141                 /** Sets of users in the whowas system */
142                 typedef TR1NS::unordered_map<std::string, WhoWas::Nick*, irc::insensitive, irc::StrHashComp> whowas_users;
143
144                 /** Primary container, links nicknames tracked by WHOWAS to a list of records */
145                 whowas_users whowas;
146
147                 /** List of nicknames in the order they were inserted into the map */
148                 FIFO whowas_fifo;
149
150                 /** Max number of WhoWas entries per user. */
151                 unsigned int GroupSize;
152
153                 /** Max number of cumulative user-entries in WhoWas.
154                  * When max reached and added to, push out oldest entry FIFO style.
155                  */
156                 unsigned int MaxGroups;
157
158                 /** Max seconds a user is kept in WhoWas before being pruned. */
159                 unsigned int MaxKeep;
160
161                 /** Shrink all data structures to honor the current settings */
162                 void Prune();
163
164                 /** Remove a nick (and all entries belonging to it) from the database
165                  * @param it Iterator to the nick to purge
166                  */
167                 void PurgeNick(whowas_users::iterator it);
168
169                 /** Remove a nick (and all entries belonging to it) from the database
170                  * @param nick Nick to purge
171                  */
172                 void PurgeNick(WhoWas::Nick* nick);
173         };
174 }
175
176 class CommandWhowas : public Command
177 {
178   public:
179         // Manager handling all whowas database related tasks
180         WhoWas::Manager manager;
181
182         CommandWhowas(Module* parent);
183         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
184 };
185
186 CommandWhowas::CommandWhowas( Module* parent)
187         : Command(parent, "WHOWAS", 1)
188 {
189         syntax = "<nick>";
190         Penalty = 2;
191 }
192
193 CmdResult CommandWhowas::Handle(User* user, const Params& parameters)
194 {
195         /* if whowas disabled in config */
196         if (!manager.IsEnabled())
197         {
198                 user->WriteNumeric(ERR_UNKNOWNCOMMAND, name, "This command has been disabled.");
199                 return CMD_FAILURE;
200         }
201
202         const WhoWas::Nick* const nick = manager.FindNick(parameters[0]);
203         if (!nick)
204         {
205                 user->WriteNumeric(ERR_WASNOSUCHNICK, parameters[0], "There was no such nickname");
206         }
207         else
208         {
209                 const WhoWas::Nick::List& list = nick->entries;
210                 for (WhoWas::Nick::List::const_iterator i = list.begin(); i != list.end(); ++i)
211                 {
212                         WhoWas::Entry* u = *i;
213
214                         user->WriteNumeric(RPL_WHOWASUSER, parameters[0], u->ident, u->dhost, '*', u->real);
215
216                         if (user->HasPrivPermission("users/auspex"))
217                                 user->WriteNumeric(RPL_WHOWASIP, parameters[0], InspIRCd::Format("was connecting from *@%s", u->host.c_str()));
218
219                         std::string signon = InspIRCd::TimeString(u->signon);
220                         bool hide_server = (!ServerInstance->Config->HideServer.empty() && !user->HasPrivPermission("servers/auspex"));
221                         user->WriteNumeric(RPL_WHOISSERVER, parameters[0], (hide_server ? ServerInstance->Config->HideServer : u->server), signon);
222                 }
223         }
224
225         user->WriteNumeric(RPL_ENDOFWHOWAS, parameters[0], "End of WHOWAS");
226         return CMD_SUCCESS;
227 }
228
229 WhoWas::Manager::Manager()
230         : GroupSize(0), MaxGroups(0), MaxKeep(0)
231 {
232 }
233
234 const WhoWas::Nick* WhoWas::Manager::FindNick(const std::string& nickname) const
235 {
236         whowas_users::const_iterator it = whowas.find(nickname);
237         if (it == whowas.end())
238                 return NULL;
239         return it->second;
240 }
241
242 WhoWas::Manager::Stats WhoWas::Manager::GetStats() const
243 {
244         size_t entrycount = 0;
245         for (whowas_users::const_iterator i = whowas.begin(); i != whowas.end(); ++i)
246         {
247                 WhoWas::Nick::List& list = i->second->entries;
248                 entrycount += list.size();
249         }
250
251         Stats stats;
252         stats.entrycount = entrycount;
253         return stats;
254 }
255
256 void WhoWas::Manager::Add(User* user)
257 {
258         if (!IsEnabled())
259                 return;
260
261         // Insert nick if it doesn't exist
262         // 'first' will point to the newly inserted element or to the existing element with an equivalent key
263         std::pair<whowas_users::iterator, bool> ret = whowas.insert(std::make_pair(user->nick, static_cast<WhoWas::Nick*>(NULL)));
264
265         if (ret.second) // If inserted
266         {
267                 // This nick is new, create a list for it and add the first record to it
268                 WhoWas::Nick* nick = new WhoWas::Nick(ret.first->first);
269                 nick->entries.push_back(new Entry(user));
270                 ret.first->second = nick;
271
272                 // Add this nick to the fifo too
273                 whowas_fifo.push_back(nick);
274
275                 if (whowas.size() > this->MaxGroups)
276                 {
277                         // Too many nicks, remove the nick which was inserted the longest time ago from both the map and the fifo
278                         PurgeNick(whowas_fifo.front());
279                 }
280         }
281         else
282         {
283                 // We've met this nick before, add a new record to the list
284                 WhoWas::Nick::List& list = ret.first->second->entries;
285                 list.push_back(new Entry(user));
286
287                 // If there are too many records for this nick, remove the oldest (front)
288                 if (list.size() > this->GroupSize)
289                 {
290                         delete list.front();
291                         list.pop_front();
292                 }
293         }
294 }
295
296 /* on rehash, refactor maps according to new conf values */
297 void WhoWas::Manager::Prune()
298 {
299         time_t min = ServerInstance->Time() - this->MaxKeep;
300
301         /* first cut the list to new size (maxgroups) and also prune entries that are timed out. */
302         while (!whowas_fifo.empty())
303         {
304                 WhoWas::Nick* nick = whowas_fifo.front();
305                 if ((whowas_fifo.size() > this->MaxGroups) || (nick->addtime < min))
306                         PurgeNick(nick);
307                 else
308                         break;
309         }
310
311         /* Then cut the whowas sets to new size (groupsize) */
312         for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); )
313         {
314                 WhoWas::Nick::List& list = i->second->entries;
315                 while (list.size() > this->GroupSize)
316                 {
317                         delete list.front();
318                         list.pop_front();
319                 }
320
321                 if (list.empty())
322                         PurgeNick(i++);
323                 else
324                         ++i;
325         }
326 }
327
328 /* call maintain once an hour to remove expired nicks */
329 void WhoWas::Manager::Maintain()
330 {
331         time_t min = ServerInstance->Time() - this->MaxKeep;
332         for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); )
333         {
334                 WhoWas::Nick::List& list = i->second->entries;
335                 while (!list.empty() && list.front()->signon < min)
336                 {
337                         delete list.front();
338                         list.pop_front();
339                 }
340
341                 if (list.empty())
342                         PurgeNick(i++);
343                 else
344                         ++i;
345         }
346 }
347
348 WhoWas::Manager::~Manager()
349 {
350         for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); ++i)
351         {
352                 WhoWas::Nick* nick = i->second;
353                 delete nick;
354         }
355 }
356
357 bool WhoWas::Manager::IsEnabled() const
358 {
359         return ((GroupSize != 0) && (MaxGroups != 0));
360 }
361
362 void WhoWas::Manager::UpdateConfig(unsigned int NewGroupSize, unsigned int NewMaxGroups, unsigned int NewMaxKeep)
363 {
364         if ((NewGroupSize == GroupSize) && (NewMaxGroups == MaxGroups) && (NewMaxKeep == MaxKeep))
365                 return;
366
367         GroupSize = NewGroupSize;
368         MaxGroups = NewMaxGroups;
369         MaxKeep = NewMaxKeep;
370         Prune();
371 }
372
373 void WhoWas::Manager::PurgeNick(whowas_users::iterator it)
374 {
375         WhoWas::Nick* nick = it->second;
376         whowas_fifo.erase(nick);
377         whowas.erase(it);
378         delete nick;
379 }
380
381 void WhoWas::Manager::PurgeNick(WhoWas::Nick* nick)
382 {
383         whowas_users::iterator it = whowas.find(nick->nick);
384         if (it == whowas.end())
385         {
386                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in whowas database, please report");
387                 return;
388         }
389         PurgeNick(it);
390 }
391
392 WhoWas::Entry::Entry(User* user)
393         : host(user->GetRealHost())
394         , dhost(user->GetDisplayedHost())
395         , ident(user->ident)
396         , server(user->server->GetName())
397         , real(user->GetRealName())
398         , signon(user->signon)
399 {
400 }
401
402 WhoWas::Nick::Nick(const std::string& nickname)
403         : addtime(ServerInstance->Time())
404         , nick(nickname)
405 {
406 }
407
408 WhoWas::Nick::~Nick()
409 {
410         stdalgo::delete_all(entries);
411 }
412
413 class ModuleWhoWas : public Module, public Stats::EventListener
414 {
415         CommandWhowas cmd;
416
417  public:
418         ModuleWhoWas()
419                 : Stats::EventListener(this)
420                 , cmd(this)
421         {
422         }
423
424         void OnGarbageCollect() CXX11_OVERRIDE
425         {
426                 // Remove all entries older than MaxKeep
427                 cmd.manager.Maintain();
428         }
429
430         void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE
431         {
432                 cmd.manager.Add(user);
433         }
434
435         ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE
436         {
437                 if (stats.GetSymbol() == 'z')
438                         stats.AddRow(249, "Whowas entries: "+ConvToStr(cmd.manager.GetStats().entrycount));
439
440                 return MOD_RES_PASSTHRU;
441         }
442
443         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
444         {
445                 ConfigTag* tag = ServerInstance->Config->ConfValue("whowas");
446                 unsigned int NewGroupSize = tag->getUInt("groupsize", 10, 0, 10000);
447                 unsigned int NewMaxGroups = tag->getUInt("maxgroups", 10240, 0, 1000000);
448                 unsigned int NewMaxKeep = tag->getDuration("maxkeep", 3600, 3600);
449
450                 cmd.manager.UpdateConfig(NewGroupSize, NewMaxGroups, NewMaxKeep);
451         }
452
453         Version GetVersion() CXX11_OVERRIDE
454         {
455                 return Version("Provides the WHOWAS command", VF_CORE | VF_VENDOR);
456         }
457 };
458
459 MODULE_INIT(ModuleWhoWas)