]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_watch.cpp
Merge branch 'insp20' into master.
[user/henk/code/inspircd.git] / src / modules / m_watch.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
5  *
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.
9  *
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
13  * details.
14  *
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/>.
17  */
18
19
20 #include "inspircd.h"
21 #include "modules/away.h"
22
23 #define INSPIRCD_MONITOR_MANAGER_ONLY
24 #include "m_monitor.cpp"
25
26 enum
27 {
28         RPL_GONEAWAY = 598,
29         RPL_NOTAWAY = 599,
30         RPL_LOGON = 600,
31         RPL_LOGOFF = 601,
32         RPL_WATCHOFF = 602,
33         RPL_WATCHSTAT = 603,
34         RPL_NOWON = 604,
35         RPL_NOWOFF = 605,
36         RPL_WATCHLIST = 606,
37         RPL_ENDOFWATCHLIST = 607,
38         // RPL_CLEARWATCH = 608, // unused
39         RPL_NOWISAWAY = 609,
40         ERR_TOOMANYWATCH = 512,
41         ERR_INVALIDWATCHNICK = 942
42 };
43
44 class CommandWatch : public SplitCommand
45 {
46         // Additional penalty for /WATCH commands that request a list from the server
47         static const unsigned int ListPenalty = 4000;
48
49         IRCv3::Monitor::Manager& manager;
50
51         static void SendOnlineOffline(LocalUser* user, const std::string& nick, bool show_offline = true)
52         {
53                 User* target = IRCv3::Monitor::Manager::FindNick(nick);
54                 if (target)
55                 {
56                         // The away state should only be sent if the client requests away notifications for a nick but 2.0 always sends them so we do that too
57                         if (target->IsAway())
58                                 user->WriteNumeric(RPL_NOWISAWAY, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->awaytime, "is away");
59                         else
60                                 user->WriteNumeric(RPL_NOWON, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->age, "is online");
61                 }
62                 else if (show_offline)
63                         user->WriteNumeric(RPL_NOWOFF, nick, "*", "*", "0", "is offline");
64         }
65
66         void HandlePlus(LocalUser* user, const std::string& nick)
67         {
68                 IRCv3::Monitor::Manager::WatchResult result = manager.Watch(user, nick, maxwatch);
69                 if (result == IRCv3::Monitor::Manager::WR_TOOMANY)
70                 {
71                         // List is full, send error numeric
72                         user->WriteNumeric(ERR_TOOMANYWATCH, nick, "Too many WATCH entries");
73                         return;
74                 }
75                 else if (result == IRCv3::Monitor::Manager::WR_INVALIDNICK)
76                 {
77                         user->WriteNumeric(ERR_INVALIDWATCHNICK, nick, "Invalid nickname");
78                         return;
79                 }
80                 else if (result != IRCv3::Monitor::Manager::WR_OK)
81                         return;
82
83                 SendOnlineOffline(user, nick);
84         }
85
86         void HandleMinus(LocalUser* user, const std::string& nick)
87         {
88                 if (!manager.Unwatch(user, nick))
89                         return;
90
91                 User* target = IRCv3::Monitor::Manager::FindNick(nick);
92                 if (target)
93                         user->WriteNumeric(RPL_WATCHOFF, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->age, "stopped watching");
94                 else
95                         user->WriteNumeric(RPL_WATCHOFF, nick, "*", "*", "0", "stopped watching");
96         }
97
98         void HandleList(LocalUser* user, bool show_offline)
99         {
100                 user->CommandFloodPenalty += ListPenalty;
101                 const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user);
102                 for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i)
103                 {
104                         const IRCv3::Monitor::Entry* entry = *i;
105                         SendOnlineOffline(user, entry->GetNick(), show_offline);
106                 }
107                 user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH list");
108         }
109
110         void HandleStats(LocalUser* user)
111         {
112                 user->CommandFloodPenalty += ListPenalty;
113
114                 // Do not show how many clients are watching this nick, it's pointless
115                 const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user);
116                 user->WriteNumeric(RPL_WATCHSTAT, InspIRCd::Format("You have %lu and are on 0 WATCH entries", (unsigned long)list.size()));
117
118                 Numeric::Builder<' '> out(user, RPL_WATCHLIST);
119                 for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i)
120                 {
121                         const IRCv3::Monitor::Entry* entry = *i;
122                         out.Add(entry->GetNick());
123                 }
124                 out.Flush();
125                 user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH S");
126         }
127
128  public:
129         unsigned int maxwatch;
130
131         CommandWatch(Module* mod, IRCv3::Monitor::Manager& managerref)
132                 : SplitCommand(mod, "WATCH")
133                 , manager(managerref)
134         {
135                 allow_empty_last_param = false;
136                 syntax = "[<C|L|S|l|+<nick1>|-<nick>>]";
137         }
138
139         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
140         {
141                 if (parameters.empty())
142                 {
143                         HandleList(user, false);
144                         return CMD_SUCCESS;
145                 }
146
147                 bool watch_l_done = false;
148                 bool watch_s_done = false;
149
150                 for (std::vector<std::string>::const_iterator i = parameters.begin(); i != parameters.end(); ++i)
151                 {
152                         const std::string& token = *i;
153                         char subcmd = toupper(token[0]);
154                         if (subcmd == '+')
155                         {
156                                 HandlePlus(user, token.substr(1));
157                         }
158                         else if (subcmd == '-')
159                         {
160                                 HandleMinus(user, token.substr(1));
161                         }
162                         else if (subcmd == 'C')
163                         {
164                                 manager.UnwatchAll(user);
165                         }
166                         else if ((subcmd == 'L') && (!watch_l_done))
167                         {
168                                 watch_l_done = true;
169                                 // WATCH L requests a full list with online and offline nicks
170                                 // WATCH l requests a list with only online nicks
171                                 HandleList(user, (token[0] == 'L'));
172                         }
173                         else if ((subcmd == 'S') && (!watch_s_done))
174                         {
175                                 watch_s_done = true;
176                                 HandleStats(user);
177                         }
178                 }
179                 return CMD_SUCCESS;
180         }
181 };
182
183 class ModuleWatch
184         : public Module
185         , public Away::EventListener
186 {
187         IRCv3::Monitor::Manager manager;
188         CommandWatch cmd;
189
190         void SendAlert(User* user, const std::string& nick, unsigned int numeric, const char* numerictext, time_t shownts)
191         {
192                 const IRCv3::Monitor::WatcherList* list = manager.GetWatcherList(nick);
193                 if (!list)
194                         return;
195
196                 Numeric::Numeric num(numeric);
197                 num.push(nick).push(user->ident).push(user->GetDisplayedHost()).push(ConvToStr(shownts)).push(numerictext);
198                 for (IRCv3::Monitor::WatcherList::const_iterator i = list->begin(); i != list->end(); ++i)
199                 {
200                         LocalUser* curr = *i;
201                         curr->WriteNumeric(num);
202                 }
203         }
204
205         void Online(User* user)
206         {
207                 SendAlert(user, user->nick, RPL_LOGON, "arrived online", user->age);
208         }
209
210         void Offline(User* user, const std::string& nick)
211         {
212                 SendAlert(user, nick, RPL_LOGOFF, "went offline", user->age);
213         }
214
215  public:
216         ModuleWatch()
217                 : Away::EventListener(this)
218                 , manager(this, "watch")
219                 , cmd(this, manager)
220         {
221         }
222
223         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
224         {
225                 ConfigTag* tag = ServerInstance->Config->ConfValue("watch");
226                 cmd.maxwatch = tag->getUInt("maxwatch", 30, 1);
227         }
228
229         void OnPostConnect(User* user) CXX11_OVERRIDE
230         {
231                 Online(user);
232         }
233
234         void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE
235         {
236                 // Detect and ignore nickname case change
237                 if (ServerInstance->FindNickOnly(oldnick) == user)
238                         return;
239
240                 Offline(user, oldnick);
241                 Online(user);
242         }
243
244         void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE
245         {
246                 LocalUser* localuser = IS_LOCAL(user);
247                 if (localuser)
248                         manager.UnwatchAll(localuser);
249                 Offline(user, user->nick);
250         }
251
252         void OnUserAway(User* user) CXX11_OVERRIDE
253         {
254                 SendAlert(user, user->nick, RPL_GONEAWAY, user->awaymsg.c_str(), user->awaytime);
255         }
256
257         void OnUserBack(User* user) CXX11_OVERRIDE
258         {
259                 SendAlert(user, user->nick, RPL_NOTAWAY, "is no longer away", ServerInstance->Time());
260         }
261
262         void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
263         {
264                 tokens["WATCH"] = ConvToStr(cmd.maxwatch);
265         }
266
267         Version GetVersion() CXX11_OVERRIDE
268         {
269                 return Version("Provides WATCH support", VF_VENDOR);
270         }
271 };
272
273 MODULE_INIT(ModuleWatch)