2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
6 * This file is part of InspIRCd. InspIRCd is free software: you can
7 * redistribute it and/or modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation, version 2.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 class ManagerInternal;
31 typedef std::vector<Entry*> WatchedList;
32 typedef std::vector<LocalUser*> WatcherList;
36 struct IRCv3::Monitor::Entry
41 void SetNick(const std::string& Nick)
44 // We may show this string to other users so do not leak the casing
45 std::transform(Nick.begin(), Nick.end(), std::back_inserter(nick), ::tolower);
48 const std::string& GetNick() const { return nick; }
51 class IRCv3::Monitor::Manager
58 class ExtItem : public ExtensionItem
63 ExtItem(Module* mod, const std::string& extname, Manager& managerref)
64 : ExtensionItem(extname, ExtensionItem::EXT_USER, mod)
69 ExtData* get(Extensible* container, bool create = false)
71 ExtData* extdata = static_cast<ExtData*>(get_raw(container));
72 if ((!extdata) && (create))
74 extdata = new ExtData;
75 set_raw(container, extdata);
80 void unset(Extensible* container)
82 free(container, unset_raw(container));
85 std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE
88 const ExtData* extdata = static_cast<ExtData*>(item);
89 for (WatchedList::const_iterator i = extdata->list.begin(); i != extdata->list.end(); ++i)
91 const Entry* entry = *i;
92 ret.append(entry->GetNick()).push_back(' ');
95 ret.erase(ret.size()-1);
99 void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE;
101 void free(Extensible* container, void* item) CXX11_OVERRIDE
103 delete static_cast<ExtData*>(item);
108 Manager(Module* mod, const std::string& extname)
109 : ext(mod, extname, *this)
121 WatchResult Watch(LocalUser* user, const std::string& nick, unsigned int maxwatch)
123 if (!ServerInstance->IsNick(nick))
124 return WR_INVALIDNICK;
126 WatchedList* watched = GetWatchedPriv(user, true);
127 if (watched->size() >= maxwatch)
130 Entry* entry = AddWatcher(nick, user);
131 if (stdalgo::isin(*watched, entry))
132 return WR_ALREADYWATCHING;
134 entry->watchers.push_back(user);
135 watched->push_back(entry);
139 bool Unwatch(LocalUser* user, const std::string& nick)
141 WatchedList* list = GetWatchedPriv(user);
145 bool ret = RemoveWatcher(nick, user, *list);
146 // If no longer watching any nick unset ext
152 const WatchedList& GetWatched(LocalUser* user)
154 WatchedList* list = GetWatchedPriv(user);
157 return emptywatchedlist;
160 void UnwatchAll(LocalUser* user)
162 WatchedList* list = GetWatchedPriv(user);
166 while (!list->empty())
168 Entry* entry = list->front();
169 RemoveWatcher(entry->GetNick(), user, *list);
174 WatcherList* GetWatcherList(const std::string& nick)
176 Entry* entry = Find(nick);
178 return &entry->watchers;
182 static User* FindNick(const std::string& nick)
184 User* user = ServerInstance->FindNickOnly(nick);
185 if ((user) && (user->registered == REG_ALL))
191 typedef TR1NS::unordered_map<std::string, Entry, irc::insensitive, irc::StrHashComp> NickHash;
193 Entry* Find(const std::string& nick)
195 NickHash::iterator it = nicks.find(nick);
196 if (it != nicks.end())
201 Entry* AddWatcher(const std::string& nick, LocalUser* user)
203 std::pair<NickHash::iterator, bool> ret = nicks.insert(std::make_pair(nick, Entry()));
204 Entry& entry = ret.first->second;
210 bool RemoveWatcher(const std::string& nick, LocalUser* user, WatchedList& watchedlist)
212 NickHash::iterator it = nicks.find(nick);
213 // If nobody is watching this nick the user trying to remove it isn't watching it for sure
214 if (it == nicks.end())
217 Entry& entry = it->second;
218 // Erase from the user's list of watched nicks
219 if (!stdalgo::vector::swaperase(watchedlist, &entry))
220 return false; // User is not watching this nick
222 // Erase from the nick's list of watching users
223 stdalgo::vector::swaperase(entry.watchers, user);
225 // If nobody else is watching the nick remove map entry
226 if (entry.watchers.empty())
232 WatchedList* GetWatchedPriv(LocalUser* user, bool create = false)
234 ExtData* extdata = ext.get(user, create);
237 return &extdata->list;
242 WatchedList emptywatchedlist;
245 void IRCv3::Monitor::Manager::ExtItem::FromInternal(Extensible* container, const std::string& value)
247 irc::spacesepstream ss(value);
248 for (std::string nick; ss.GetToken(nick); )
249 manager.Watch(static_cast<LocalUser*>(container), nick, UINT_MAX);
252 #ifndef INSPIRCD_MONITOR_MANAGER_ONLY
257 RPL_MONOFFLINE = 731,
259 RPL_ENDOFMONLIST = 733,
260 ERR_MONLISTFULL = 734
263 class CommandMonitor : public SplitCommand
265 typedef Numeric::Builder<> ReplyBuilder;
266 // Additional penalty for the /MONITOR L and /MONITOR S commands that request a list from the server
267 static const unsigned int ListPenalty = 3000;
269 IRCv3::Monitor::Manager& manager;
271 void HandlePlus(LocalUser* user, const std::string& input)
273 ReplyBuilder online(user, RPL_MONONLINE);
274 ReplyBuilder offline(user, RPL_MONOFFLINE);
275 irc::commasepstream ss(input);
276 for (std::string nick; ss.GetToken(nick); )
278 IRCv3::Monitor::Manager::WatchResult result = manager.Watch(user, nick, maxmonitor);
279 if (result == IRCv3::Monitor::Manager::WR_TOOMANY)
281 // List is full, send error which includes the remaining nicks that were not processed
282 user->WriteNumeric(ERR_MONLISTFULL, maxmonitor, InspIRCd::Format("%s%s%s", nick.c_str(), (ss.StreamEnd() ? "" : ","), ss.GetRemaining().c_str()), "Monitor list is full");
285 else if (result != IRCv3::Monitor::Manager::WR_OK)
286 continue; // Already added or invalid nick
288 ReplyBuilder& out = (IRCv3::Monitor::Manager::FindNick(nick) ? online : offline);
296 void HandleMinus(LocalUser* user, const std::string& input)
298 irc::commasepstream ss(input);
299 for (std::string nick; ss.GetToken(nick); )
300 manager.Unwatch(user, nick);
304 unsigned int maxmonitor;
306 CommandMonitor(Module* mod, IRCv3::Monitor::Manager& managerref)
307 : SplitCommand(mod, "MONITOR", 1)
308 , manager(managerref)
311 allow_empty_last_param = false;
312 syntax = "C|L|S|(+|-) <nick>[,<nick>]+";
315 CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
317 char subcmd = toupper(parameters[0][0]);
320 if (parameters.size() > 1)
321 HandlePlus(user, parameters[1]);
323 else if (subcmd == '-')
325 if (parameters.size() > 1)
326 HandleMinus(user, parameters[1]);
328 else if (subcmd == 'C')
330 manager.UnwatchAll(user);
332 else if (subcmd == 'L')
334 user->CommandFloodPenalty += ListPenalty;
335 const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user);
336 ReplyBuilder out(user, RPL_MONLIST);
337 for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i)
339 IRCv3::Monitor::Entry* entry = *i;
340 out.Add(entry->GetNick());
343 user->WriteNumeric(RPL_ENDOFMONLIST, "End of MONITOR list");
345 else if (subcmd == 'S')
347 user->CommandFloodPenalty += ListPenalty;
349 ReplyBuilder online(user, RPL_MONONLINE);
350 ReplyBuilder offline(user, RPL_MONOFFLINE);
352 const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user);
353 for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i)
355 IRCv3::Monitor::Entry* entry = *i;
356 ReplyBuilder& out = (IRCv3::Monitor::Manager::FindNick(entry->GetNick()) ? online : offline);
357 out.Add(entry->GetNick());
370 class ModuleMonitor : public Module
372 IRCv3::Monitor::Manager manager;
375 void SendAlert(unsigned int numeric, const std::string& nick)
377 const IRCv3::Monitor::WatcherList* list = manager.GetWatcherList(nick);
381 for (IRCv3::Monitor::WatcherList::const_iterator i = list->begin(); i != list->end(); ++i)
383 LocalUser* curr = *i;
384 curr->WriteNumeric(numeric, nick);
390 : manager(this, "monitor")
395 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
397 ConfigTag* tag = ServerInstance->Config->ConfValue("monitor");
398 cmd.maxmonitor = tag->getUInt("maxentries", 30, 1);
401 void OnPostConnect(User* user) CXX11_OVERRIDE
403 SendAlert(RPL_MONONLINE, user->nick);
406 void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE
408 // Detect and ignore nickname case change
409 if (ServerInstance->FindNickOnly(oldnick) == user)
412 SendAlert(RPL_MONOFFLINE, oldnick);
413 SendAlert(RPL_MONONLINE, user->nick);
416 void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE
418 LocalUser* localuser = IS_LOCAL(user);
420 manager.UnwatchAll(localuser);
421 SendAlert(RPL_MONOFFLINE, user->nick);
424 void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
426 tokens["MONITOR"] = ConvToStr(cmd.maxmonitor);
429 Version GetVersion() CXX11_OVERRIDE
431 return Version("Provides MONITOR support", VF_VENDOR);
435 MODULE_INIT(ModuleMonitor)