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