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