Author: Kev <klmitch@mit.edu>
[ircu2.10.12-pk.git] / ircd / m_stats.c
1 /*
2  * IRC - Internet Relay Chat, ircd/m_stats.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Computing Center
5  *
6  * See file AUTHORS in IRC package for additional names of
7  * the programmers.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 1, or (at your option)
12  * any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  *
23  * $Id$
24  */
25
26 /*
27  * m_functions execute protocol messages on this server:
28  *
29  *    cptr    is always NON-NULL, pointing to a *LOCAL* client
30  *            structure (with an open socket connected!). This
31  *            identifies the physical socket where the message
32  *            originated (or which caused the m_function to be
33  *            executed--some m_functions may call others...).
34  *
35  *    sptr    is the source of the message, defined by the
36  *            prefix part of the message if present. If not
37  *            or prefix not found, then sptr==cptr.
38  *
39  *            (!IsServer(cptr)) => (cptr == sptr), because
40  *            prefixes are taken *only* from servers...
41  *
42  *            (IsServer(cptr))
43  *                    (sptr == cptr) => the message didn't
44  *                    have the prefix.
45  *
46  *                    (sptr != cptr && IsServer(sptr) means
47  *                    the prefix specified servername. (?)
48  *
49  *                    (sptr != cptr && !IsServer(sptr) means
50  *                    that message originated from a remote
51  *                    user (not local).
52  *
53  *            combining
54  *
55  *            (!IsServer(sptr)) means that, sptr can safely
56  *            taken as defining the target structure of the
57  *            message in this server.
58  *
59  *    *Always* true (if 'parse' and others are working correct):
60  *
61  *    1)      sptr->from == cptr  (note: cptr->from == cptr)
62  *
63  *    2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
64  *            *cannot* be a local connection, unless it's
65  *            actually cptr!). [MyConnect(x) should probably
66  *            be defined as (x == x->from) --msa ]
67  *
68  *    parc    number of variable parameter strings (if zero,
69  *            parv is allowed to be NULL)
70  *
71  *    parv    a NULL terminated list of parameter pointers,
72  *
73  *                    parv[0], sender (prefix string), if not present
74  *                            this points to an empty string.
75  *                    parv[1]...parv[parc-1]
76  *                            pointers to additional parameters
77  *                    parv[parc] == NULL, *always*
78  *
79  *            note:   it is guaranteed that parv[0]..parv[parc-1] are all
80  *                    non-NULL pointers.
81  */
82 #if 0
83 /*
84  * No need to include handlers.h here the signatures must match
85  * and we don't need to force a rebuild of all the handlers everytime
86  * we add a new one to the list. --Bleep
87  */
88 #include "handlers.h"
89 #endif /* 0 */
90 /*
91  * XXX - ack!!!
92  */
93 #include "s_stats.h"
94 #include "channel.h"
95 #include "class.h"
96 #include "client.h"
97 #include "gline.h"
98 #include "hash.h"
99 #include "ircd.h"
100 #include "ircd_alloc.h"
101 #include "ircd_chattr.h"
102 #include "ircd_reply.h"
103 #include "ircd_string.h"
104 #include "list.h"
105 #include "listener.h"
106 #include "match.h"
107 #include "motd.h"
108 #include "msg.h"
109 #include "numeric.h"
110 #include "numnicks.h"
111 #include "opercmds.h"
112 #include "s_bsd.h"
113 #include "s_conf.h"
114 #include "s_debug.h"
115 #include "s_misc.h"
116 #include "s_serv.h"
117 #include "s_user.h"
118 #include "send.h"
119 #include "struct.h"
120 #include "userload.h"
121
122 #include <assert.h>
123 #include <stdlib.h>
124 #include <string.h>
125
126
127 int report_klines(struct Client* sptr, char* mask, int limit_query)
128 {
129   int   wilds = 0;
130   int   count = 3;
131   char* user  = 0;
132   char* host;
133   const struct DenyConf* conf;
134
135   if (EmptyString(mask)) {
136     if (limit_query)
137       return need_more_params(sptr, "STATS K");
138     else
139       report_deny_list(sptr);
140     return 1;
141   }
142
143   if (!limit_query) {
144     wilds = string_has_wildcards(mask);
145     count = 1000;
146   }
147
148   if ((host = strchr(mask, '@'))) {
149     user = mask;
150     *host++ = '\0';
151   }
152   else {
153     host = mask;
154   }
155
156   for (conf = conf_get_deny_list(); conf; conf = conf->next) {
157     if ((!wilds && ((user || conf->hostmask) &&
158         !match(conf->hostmask, host) &&
159         (!user || !match(conf->usermask, user)))) ||
160         (wilds && !mmatch(host, conf->hostmask) &&
161         (!user || !mmatch(user, conf->usermask))))
162     {
163       send_reply(sptr, RPL_STATSKLINE, (conf->ip_kill) ? 'k' : 'K',
164                  conf->hostmask, conf->message, conf->usermask);
165       if (--count == 0)
166         return 1;
167     }
168   }
169   /* send_reply(sptr, RPL_ENDOFSTATS, stat); */
170   return 1;
171 }
172
173
174 /*
175  * m_stats - generic message handler
176  *
177  *    parv[0] = sender prefix
178  *    parv[1] = statistics selector (defaults to Message frequency)
179  *    parv[2] = target server (current server defaulted, if omitted)
180  * And 'stats l' and 'stats' L:
181  *    parv[3] = server mask ("*" defaulted, if omitted)
182  * Or for stats p,P:
183  *    parv[3] = port mask (returns p-lines when its port is matched by this)
184  * Or for stats k,K,i and I:
185  *    parv[3] = [user@]host.name  (returns which K/I-lines match this)
186  *           or [user@]host.mask  (returns which K/I-lines are mmatched by this)
187  *              (defaults to old reply if ommitted, when local or Oper)
188  *              A remote mask (something containing wildcards) is only
189  *              allowed for IRC Operators.
190  * Or for stats M:
191  *    parv[3] = time param
192  *    parv[4] = time param 
193  *    (see report_memleak_stats() in runmalloc.c for details)
194  *
195  * This function is getting really ugly. -Ghostwolf
196  */
197 int m_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
198 {
199   struct Message *mptr;
200   struct Client *acptr;
201   struct ConfItem *aconf;
202   char stat = parc > 1 ? parv[1][0] : '\0';
203   const char **infotext = statsinfo;
204   int i;
205
206   if (hunt_stats(cptr, sptr, parc, parv, stat) != HUNTED_ISME)
207     return 0;
208
209   switch (stat)
210   {
211     case 'L':
212     case 'l':
213     {
214       int doall = 0;
215       int wilds = 0;
216       char *name = "*";
217
218       if (parc > 3 && !EmptyString(parv[3])) {
219         name = parv[3];
220         wilds = string_has_wildcards(name);
221       }
222       else
223         doall = 1;
224       /*
225        * Send info about connections which match, or all if the
226        * mask matches me.name.  Only restrictions are on those who
227        * are invisible not being visible to 'foreigners' who use
228        * a wild card based search to list it.
229        */
230       send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ "
231                  "SendM SendKBytes RcveM RcveKBytes :Open since");
232       for (i = 0; i <= HighestFd; i++)
233       {
234         if (!(acptr = LocalClientArray[i]))
235           continue;
236         /* Don't return clients when this is a request for `all' */
237         if (doall && IsUser(acptr))
238           continue;
239         /* Don't show invisible people to non opers unless they know the nick */
240         if (IsInvisible(acptr) && (doall || wilds) && !IsAnOper(acptr) && (acptr != sptr))
241           continue;
242         /* Only show the ones that match the given mask - if any */
243         if (!doall && wilds && match(name, cli_name(acptr)))
244           continue;
245         /* Skip all that do not match the specific query */
246         if (!(doall || wilds) && 0 != ircd_strcmp(name, cli_name(acptr)))
247           continue;
248         send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO,
249                    "%s %u %u %u %u %u :%Tu", (*(cli_name(acptr))) ? cli_name(acptr) : "<unregistered>",
250                    (int)MsgQLength(&(cli_sendQ(acptr))), (int)cli_sendM(acptr),
251                    (int)cli_sendK(acptr), (int)cli_receiveM(acptr),
252                    (int)cli_receiveK(acptr), CurrentTime - cli_firsttime(acptr));
253       }
254       break;
255     }
256     case 'C':
257     case 'c':
258       report_configured_links(sptr, CONF_SERVER);
259       break;
260     case 'G':
261     case 'g': /* send glines */
262       gline_stats(sptr);
263       break;
264     case 'H':
265     case 'h':
266       report_configured_links(sptr, CONF_HUB | CONF_LEAF);
267       break;
268     case 'K':
269     case 'k':    /* display CONF_IPKILL as well as CONF_KILL -Kev */
270       if (0 == report_klines(sptr, (parc == 4) ? parv[3] : 0, 0))
271         return 0;
272       break;
273     case 'F':
274     case 'f':
275       report_feature_list(sptr);
276       break;
277     case 'I':
278     case 'i':
279     {
280       int wilds = 0;
281       int count = 1000;
282       char* host;
283
284       if (parc < 4) {
285         report_configured_links(sptr, CONF_CLIENT);
286         break;
287       }
288       if (EmptyString(parv[3]))
289         return need_more_params(sptr, "STATS I");
290
291       host = parv[3];
292       wilds = string_has_wildcards(host);
293
294       for (aconf = GlobalConfList; aconf; aconf = aconf->next) {
295         if (CONF_CLIENT == aconf->status) {
296           if ((!wilds && (!match(aconf->host, host) ||
297               !match(aconf->name, host))) ||
298               (wilds && (!mmatch(host, aconf->host) ||
299               !mmatch(host, aconf->name))))
300           {
301             send_reply(sptr, RPL_STATSILINE, 'I', aconf->host, aconf->name,
302                        aconf->port, get_conf_class(aconf));
303             if (--count == 0)
304               break;
305           }
306         }
307       }
308       break;
309     }
310     case 'M':
311 #if !defined(NDEBUG)
312       send_reply(sptr, RPL_STATMEMTOT, fda_get_byte_count(),
313                  fda_get_block_count());
314 #endif
315       break;
316     case 'm':
317       for (mptr = msgtab; mptr->cmd; mptr++)
318         if (mptr->count)
319           send_reply(sptr, RPL_STATSCOMMANDS, mptr->cmd, mptr->count,
320                      mptr->bytes);
321       break;
322     case 'o':
323     case 'O':
324       report_configured_links(sptr, CONF_OPS);
325       break;
326     case 'p':
327     case 'P':
328       /*
329        * show listener ports
330        * show hidden ports to opers, if there are more than 3 parameters,
331        * interpret the fourth parameter as the port number.
332        */ 
333       show_ports(sptr, 0, (parc > 3) ? atoi(parv[3]) : 0, 100);
334       break;
335     case 'R':
336     case 'r':
337 #ifdef DEBUGMODE
338       send_usage(sptr, parv[0]);
339 #endif
340       break;
341     case 'D':
342       report_crule_list(sptr, CRULE_ALL);
343       break;
344     case 'd':
345       report_crule_list(sptr, CRULE_MASK);
346       break;
347     case 't':
348       tstats(sptr, parv[0]);
349       break;
350     case 'T':
351       motd_report(sptr);
352       break;
353     case 'U':
354       report_configured_links(sptr, CONF_UWORLD);
355       break;
356     case 'u':
357     {
358       time_t nowr;
359
360       nowr = CurrentTime - cli_since(&me);
361       send_reply(sptr, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24,
362                  (nowr / 60) % 60, nowr % 60);
363       send_reply(sptr, RPL_STATSCONN, max_connection_count, max_client_count);
364       break;
365     }
366     case 'W':
367     case 'w':
368       calc_load(sptr);
369       break;
370     case 'X':
371     case 'x':
372 #ifdef  DEBUGMODE
373       class_send_meminfo(sptr);
374       send_listinfo(sptr, parv[0]);
375 #endif
376       break;
377     case 'Y':
378     case 'y':
379       report_classes(sptr);
380       break;
381     case 'Z':
382     case 'z':
383       count_memory(sptr, parv[0]);
384       break;
385     default:
386       stat = '*';
387       while (*infotext)
388         sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :%s", sptr, *infotext++);
389       break;
390   }
391   send_reply(sptr, RPL_ENDOFSTATS, stat);
392   return 0;
393 }
394
395 /*
396  * ms_stats - server message handler
397  *
398  *    parv[0] = sender prefix
399  *    parv[1] = statistics selector (defaults to Message frequency)
400  *    parv[2] = target server (current server defaulted, if omitted)
401  * And 'stats l' and 'stats' L:
402  *    parv[3] = server mask ("*" defaulted, if omitted)
403  * Or for stats p,P:
404  *    parv[3] = port mask (returns p-lines when its port is matched by this)
405  * Or for stats k,K,i and I:
406  *    parv[3] = [user@]host.name  (returns which K/I-lines match this)
407  *           or [user@]host.mask  (returns which K/I-lines are mmatched by this)
408  *              (defaults to old reply if ommitted, when local or Oper)
409  *              A remote mask (something containing wildcards) is only
410  *              allowed for IRC Operators.
411  * Or for stats M:
412  *    parv[3] = time param
413  *    parv[4] = time param 
414  *    (see report_memleak_stats() in runmalloc.c for details)
415  *
416  * This function is getting really ugly. -Ghostwolf
417  */
418 int ms_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
419 {
420   struct Message *mptr;
421   struct Client *acptr;
422   struct ConfItem *aconf;
423   char stat = parc > 1 ? parv[1][0] : '\0';
424   int i;
425
426   if (hunt_stats(cptr, sptr, parc, parv, stat) != HUNTED_ISME)
427     return 0;
428
429   switch (stat)
430   {
431     case 'L':
432     case 'l':
433     {
434       int doall = 0;
435       int wilds = 0;
436       char *name = "*";
437
438       if (parc > 3 && !EmptyString(parv[3])) {
439         name = parv[3];
440         wilds = string_has_wildcards(name);
441       }
442       else
443         doall = 1;
444       /*
445        * Send info about connections which match, or all if the
446        * mask matches me.name.  Only restrictions are on those who
447        * are invisible not being visible to 'foreigners' who use
448        * a wild card based search to list it.
449        */
450       send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ "
451                  "SendM SendKBytes RcveM RcveKBytes :Open since");
452       for (i = 0; i <= HighestFd; i++)
453       {
454         if (!(acptr = LocalClientArray[i]))
455           continue;
456         /* Don't return clients when this is a request for `all' */
457         if (doall && IsUser(acptr))
458           continue;
459         /* Don't show invisible people to unauthorized people when using
460          * wildcards  -- Is this still needed now /stats is oper only ? 
461          * Not here, because ms_stats is specifically a remote command, 
462          * thus the check was removed. -Ghostwolf */
463         /* Only show the ones that match the given mask - if any */
464         if (!doall && wilds && match(name, cli_name(acptr)))
465           continue;
466         /* Skip all that do not match the specific query */
467         if (!(doall || wilds) && 0 != ircd_strcmp(name, cli_name(acptr)))
468           continue;
469         send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO,
470                    "%s %u %u %u %u %u :%Tu", cli_name(acptr),
471                    (int)MsgQLength(&(cli_sendQ(acptr))), (int)cli_sendM(acptr),
472                    (int)cli_sendK(acptr), (int)cli_receiveM(acptr),
473                    (int)cli_receiveK(acptr), CurrentTime - cli_firsttime(acptr));
474       }
475       break;
476     }
477     case 'C':
478     case 'c':
479       report_configured_links(sptr, CONF_SERVER);
480       break;
481     case 'G':
482     case 'g': /* send glines */
483       gline_stats(sptr);
484       break;
485     case 'H':
486     case 'h':
487       report_configured_links(sptr, CONF_HUB | CONF_LEAF);
488       break;
489     case 'K':
490     case 'k':    /* display CONF_IPKILL as well as CONF_KILL -Kev */
491       if (0 == report_klines(sptr, (parc > 3) ? parv[3] : 0, !IsOper(sptr)))
492         return 0;
493       break;
494     case 'F':
495     case 'f':
496       report_feature_list(sptr);
497       break;
498     case 'I':
499     case 'i':
500     {
501       int   wilds = 0;
502       int   count = 3;
503       char* host;
504
505       if (parc < 4 && IsOper(sptr)) {
506         report_configured_links(sptr, CONF_CLIENT);
507         break;
508       }
509       if (parc < 4 || EmptyString(parv[3]))
510         return need_more_params(sptr, "STATS I");
511
512       if (IsOper(sptr)) {
513         wilds = string_has_wildcards(parv[3]);
514         count = 1000;
515       }
516
517       host = parv[3];
518
519       for (aconf = GlobalConfList; aconf; aconf = aconf->next) {
520         if (CONF_CLIENT == aconf->status) {
521           if ((!wilds && (!match(aconf->host, host) ||
522               !match(aconf->name, host))) ||
523               (wilds && (!mmatch(host, aconf->host) ||
524               !mmatch(host, aconf->name))))
525           {
526             send_reply(sptr, RPL_STATSILINE, 'I', aconf->host, aconf->name,
527                        aconf->port, get_conf_class(aconf));
528             if (--count == 0)
529               break;
530           }
531         }
532       }
533       break;
534     }
535     case 'M':
536 #if !defined(NDEBUG)
537       send_reply(sptr, RPL_STATMEMTOT, fda_get_byte_count(),
538                  fda_get_block_count());
539 #endif
540       break;
541     case 'm':
542       for (mptr = msgtab; mptr->cmd; mptr++)
543         if (mptr->count)
544           send_reply(sptr, RPL_STATSCOMMANDS, mptr->cmd, mptr->count,
545                      mptr->bytes);
546       break;
547     case 'o':
548     case 'O':
549       report_configured_links(sptr, CONF_OPS);
550       break;
551     case 'p':
552     case 'P':
553       /*
554        * show listener ports
555        * show hidden ports to opers, if there are more than 3 parameters,
556        * interpret the fourth parameter as the port number, limit non-local
557        * or non-oper results to 8 ports.
558        */ 
559       show_ports(sptr, IsOper(sptr), (parc > 3) ? atoi(parv[3]) : 0, IsOper(sptr) ? 100 : 8);
560       break;
561     case 'R':
562     case 'r':
563 #ifdef DEBUGMODE
564       send_usage(sptr, parv[0]);
565 #endif
566       break;
567     case 'D':
568       report_crule_list(sptr, CRULE_ALL);
569       break;
570     case 'd':
571       report_crule_list(sptr, CRULE_MASK);
572       break;
573     case 't':
574       tstats(sptr, parv[0]);
575       break;
576     case 'T':
577       motd_report(sptr);
578       break;
579     case 'U':
580       report_configured_links(sptr, CONF_UWORLD);
581       break;
582     case 'u':
583     {
584       time_t nowr;
585
586       nowr = CurrentTime - cli_since(&me);
587       send_reply(sptr, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24,
588                  (nowr / 60) % 60, nowr % 60);
589       send_reply(sptr, RPL_STATSCONN, max_connection_count, max_client_count);
590       break;
591     }
592     case 'W':
593     case 'w':
594       calc_load(sptr);
595       break;
596     case 'X':
597     case 'x':
598 #ifdef  DEBUGMODE
599       class_send_meminfo(sptr);
600       send_listinfo(sptr, parv[0]);
601 #endif
602       break;
603     case 'Y':
604     case 'y':
605       report_classes(sptr);
606       break;
607     case 'Z':
608     case 'z':
609       count_memory(sptr, parv[0]);
610       break;
611     default:
612       stat = '*';
613       break;
614   }
615   send_reply(sptr, RPL_ENDOFSTATS, stat);
616   return 0;
617 }
618
619 /*
620  * mo_stats - oper message handler
621  *
622  *    parv[0] = sender prefix
623  *    parv[1] = statistics selector (defaults to Message frequency)
624  *    parv[2] = target server (current server defaulted, if omitted)
625  * And 'stats l' and 'stats' L:
626  *    parv[3] = server mask ("*" defaulted, if omitted)
627  * Or for stats p,P:
628  *    parv[3] = port mask (returns p-lines when its port is matched by this)
629  * Or for stats k,K,i and I:
630  *    parv[3] = [user@]host.name  (returns which K/I-lines match this)
631  *           or [user@]host.mask  (returns which K/I-lines are mmatched by this)
632  *              (defaults to old reply if ommitted, when local or Oper)
633  *              A remote mask (something containing wildcards) is only
634  *              allowed for IRC Operators.
635  * Or for stats M:
636  *    parv[3] = time param
637  *    parv[4] = time param 
638  *    (see report_memleak_stats() in runmalloc.c for details)
639  *
640  * This function is getting really ugly. -Ghostwolf
641  */
642 int mo_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
643 {
644   struct Message*  mptr;
645   struct Client*   acptr;
646   struct ConfItem* aconf;
647   char             stat = parc > 1 ? parv[1][0] : '\0';
648   const char**     infotext = statsinfo;
649   int              i;
650
651   if (hunt_stats(cptr, sptr, parc, parv, stat) != HUNTED_ISME)
652     return 0;
653
654   switch (stat)
655   {
656     case 'L':
657     case 'l':
658     {
659       int doall = 0, wilds = 0;
660       char* name = "*";
661       if (parc > 3 && !EmptyString(parv[3])) {
662         name = parv[3];
663         wilds = string_has_wildcards(name);
664       }
665       else
666         doall = 1;
667       /*
668        * Send info about connections which match, or all if the
669        * mask matches me.name.  Only restrictions are on those who
670        * are invisible not being visible to 'foreigners' who use
671        * a wild card based search to list it.
672        */
673       send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ "
674                  "SendM SendKBytes RcveM RcveKBytes :Open since");
675       for (i = 0; i <= HighestFd; i++)
676       {
677         if (!(acptr = LocalClientArray[i]))
678           continue;
679         /* Don't return clients when this is a request for `all' */
680         if (doall && IsUser(acptr))
681           continue;
682         /* Only show the ones that match the given mask - if any */
683         if (!doall && wilds && match(name, cli_name(acptr)))
684           continue;
685         /* Skip all that do not match the specific query */
686         if (!(doall || wilds) && 0 != ircd_strcmp(name, cli_name(acptr)))
687           continue;
688         send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO,
689                    "%s %u %u %u %u %u :%Tu", cli_name(acptr),
690                    (int)MsgQLength(&(cli_sendQ(acptr))), (int)cli_sendM(acptr),
691                    (int)cli_sendK(acptr), (int)cli_receiveM(acptr),
692                    (int)cli_receiveK(acptr), CurrentTime - cli_firsttime(acptr));
693       }
694       break;
695     }
696     case 'C':
697     case 'c':
698       report_configured_links(sptr, CONF_SERVER);
699       break;
700     case 'G':
701     case 'g': /* send glines */
702       gline_stats(sptr);
703       break;
704     case 'H':
705     case 'h':
706       report_configured_links(sptr, CONF_HUB | CONF_LEAF);
707       break;
708     case 'K':
709     case 'k':    /* display CONF_IPKILL as well as CONF_KILL -Kev */
710       if (0 == report_klines(sptr, (parc > 3) ? parv[3] : 0, 0))
711         return 0;
712       break;
713     case 'F':
714     case 'f':
715       report_feature_list(sptr);
716       break;
717     case 'I':
718     case 'i':
719       {
720         int   wilds = 0;
721         int   count = 1000;
722         char* host;
723
724         if (parc < 4) {
725           report_configured_links(sptr, CONF_CLIENT);
726           break;
727         }
728         if (EmptyString(parv[3]))
729           return need_more_params(sptr, "STATS I");
730
731         host = parv[3];
732         wilds = string_has_wildcards(host);
733
734         for (aconf = GlobalConfList; aconf; aconf = aconf->next) {
735           if (CONF_CLIENT == aconf->status) {
736             if ((!wilds && (!match(aconf->host, host) ||
737                 !match(aconf->name, host))) ||
738                 (wilds && (!mmatch(host, aconf->host) ||
739                 !mmatch(host, aconf->name))))
740             {
741               send_reply(sptr, RPL_STATSILINE, 'I', aconf->host, aconf->name,
742                          aconf->port, get_conf_class(aconf));
743               if (--count == 0)
744                 break;
745             }
746           }
747         }
748       }
749       break;
750     case 'M':
751 #if !defined(NDEBUG)
752       send_reply(sptr, RPL_STATMEMTOT, fda_get_byte_count(),
753                  fda_get_block_count());
754 #endif
755       break;
756     case 'm':
757       for (mptr = msgtab; mptr->cmd; mptr++)
758         if (mptr->count)
759           send_reply(sptr, RPL_STATSCOMMANDS, mptr->cmd, mptr->count,
760                      mptr->bytes);
761       break;
762     case 'o':
763     case 'O':
764       report_configured_links(sptr, CONF_OPS);
765       break;
766     case 'p':
767     case 'P':
768       /*
769        * show listener ports
770        * show hidden ports to opers, if there are more than 3 parameters,
771        * interpret the fourth parameter as the port number, limit non-local
772        * or non-oper results to 8 ports.
773        */ 
774       show_ports(sptr, 1, (parc > 3) ? atoi(parv[3]) : 0, 100);
775       break;
776     case 'R':
777     case 'r':
778 #ifdef DEBUGMODE
779       send_usage(sptr, parv[0]);
780 #endif
781       break;
782     case 'D':
783       report_crule_list(sptr, CRULE_ALL);
784       break;
785     case 'd':
786       report_crule_list(sptr, CRULE_MASK);
787       break;
788     case 't':
789       tstats(sptr, parv[0]);
790       break;
791     case 'T':
792       motd_report(sptr);
793       break;
794     case 'U':
795       report_configured_links(sptr, CONF_UWORLD);
796       break;
797     case 'u':
798     {
799       time_t nowr;
800
801       nowr = CurrentTime - cli_since(&me);
802       send_reply(sptr, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24,
803                  (nowr / 60) % 60, nowr % 60);
804       send_reply(sptr, RPL_STATSCONN, max_connection_count, max_client_count);
805       break;
806     }
807     case 'W':
808     case 'w':
809       calc_load(sptr);
810       break;
811     case 'X':
812     case 'x':
813 #ifdef  DEBUGMODE
814       class_send_meminfo(sptr);
815       send_listinfo(sptr, parv[0]);
816 #endif
817       break;
818     case 'Y':
819     case 'y':
820       report_classes(sptr);
821       break;
822     case 'Z':
823     case 'z':
824       count_memory(sptr, parv[0]);
825       break;
826     default:
827       stat = '*';
828       while (*infotext)
829         sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :%s", sptr, *infotext++);
830       break;
831   }
832   send_reply(sptr, RPL_ENDOFSTATS, stat);
833   return 0;
834 }
835