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