1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018 Puck Meerburg <puck@puckipedia.com>
5  *   Copyright (C) 2018 Dylan Frank <b00mx0r@aureus.pw>
6  *   Copyright (C) 2016-2021 Sadie Powell <sadie@witchery.services>
7  *   Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
8  *   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
9  *   Copyright (C) 2012 ChrisTX <xpipe@hotmail.de>
10  *   Copyright (C) 2012 Adam <Adam@anope.org>
11  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
12  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
13  *   Copyright (C) 2006, 2008, 2010 Craig Edwards <brain@inspircd.org>
14  *
15  * This file is part of InspIRCd.  InspIRCd is free software: you can
16  * redistribute it and/or modify it under the terms of the GNU General Public
17  * License as published by the Free Software Foundation, version 2.
18  *
19  * This program is distributed in the hope that it will be useful, but WITHOUT
20  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22  * details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 
29 #include "inspircd.h"
30 #include "xline.h"
31 #include "modules/stats.h"
32 
33 #ifdef _WIN32
34 #include <psapi.h>
35 #pragma comment(lib, "psapi.lib") // For GetProcessMemoryInfo()
36 #endif
37 
38 /** Handle /STATS.
39  */
40 class CommandStats : public Command
41 {
42 	Events::ModuleEventProvider statsevprov;
43 	void DoStats(Stats::Context& stats);
44 
45  public:
46 	/** STATS characters which non-opers can request. */
47 	std::string userstats;
48 
CommandStats(Module * Creator)49 	CommandStats(Module* Creator)
50 		: Command(Creator, "STATS", 1, 2)
51 		, statsevprov(Creator, "event/stats")
52 	{
53 		allow_empty_last_param = false;
54 		syntax = "<symbol> [<servername>]";
55 	}
56 
57 	/** Handle command.
58 	 * @param parameters The parameters to the command
59 	 * @param user The user issuing the command
60 	 * @return A value from CmdResult to indicate command success or failure.
61 	 */
62 	CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
GetRouting(User * user,const Params & parameters)63 	RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
64 	{
65 		if ((parameters.size() > 1) && (parameters[1].find('.') != std::string::npos))
66 			return ROUTE_UNICAST(parameters[1]);
67 		return ROUTE_LOCALONLY;
68 	}
69 };
70 
GenerateStatsLl(Stats::Context & stats)71 static void GenerateStatsLl(Stats::Context& stats)
72 {
73 	stats.AddRow(211, InspIRCd::Format("nick[ident@%s] sendq cmds_out bytes_out cmds_in bytes_in time_open", (stats.GetSymbol() == 'l' ? "host" : "ip")));
74 
75 	const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
76 	for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
77 	{
78 		LocalUser* u = *i;
79 		stats.AddRow(211, u->nick+"["+u->ident+"@"+(stats.GetSymbol() == 'l' ? u->GetDisplayedHost() : u->GetIPString())+"] "+ConvToStr(u->eh.getSendQSize())+" "+ConvToStr(u->cmds_out)+" "+ConvToStr(u->bytes_out)+" "+ConvToStr(u->cmds_in)+" "+ConvToStr(u->bytes_in)+" "+ConvToStr(ServerInstance->Time() - u->signon));
80 	}
81 }
82 
DoStats(Stats::Context & stats)83 void CommandStats::DoStats(Stats::Context& stats)
84 {
85 	User* const user = stats.GetSource();
86 	const char statschar = stats.GetSymbol();
87 
88 	bool isPublic = userstats.find(statschar) != std::string::npos;
89 	bool isRemoteOper = IS_REMOTE(user) && (user->IsOper());
90 	bool isLocalOperWithPrivs = IS_LOCAL(user) && user->HasPrivPermission("servers/auspex");
91 
92 	if (!isPublic && !isRemoteOper && !isLocalOperWithPrivs)
93 	{
94 		ServerInstance->SNO->WriteToSnoMask('t',
95 				"%s '%c' denied for %s (%s@%s)",
96 				(IS_LOCAL(user) ? "Stats" : "Remote stats"),
97 				statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
98 		stats.AddRow(481, (std::string("Permission Denied - STATS ") + statschar + " requires the servers/auspex priv."));
99 		return;
100 	}
101 
102 	ModResult MOD_RESULT;
103 	FIRST_MOD_RESULT_CUSTOM(statsevprov, Stats::EventListener, OnStats, MOD_RESULT, (stats));
104 	if (MOD_RESULT == MOD_RES_DENY)
105 	{
106 		stats.AddRow(219, statschar, "End of /STATS report");
107 		ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",
108 			(IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
109 		return;
110 	}
111 
112 	switch (statschar)
113 	{
114 		/* stats p (show listening ports) */
115 		case 'p':
116 		{
117 			for (std::vector<ListenSocket*>::const_iterator i = ServerInstance->ports.begin(); i != ServerInstance->ports.end(); ++i)
118 			{
119 				ListenSocket* ls = *i;
120 				std::stringstream portentry;
121 
122 				const std::string type = ls->bind_tag->getString("type", "clients", 1);
123 				portentry << ls->bind_sa.str() << " (type: " << type;
124 
125 				const std::string hook = ls->bind_tag->getString("hook");
126 				if (!hook.empty())
127 					portentry << ", hook: " << hook;
128 
129 				const std::string sslprofile = ls->bind_tag->getString("sslprofile", ls->bind_tag->getString("ssl"));
130 				if (!sslprofile.empty())
131 					portentry << ", ssl profile: " << sslprofile;
132 
133 				portentry << ')';
134 				stats.AddRow(249, portentry.str());
135 			}
136 		}
137 		break;
138 
139 		/* These stats symbols must be handled by a linking module */
140 		case 'n':
141 		case 'c':
142 		break;
143 
144 		case 'i':
145 		{
146 			for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i)
147 			{
148 				ConnectClass* c = *i;
149 				Stats::Row row(215);
150 				row.push("I").push(c->name);
151 
152 				std::string param;
153 				if (c->type == CC_ALLOW)
154 					param.push_back('+');
155 				if (c->type == CC_DENY)
156 					param.push_back('-');
157 
158 				if (c->type == CC_NAMED)
159 					param.push_back('*');
160 				else
161 					param.append(c->host);
162 
163 				row.push(param).push(c->config->getString("port", "*", 1));
164 				row.push(ConvToStr(c->GetRecvqMax())).push(ConvToStr(c->GetSendqSoftMax())).push(ConvToStr(c->GetSendqHardMax())).push(ConvToStr(c->GetCommandRate()));
165 
166 				param = ConvToStr(c->GetPenaltyThreshold());
167 				if (c->fakelag)
168 					param.push_back('*');
169 				row.push(param);
170 
171 				stats.AddRow(row);
172 			}
173 		}
174 		break;
175 
176 		case 'Y':
177 		{
178 			int idx = 0;
179 			for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++)
180 			{
181 				ConnectClass* c = *i;
182 				stats.AddRow(215, 'i', "NOMATCH", '*', c->GetHost(), (c->limit ? c->limit : SocketEngine::GetMaxFds()), idx, ServerInstance->Config->ServerName, '*');
183 				stats.AddRow(218, 'Y', idx, c->GetPingTime(), '0', c->GetSendqHardMax(), ConvToStr(c->GetRecvqMax())+" "+ConvToStr(c->GetRegTimeout()));
184 				idx++;
185 			}
186 		}
187 		break;
188 
189 		case 'P':
190 		{
191 			unsigned int idx = 0;
192 			const UserManager::OperList& opers = ServerInstance->Users->all_opers;
193 			for (UserManager::OperList::const_iterator i = opers.begin(); i != opers.end(); ++i)
194 			{
195 				User* oper = *i;
196 				if (!oper->server->IsULine())
197 				{
198 					LocalUser* lu = IS_LOCAL(oper);
199 					const std::string idle = lu ? InspIRCd::DurationString(ServerInstance->Time() - lu->idle_lastmsg) : "unavailable";
200 					stats.AddRow(249, InspIRCd::Format("%s (%s@%s) Idle: %s", oper->nick.c_str(),
201 						oper->ident.c_str(), oper->GetDisplayedHost().c_str(), idle.c_str()));
202 					idx++;
203 				}
204 			}
205 			stats.AddRow(249, ConvToStr(idx)+" OPER(s)");
206 		}
207 		break;
208 
209 		case 'k':
210 			ServerInstance->XLines->InvokeStats("K", stats);
211 		break;
212 		case 'g':
213 			ServerInstance->XLines->InvokeStats("G", stats);
214 		break;
215 		case 'q':
216 			ServerInstance->XLines->InvokeStats("Q", stats);
217 		break;
218 		case 'Z':
219 			ServerInstance->XLines->InvokeStats("Z", stats);
220 		break;
221 		case 'e':
222 			ServerInstance->XLines->InvokeStats("E", stats);
223 		break;
224 		case 'E':
225 		{
226 			const SocketEngine::Statistics& sestats = SocketEngine::GetStats();
227 			stats.AddRow(249, "Total events: "+ConvToStr(sestats.TotalEvents));
228 			stats.AddRow(249, "Read events:  "+ConvToStr(sestats.ReadEvents));
229 			stats.AddRow(249, "Write events: "+ConvToStr(sestats.WriteEvents));
230 			stats.AddRow(249, "Error events: "+ConvToStr(sestats.ErrorEvents));
231 			break;
232 		}
233 
234 		/* stats m (list number of times each command has been used, plus bytecount) */
235 		case 'm':
236 		{
237 			const CommandParser::CommandMap& commands = ServerInstance->Parser.GetCommands();
238 			for (CommandParser::CommandMap::const_iterator i = commands.begin(); i != commands.end(); ++i)
239 			{
240 				if (i->second->use_count)
241 				{
242 					/* RPL_STATSCOMMANDS */
243 					stats.AddRow(212, i->second->name, i->second->use_count);
244 				}
245 			}
246 		}
247 		break;
248 
249 		/* stats z (debug and memory info) */
250 		case 'z':
251 		{
252 			stats.AddRow(249, "Users: "+ConvToStr(ServerInstance->Users->GetUsers().size()));
253 			stats.AddRow(249, "Channels: "+ConvToStr(ServerInstance->GetChans().size()));
254 			stats.AddRow(249, "Commands: "+ConvToStr(ServerInstance->Parser.GetCommands().size()));
255 
256 			float kbitpersec_in, kbitpersec_out, kbitpersec_total;
257 			SocketEngine::GetStats().GetBandwidth(kbitpersec_in, kbitpersec_out, kbitpersec_total);
258 
259 			stats.AddRow(249, InspIRCd::Format("Bandwidth total:  %03.5f kilobits/sec", kbitpersec_total));
260 			stats.AddRow(249, InspIRCd::Format("Bandwidth out:    %03.5f kilobits/sec", kbitpersec_out));
261 			stats.AddRow(249, InspIRCd::Format("Bandwidth in:     %03.5f kilobits/sec", kbitpersec_in));
262 
263 #ifndef _WIN32
264 			/* Moved this down here so all the not-windows stuff (look w00tie, I didn't say win32!) is in one ifndef.
265 			 * Also cuts out some identical code in both branches of the ifndef. -- Om
266 			 */
267 			rusage R;
268 
269 			/* Not sure why we were doing '0' with a RUSAGE_SELF comment rather than just using RUSAGE_SELF -- Om */
270 			if (!getrusage(RUSAGE_SELF,&R))	/* RUSAGE_SELF */
271 			{
272 #ifndef __HAIKU__
273 				stats.AddRow(249, "Total allocation: "+ConvToStr(R.ru_maxrss)+"K");
274 				stats.AddRow(249, "Signals:          "+ConvToStr(R.ru_nsignals));
275 				stats.AddRow(249, "Page faults:      "+ConvToStr(R.ru_majflt));
276 				stats.AddRow(249, "Swaps:            "+ConvToStr(R.ru_nswap));
277 				stats.AddRow(249, "Context Switches: Voluntary; "+ConvToStr(R.ru_nvcsw)+" Involuntary; "+ConvToStr(R.ru_nivcsw));
278 #endif
279 				float n_elapsed = (ServerInstance->Time() - ServerInstance->stats.LastSampled.tv_sec) * 1000000
280 					+ (ServerInstance->Time_ns() - ServerInstance->stats.LastSampled.tv_nsec) / 1000;
281 				float n_eaten = ((R.ru_utime.tv_sec - ServerInstance->stats.LastCPU.tv_sec) * 1000000 + R.ru_utime.tv_usec - ServerInstance->stats.LastCPU.tv_usec);
282 				float per = (n_eaten / n_elapsed) * 100;
283 
284 				stats.AddRow(249, InspIRCd::Format("CPU Use (now):    %03.5f%%", per));
285 
286 				n_elapsed = ServerInstance->Time() - ServerInstance->startup_time;
287 				n_eaten = (float)R.ru_utime.tv_sec + R.ru_utime.tv_usec / 100000.0;
288 				per = (n_eaten / n_elapsed) * 100;
289 
290 				stats.AddRow(249, InspIRCd::Format("CPU Use (total):  %03.5f%%", per));
291 			}
292 #else
293 			PROCESS_MEMORY_COUNTERS MemCounters;
294 			if (GetProcessMemoryInfo(GetCurrentProcess(), &MemCounters, sizeof(MemCounters)))
295 			{
296 				stats.AddRow(249, "Total allocation: "+ConvToStr((MemCounters.WorkingSetSize + MemCounters.PagefileUsage) / 1024)+"K");
297 				stats.AddRow(249, "Pagefile usage:   "+ConvToStr(MemCounters.PagefileUsage / 1024)+"K");
298 				stats.AddRow(249, "Page faults:      "+ConvToStr(MemCounters.PageFaultCount));
299 			}
300 
301 			FILETIME CreationTime;
302 			FILETIME ExitTime;
303 			FILETIME KernelTime;
304 			FILETIME UserTime;
305 			LARGE_INTEGER ThisSample;
306 			if(GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime) &&
307 				QueryPerformanceCounter(&ThisSample))
308 			{
309 				KernelTime.dwHighDateTime += UserTime.dwHighDateTime;
310 				KernelTime.dwLowDateTime += UserTime.dwLowDateTime;
311 				double n_eaten = (double)( ( (uint64_t)(KernelTime.dwHighDateTime - ServerInstance->stats.LastCPU.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime - ServerInstance->stats.LastCPU.dwLowDateTime) )/100000;
312 				double n_elapsed = (double)(ThisSample.QuadPart - ServerInstance->stats.LastSampled.QuadPart) / ServerInstance->stats.QPFrequency.QuadPart;
313 				double per = (n_eaten/n_elapsed);
314 
315 				stats.AddRow(249, InspIRCd::Format("CPU Use (now):    %03.5f%%", per));
316 
317 				n_elapsed = ServerInstance->Time() - ServerInstance->startup_time;
318 				n_eaten = (double)(( (uint64_t)(KernelTime.dwHighDateTime) << 32 ) + (uint64_t)(KernelTime.dwLowDateTime))/100000;
319 				per = (n_eaten / n_elapsed);
320 
321 				stats.AddRow(249, InspIRCd::Format("CPU Use (total):  %03.5f%%", per));
322 			}
323 #endif
324 		}
325 		break;
326 
327 		case 'T':
328 		{
329 			stats.AddRow(249, "accepts "+ConvToStr(ServerInstance->stats.Accept)+" refused "+ConvToStr(ServerInstance->stats.Refused));
330 			stats.AddRow(249, "unknown commands "+ConvToStr(ServerInstance->stats.Unknown));
331 			stats.AddRow(249, "nick collisions "+ConvToStr(ServerInstance->stats.Collisions));
332 			stats.AddRow(249, "dns requests "+ConvToStr(ServerInstance->stats.DnsGood+ServerInstance->stats.DnsBad)+" succeeded "+ConvToStr(ServerInstance->stats.DnsGood)+" failed "+ConvToStr(ServerInstance->stats.DnsBad));
333 			stats.AddRow(249, "connection count "+ConvToStr(ServerInstance->stats.Connects));
334 			stats.AddRow(249, InspIRCd::Format("bytes sent %5.2fK recv %5.2fK",
335 				ServerInstance->stats.Sent / 1024.0, ServerInstance->stats.Recv / 1024.0));
336 		}
337 		break;
338 
339 		/* stats o */
340 		case 'o':
341 		{
342 			for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->oper_blocks.begin(); i != ServerInstance->Config->oper_blocks.end(); ++i)
343 			{
344 				OperInfo* ifo = i->second;
345 				ConfigTag* tag = ifo->oper_block;
346 				stats.AddRow(243, 'O', tag->getString("host"), '*', tag->getString("name"), tag->getString("type"), '0');
347 			}
348 		}
349 		break;
350 		case 'O':
351 		{
352 			for (ServerConfig::OperIndex::const_iterator i = ServerInstance->Config->OperTypes.begin(); i != ServerInstance->Config->OperTypes.end(); ++i)
353 			{
354 				OperInfo* tag = i->second;
355 				tag->init();
356 				std::string umodes;
357 				std::string cmodes;
358 				for(char c='A'; c <= 'z'; c++)
359 				{
360 					ModeHandler* mh = ServerInstance->Modes->FindMode(c, MODETYPE_USER);
361 					if (mh && mh->NeedsOper() && tag->AllowedUserModes[c - 'A'])
362 						umodes.push_back(c);
363 					mh = ServerInstance->Modes->FindMode(c, MODETYPE_CHANNEL);
364 					if (mh && mh->NeedsOper() && tag->AllowedChanModes[c - 'A'])
365 						cmodes.push_back(c);
366 				}
367 				stats.AddRow(243, 'O', tag->name, umodes, cmodes);
368 			}
369 		}
370 		break;
371 
372 		/* stats l (show user I/O stats) */
373 		case 'l':
374 		/* stats L (show user I/O stats with IP addresses) */
375 		case 'L':
376 			GenerateStatsLl(stats);
377 		break;
378 
379 		/* stats u (show server uptime) */
380 		case 'u':
381 		{
382 			unsigned int up = static_cast<unsigned int>(ServerInstance->Time() - ServerInstance->startup_time);
383 			stats.AddRow(242, InspIRCd::Format("Server up %u days, %.2u:%.2u:%.2u",
384 				up / 86400, (up / 3600) % 24, (up / 60) % 60, up % 60));
385 		}
386 		break;
387 
388 		default:
389 		break;
390 	}
391 
392 	stats.AddRow(219, statschar, "End of /STATS report");
393 	ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",
394 		(IS_LOCAL(user) ? "Stats" : "Remote stats"), statschar, user->nick.c_str(), user->ident.c_str(), user->GetRealHost().c_str());
395 	return;
396 }
397 
Handle(User * user,const Params & parameters)398 CmdResult CommandStats::Handle(User* user, const Params& parameters)
399 {
400 	if (parameters.size() > 1 && !irc::equals(parameters[1], ServerInstance->Config->ServerName))
401 	{
402 		// Give extra penalty if a non-oper does /STATS <remoteserver>
403 		LocalUser* localuser = IS_LOCAL(user);
404 		if ((localuser) && (!user->IsOper()))
405 			localuser->CommandFloodPenalty += 2000;
406 		return CMD_SUCCESS;
407 	}
408 	Stats::Context stats(user, parameters[0][0]);
409 	DoStats(stats);
410 	const std::vector<Stats::Row>& rows = stats.GetRows();
411 	for (std::vector<Stats::Row>::const_iterator i = rows.begin(); i != rows.end(); ++i)
412 	{
413 		const Stats::Row& row = *i;
414 		user->WriteRemoteNumeric(row);
415 	}
416 
417 	return CMD_SUCCESS;
418 }
419 
420 class CoreModStats : public Module
421 {
422  private:
423 	CommandStats cmd;
424 
425  public:
CoreModStats()426 	CoreModStats()
427 		: cmd(this)
428 	{
429 	}
430 
ReadConfig(ConfigStatus & status)431 	void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
432 	{
433 		ConfigTag* security = ServerInstance->Config->ConfValue("security");
434 		cmd.userstats = security->getString("userstats", "Pu");
435 	}
436 
GetVersion()437 	Version GetVersion() CXX11_OVERRIDE
438 	{
439 		return Version("Provides the STATS command", VF_CORE | VF_VENDOR);
440 	}
441 };
442 
443 MODULE_INIT(CoreModStats)
444