X-Git-Url: http://git.pk910.de/?a=blobdiff_plain;f=ircd%2Fm_stats.c;h=a68d0d7f804f527093a313202f54db496048843b;hb=refs%2Fheads%2Fupstream-ssl;hp=de306a0f65e40d21252bc80dc90ab812b018d195;hpb=995343b7b6c97bb8499f5c4b625c10601592d080;p=ircu2.10.12-pk.git diff --git a/ircd/m_stats.c b/ircd/m_stats.c index de306a0..a68d0d7 100644 --- a/ircd/m_stats.c +++ b/ircd/m_stats.c @@ -79,787 +79,88 @@ * note: it is guaranteed that parv[0]..parv[parc-1] are all * non-NULL pointers. */ -#if 0 -/* - * No need to include handlers.h here the signatures must match - * and we don't need to force a rebuild of all the handlers everytime - * we add a new one to the list. --Bleep - */ -#include "handlers.h" -#endif /* 0 */ -/* - * XXX - ack!!! - */ -#include "s_stats.h" -#include "channel.h" -#include "class.h" +#include "config.h" + #include "client.h" -#include "gline.h" -#include "hash.h" #include "ircd.h" -#include "ircd_alloc.h" -#include "ircd_chattr.h" +#include "ircd_features.h" +#include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" -#include "list.h" -#include "listener.h" -#include "match.h" -#include "motd.h" #include "msg.h" #include "numeric.h" -#include "numnicks.h" -#include "opercmds.h" -#include "s_bsd.h" -#include "s_conf.h" -#include "s_debug.h" -#include "s_misc.h" -#include "s_serv.h" +#include "s_stats.h" #include "s_user.h" #include "send.h" #include "struct.h" -#include "userload.h" -#include +/* #include -- Now using assert in ircd_log.h */ #include #include - -int report_klines(struct Client* sptr, char* mask, int limit_query) -{ - int wilds = 0; - int count = 3; - char* user = 0; - char* host; - const struct DenyConf* conf; - - if (EmptyString(mask)) { - if (limit_query) - return need_more_params(sptr, "STATS K"); - else - report_deny_list(sptr); - return 1; - } - - if (!limit_query) { - wilds = string_has_wildcards(mask); - count = 1000; - } - - if ((host = strchr(mask, '@'))) { - user = mask; - *host++ = '\0'; - } - else { - host = mask; - } - - for (conf = conf_get_deny_list(); conf; conf = conf->next) { - if ((!wilds && ((user || conf->hostmask) && - !match(conf->hostmask, host) && - (!user || !match(conf->usermask, user)))) || - (wilds && !mmatch(host, conf->hostmask) && - (!user || !mmatch(user, conf->usermask)))) - { - send_reply(sptr, RPL_STATSKLINE, (conf->ip_kill) ? 'k' : 'K', - conf->hostmask, conf->message, conf->usermask); - if (--count == 0) - return 1; - } - } - /* send_reply(sptr, RPL_ENDOFSTATS, stat); */ - return 1; -} - - /* * m_stats - generic message handler * * parv[0] = sender prefix - * parv[1] = statistics selector (defaults to Message frequency) - * parv[2] = target server (current server defaulted, if omitted) - * And 'stats l' and 'stats' L: - * parv[3] = server mask ("*" defaulted, if omitted) - * Or for stats p,P: - * parv[3] = port mask (returns p-lines when its port is matched by this) - * Or for stats k,K,i and I: - * parv[3] = [user@]host.name (returns which K/I-lines match this) - * or [user@]host.mask (returns which K/I-lines are mmatched by this) - * (defaults to old reply if ommitted, when local or Oper) - * A remote mask (something containing wildcards) is only - * allowed for IRC Operators. - * Or for stats M: - * parv[3] = time param - * parv[4] = time param - * (see report_memleak_stats() in runmalloc.c for details) - * - * This function is getting really ugly. -Ghostwolf - */ -int m_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) -{ - struct Message *mptr; - struct Client *acptr; - struct ConfItem *aconf; - char stat = parc > 1 ? parv[1][0] : '\0'; - const char **infotext = statsinfo; - int i; - - if (hunt_stats(cptr, sptr, parc, parv, stat) != HUNTED_ISME) - return 0; - - switch (stat) - { - case 'L': - case 'l': - { - int doall = 0; - int wilds = 0; - char *name = "*"; - - if (parc > 3 && !EmptyString(parv[3])) { - name = parv[3]; - wilds = string_has_wildcards(name); - } - else - doall = 1; - /* - * Send info about connections which match, or all if the - * mask matches me.name. Only restrictions are on those who - * are invisible not being visible to 'foreigners' who use - * a wild card based search to list it. - */ - send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ " - "SendM SendKBytes RcveM RcveKBytes :Open since"); - for (i = 0; i <= HighestFd; i++) - { - if (!(acptr = LocalClientArray[i])) - continue; - /* Don't return clients when this is a request for `all' */ - if (doall && IsUser(acptr)) - continue; - /* Don't show invisible people to non opers unless they know the nick */ - if (IsInvisible(acptr) && (doall || wilds) && !IsAnOper(acptr) && (acptr != sptr)) - continue; - /* Only show the ones that match the given mask - if any */ - if (!doall && wilds && match(name, acptr->name)) - continue; - /* Skip all that do not match the specific query */ - if (!(doall || wilds) && 0 != ircd_strcmp(name, acptr->name)) - continue; - send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, - "%s %u %u %u %u %u :%Tu", (*acptr->name) ? acptr->name : "", - (int)DBufLength(&acptr->sendQ), (int)acptr->sendM, - (int)acptr->sendK, (int)acptr->receiveM, - (int)acptr->receiveK, CurrentTime - acptr->firsttime); - } - break; - } - case 'C': - case 'c': - report_configured_links(sptr, CONF_SERVER); - break; - case 'G': - case 'g': /* send glines */ - gline_stats(sptr); - break; - case 'H': - case 'h': - report_configured_links(sptr, CONF_HUB | CONF_LEAF); - break; - case 'K': - case 'k': /* display CONF_IPKILL as well as CONF_KILL -Kev */ - if (0 == report_klines(sptr, (parc == 4) ? parv[3] : 0, 0)) - return 0; - break; - case 'I': - case 'i': - { - int wilds = 0; - int count = 1000; - char* host; - - if (parc < 4) { - report_configured_links(sptr, CONF_CLIENT); - break; - } - if (EmptyString(parv[3])) - return need_more_params(sptr, "STATS I"); - - host = parv[3]; - wilds = string_has_wildcards(host); - - for (aconf = GlobalConfList; aconf; aconf = aconf->next) { - if (CONF_CLIENT == aconf->status) { - if ((!wilds && (!match(aconf->host, host) || - !match(aconf->name, host))) || - (wilds && (!mmatch(host, aconf->host) || - !mmatch(host, aconf->name)))) - { - send_reply(sptr, RPL_STATSILINE, 'I', aconf->host, aconf->name, - aconf->port, get_conf_class(aconf)); - if (--count == 0) - break; - } - } - } - break; - } - case 'M': -#if !defined(NDEBUG) - send_reply(sptr, RPL_STATMEMTOT, fda_get_byte_count(), - fda_get_block_count()); -#endif - -#if 0 -#ifdef MEMSIZESTATS - sendto_one(sptr, rpl_str(RPL_STATMEMTOT), /* XXX DEAD */ - me.name, parv[0], get_mem_size(), get_alloc_cnt()); -#endif -#ifdef MEMLEAKSTATS - report_memleak_stats(sptr, parc, parv); -#endif -#if !defined(MEMSIZESTATS) && !defined(MEMLEAKSTATS) - sendto_one(sptr, ":%s NOTICE %s :stats M : Memory allocation monitoring " /* XXX DEAD */ - "is not enabled on this server", me.name, parv[0]); -#endif -#endif /* 0 */ - break; - case 'm': - for (mptr = msgtab; mptr->cmd; mptr++) - if (mptr->count) - send_reply(sptr, RPL_STATSCOMMANDS, mptr->cmd, mptr->count, - mptr->bytes); - break; - case 'o': - case 'O': - report_configured_links(sptr, CONF_OPS); - break; - case 'p': - case 'P': - /* - * show listener ports - * show hidden ports to opers, if there are more than 3 parameters, - * interpret the fourth parameter as the port number. - */ - show_ports(sptr, 0, (parc > 3) ? atoi(parv[3]) : 0, 100); - break; - case 'R': - case 'r': -#ifdef DEBUGMODE - send_usage(sptr, parv[0]); -#endif - break; - case 'D': - report_crule_list(sptr, CRULE_ALL); - break; - case 'd': - report_crule_list(sptr, CRULE_MASK); - break; - case 't': - tstats(sptr, parv[0]); - break; - case 'T': - motd_report(sptr); - break; - case 'U': - report_configured_links(sptr, CONF_UWORLD); - break; - case 'u': - { - time_t nowr; - - nowr = CurrentTime - me.since; - send_reply(sptr, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24, - (nowr / 60) % 60, nowr % 60); - send_reply(sptr, RPL_STATSCONN, max_connection_count, max_client_count); - break; - } - case 'W': - case 'w': - calc_load(sptr); - break; - case 'X': - case 'x': -#ifdef DEBUGMODE - class_send_meminfo(sptr); - send_listinfo(sptr, parv[0]); -#endif - break; - case 'Y': - case 'y': - report_classes(sptr); - break; - case 'Z': - case 'z': - count_memory(sptr, parv[0]); - break; - default: - stat = '*'; - while (*infotext) - sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :%s", sptr, *infotext++); - break; - } - send_reply(sptr, RPL_ENDOFSTATS, stat); - return 0; -} - -/* - * ms_stats - server message handler - * - * parv[0] = sender prefix - * parv[1] = statistics selector (defaults to Message frequency) - * parv[2] = target server (current server defaulted, if omitted) - * And 'stats l' and 'stats' L: - * parv[3] = server mask ("*" defaulted, if omitted) - * Or for stats p,P: - * parv[3] = port mask (returns p-lines when its port is matched by this) - * Or for stats k,K,i and I: - * parv[3] = [user@]host.name (returns which K/I-lines match this) - * or [user@]host.mask (returns which K/I-lines are mmatched by this) - * (defaults to old reply if ommitted, when local or Oper) - * A remote mask (something containing wildcards) is only - * allowed for IRC Operators. - * Or for stats M: - * parv[3] = time param - * parv[4] = time param - * (see report_memleak_stats() in runmalloc.c for details) - * - * This function is getting really ugly. -Ghostwolf - */ -int ms_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) -{ - struct Message *mptr; - struct Client *acptr; - struct ConfItem *aconf; - char stat = parc > 1 ? parv[1][0] : '\0'; - int i; - - if (hunt_stats(cptr, sptr, parc, parv, stat) != HUNTED_ISME) - return 0; - - switch (stat) - { - case 'L': - case 'l': - { - int doall = 0; - int wilds = 0; - char *name = "*"; - - if (parc > 3 && !EmptyString(parv[3])) { - name = parv[3]; - wilds = string_has_wildcards(name); - } - else - doall = 1; - /* - * Send info about connections which match, or all if the - * mask matches me.name. Only restrictions are on those who - * are invisible not being visible to 'foreigners' who use - * a wild card based search to list it. - */ - send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ " - "SendM SendKBytes RcveM RcveKBytes :Open since"); - for (i = 0; i <= HighestFd; i++) - { - if (!(acptr = LocalClientArray[i])) - continue; - /* Don't return clients when this is a request for `all' */ - if (doall && IsUser(acptr)) - continue; - /* Don't show invisible people to unauthorized people when using - * wildcards -- Is this still needed now /stats is oper only ? - * Not here, because ms_stats is specifically a remote command, - * thus the check was removed. -Ghostwolf */ - /* Only show the ones that match the given mask - if any */ - if (!doall && wilds && match(name, acptr->name)) - continue; - /* Skip all that do not match the specific query */ - if (!(doall || wilds) && 0 != ircd_strcmp(name, acptr->name)) - continue; - send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, - "%s %u %u %u %u %u :%Tu", acptr->name, - (int)DBufLength(&acptr->sendQ), (int)acptr->sendM, - (int)acptr->sendK, (int)acptr->receiveM, - (int)acptr->receiveK, CurrentTime - acptr->firsttime); - } - break; - } - case 'C': - case 'c': - report_configured_links(sptr, CONF_SERVER); - break; - case 'G': - case 'g': /* send glines */ - gline_stats(sptr); - break; - case 'H': - case 'h': - report_configured_links(sptr, CONF_HUB | CONF_LEAF); - break; - case 'K': - case 'k': /* display CONF_IPKILL as well as CONF_KILL -Kev */ - if (0 == report_klines(sptr, (parc > 3) ? parv[3] : 0, !IsOper(sptr))) - return 0; - break; - case 'I': - case 'i': - { - int wilds = 0; - int count = 3; - char* host; - - if (parc < 4 && IsOper(sptr)) { - report_configured_links(sptr, CONF_CLIENT); - break; - } - if (parc < 4 || EmptyString(parv[3])) - return need_more_params(sptr, "STATS I"); - - if (IsOper(sptr)) { - wilds = string_has_wildcards(parv[3]); - count = 1000; - } - - host = parv[3]; - - for (aconf = GlobalConfList; aconf; aconf = aconf->next) { - if (CONF_CLIENT == aconf->status) { - if ((!wilds && (!match(aconf->host, host) || - !match(aconf->name, host))) || - (wilds && (!mmatch(host, aconf->host) || - !mmatch(host, aconf->name)))) - { - send_reply(sptr, RPL_STATSILINE, 'I', aconf->host, aconf->name, - aconf->port, get_conf_class(aconf)); - if (--count == 0) - break; - } - } - } - break; - } - case 'M': -#if !defined(NDEBUG) - send_reply(sptr, RPL_STATMEMTOT, fda_get_byte_count(), - fda_get_block_count()); -#endif - -#if 0 -#ifdef MEMSIZESTATS - sendto_one(sptr, rpl_str(RPL_STATMEMTOT), /* XXX DEAD */ - me.name, parv[0], get_mem_size(), get_alloc_cnt()); -#endif -#ifdef MEMLEAKSTATS - report_memleak_stats(sptr, parc, parv); -#endif -#if !defined(MEMSIZESTATS) && !defined(MEMLEAKSTATS) - sendto_one(sptr, ":%s NOTICE %s :stats M : Memory allocation monitoring " /* XXX DEAD */ - "is not enabled on this server", me.name, parv[0]); -#endif -#endif /* 0 */ - break; - case 'm': - for (mptr = msgtab; mptr->cmd; mptr++) - if (mptr->count) - send_reply(sptr, RPL_STATSCOMMANDS, mptr->cmd, mptr->count, - mptr->bytes); - break; - case 'o': - case 'O': - report_configured_links(sptr, CONF_OPS); - break; - case 'p': - case 'P': - /* - * show listener ports - * show hidden ports to opers, if there are more than 3 parameters, - * interpret the fourth parameter as the port number, limit non-local - * or non-oper results to 8 ports. - */ - show_ports(sptr, IsOper(sptr), (parc > 3) ? atoi(parv[3]) : 0, IsOper(sptr) ? 100 : 8); - break; - case 'R': - case 'r': -#ifdef DEBUGMODE - send_usage(sptr, parv[0]); -#endif - break; - case 'D': - report_crule_list(sptr, CRULE_ALL); - break; - case 'd': - report_crule_list(sptr, CRULE_MASK); - break; - case 't': - tstats(sptr, parv[0]); - break; - case 'T': - motd_report(sptr); - break; - case 'U': - report_configured_links(sptr, CONF_UWORLD); - break; - case 'u': - { - time_t nowr; - - nowr = CurrentTime - me.since; - send_reply(sptr, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24, - (nowr / 60) % 60, nowr % 60); - send_reply(sptr, RPL_STATSCONN, max_connection_count, max_client_count); - break; - } - case 'W': - case 'w': - calc_load(sptr); - break; - case 'X': - case 'x': -#ifdef DEBUGMODE - class_send_meminfo(sptr); - send_listinfo(sptr, parv[0]); -#endif - break; - case 'Y': - case 'y': - report_classes(sptr); - break; - case 'Z': - case 'z': - count_memory(sptr, parv[0]); - break; - default: - stat = '*'; - break; - } - send_reply(sptr, RPL_ENDOFSTATS, stat); - return 0; -} - -/* - * mo_stats - oper message handler - * - * parv[0] = sender prefix - * parv[1] = statistics selector (defaults to Message frequency) + * parv[1] = statistics selector * parv[2] = target server (current server defaulted, if omitted) * And 'stats l' and 'stats' L: - * parv[3] = server mask ("*" defaulted, if omitted) + * parv[3] = server mask ("*" default, if omitted) * Or for stats p,P: * parv[3] = port mask (returns p-lines when its port is matched by this) * Or for stats k,K,i and I: - * parv[3] = [user@]host.name (returns which K/I-lines match this) - * or [user@]host.mask (returns which K/I-lines are mmatched by this) - * (defaults to old reply if ommitted, when local or Oper) + * parv[3] = [user@]host.name (returns which K/I-lines match this) + * or [user@]host.mask (returns which K/I-lines are mmatched by this) + * (defaults to old reply if omitted, when local or Oper) * A remote mask (something containing wildcards) is only * allowed for IRC Operators. - * Or for stats M: - * parv[3] = time param - * parv[4] = time param - * (see report_memleak_stats() in runmalloc.c for details) - * - * This function is getting really ugly. -Ghostwolf */ -int mo_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) +int +m_stats(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { - struct Message* mptr; - struct Client* acptr; - struct ConfItem* aconf; - char stat = parc > 1 ? parv[1][0] : '\0'; - const char** infotext = statsinfo; - int i; - - if (hunt_stats(cptr, sptr, parc, parv, stat) != HUNTED_ISME) - return 0; - - switch (stat) - { - case 'L': - case 'l': - { - int doall = 0, wilds = 0; - char* name = "*"; - if (parc > 3 && !EmptyString(parv[3])) { - name = parv[3]; - wilds = string_has_wildcards(name); - } - else - doall = 1; - /* - * Send info about connections which match, or all if the - * mask matches me.name. Only restrictions are on those who - * are invisible not being visible to 'foreigners' who use - * a wild card based search to list it. - */ - send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, "Connection SendQ " - "SendM SendKBytes RcveM RcveKBytes :Open since"); - for (i = 0; i <= HighestFd; i++) - { - if (!(acptr = LocalClientArray[i])) - continue; - /* Don't return clients when this is a request for `all' */ - if (doall && IsUser(acptr)) - continue; - /* Only show the ones that match the given mask - if any */ - if (!doall && wilds && match(name, acptr->name)) - continue; - /* Skip all that do not match the specific query */ - if (!(doall || wilds) && 0 != ircd_strcmp(name, acptr->name)) - continue; - send_reply(sptr, SND_EXPLICIT | RPL_STATSLINKINFO, - "%s %u %u %u %u %u :%Tu", acptr->name, - (int)DBufLength(&acptr->sendQ), (int)acptr->sendM, - (int)acptr->sendK, (int)acptr->receiveM, - (int)acptr->receiveK, CurrentTime - acptr->firsttime); - } - break; - } - case 'C': - case 'c': - report_configured_links(sptr, CONF_SERVER); - break; - case 'G': - case 'g': /* send glines */ - gline_stats(sptr); - break; - case 'H': - case 'h': - report_configured_links(sptr, CONF_HUB | CONF_LEAF); - break; - case 'K': - case 'k': /* display CONF_IPKILL as well as CONF_KILL -Kev */ - if (0 == report_klines(sptr, (parc > 3) ? parv[3] : 0, 0)) - return 0; - break; - case 'I': - case 'i': - { - int wilds = 0; - int count = 1000; - char* host; - - if (parc < 4) { - report_configured_links(sptr, CONF_CLIENT); - break; - } - if (EmptyString(parv[3])) - return need_more_params(sptr, "STATS I"); - - host = parv[3]; - wilds = string_has_wildcards(host); - - for (aconf = GlobalConfList; aconf; aconf = aconf->next) { - if (CONF_CLIENT == aconf->status) { - if ((!wilds && (!match(aconf->host, host) || - !match(aconf->name, host))) || - (wilds && (!mmatch(host, aconf->host) || - !mmatch(host, aconf->name)))) - { - send_reply(sptr, RPL_STATSILINE, 'I', aconf->host, aconf->name, - aconf->port, get_conf_class(aconf)); - if (--count == 0) - break; - } - } - } - } - break; - case 'M': -#if !defined(NDEBUG) - send_reply(sptr, RPL_STATMEMTOT, fda_get_byte_count(), - fda_get_block_count()); -#endif - -#if 0 -#ifdef MEMSIZESTATS - sendto_one(sptr, rpl_str(RPL_STATMEMTOT), /* XXX DEAD */ - me.name, parv[0], get_mem_size(), get_alloc_cnt()); -#endif -#ifdef MEMLEAKSTATS - report_memleak_stats(sptr, parc, parv); -#endif -#if !defined(MEMSIZESTATS) && !defined(MEMLEAKSTATS) - sendto_one(sptr, ":%s NOTICE %s :stats M : Memory allocation monitoring " /* XXX DEAD */ - "is not enabled on this server", me.name, parv[0]); -#endif -#endif /* 0 */ - break; - case 'm': - for (mptr = msgtab; mptr->cmd; mptr++) - if (mptr->count) - send_reply(sptr, RPL_STATSCOMMANDS, mptr->cmd, mptr->count, - mptr->bytes); - break; - case 'o': - case 'O': - report_configured_links(sptr, CONF_OPS); - break; - case 'p': - case 'P': - /* - * show listener ports - * show hidden ports to opers, if there are more than 3 parameters, - * interpret the fourth parameter as the port number, limit non-local - * or non-oper results to 8 ports. - */ - show_ports(sptr, 1, (parc > 3) ? atoi(parv[3]) : 0, 100); - break; - case 'R': - case 'r': -#ifdef DEBUGMODE - send_usage(sptr, parv[0]); -#endif - break; - case 'D': - report_crule_list(sptr, CRULE_ALL); - break; - case 'd': - report_crule_list(sptr, CRULE_MASK); - break; - case 't': - tstats(sptr, parv[0]); - break; - case 'T': - motd_report(sptr); - break; - case 'U': - report_configured_links(sptr, CONF_UWORLD); - break; - case 'u': - { - time_t nowr; - - nowr = CurrentTime - me.since; - send_reply(sptr, RPL_STATSUPTIME, nowr / 86400, (nowr / 3600) % 24, - (nowr / 60) % 60, nowr % 60); - send_reply(sptr, RPL_STATSCONN, max_connection_count, max_client_count); - break; - } - case 'W': - case 'w': - calc_load(sptr); - break; - case 'X': - case 'x': -#ifdef DEBUGMODE - class_send_meminfo(sptr); - send_listinfo(sptr, parv[0]); -#endif - break; - case 'Y': - case 'y': - report_classes(sptr); - break; - case 'Z': - case 'z': - count_memory(sptr, parv[0]); - break; - default: - stat = '*'; - while (*infotext) - sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :%s", sptr, *infotext++); - break; - } - send_reply(sptr, RPL_ENDOFSTATS, stat); - return 0; + const struct StatDesc *sd; + char *param; + + /* If we didn't find a descriptor, send them help */ + if ((parc < 2) || !(sd = stats_find(parv[1]))) + parv[1] = "*", sd = stats_find("*"); + + assert(sd != 0); + + /* Check whether the client can issue this command. If source is + * not privileged (server or an operator), then the STAT_FLAG_OPERONLY + * flag must not be set, and if the STAT_FLAG_OPERFEAT flag is set, + * then the feature given by sd->sd_control must be off. + * + * This checks cptr rather than sptr so that a local oper may send + * /stats queries to other servers. + */ + if (!IsPrivileged(cptr) && + ((sd->sd_flags & STAT_FLAG_OPERONLY) || + ((sd->sd_flags & STAT_FLAG_OPERFEAT) && feature_bool(sd->sd_control)))) + return send_reply(sptr, ERR_NOPRIVILEGES); + + /* Check for extra parameter */ + if ((sd->sd_flags & STAT_FLAG_VARPARAM) && parc > 3 && !EmptyString(parv[3])) + param = parv[3]; + else + param = NULL; + + /* Ok, track down who's supposed to get this... */ + if (hunt_server_cmd(sptr, CMD_STATS, cptr, feature_int(FEAT_HIS_REMOTE), + param ? "%s %C :%s" : "%s :%C", 2, parc, parv) != + HUNTED_ISME) + return 0; /* Someone else--cool :) */ + + /* Check if they are a local user */ + if ((sd->sd_flags & STAT_FLAG_LOCONLY) && !MyUser(sptr)) + return send_reply(sptr, ERR_NOPRIVILEGES); + + assert(sd->sd_func != 0); + + /* Ok, dispatch the stats function */ + (*sd->sd_func)(sptr, sd, param); + + /* Done sending them the stats */ + return send_reply(sptr, RPL_ENDOFSTATS, parv[1]); } -