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