/************************************************************************
 *   IRC - Internet Relay Chat, src/m_stats.c
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Computing Center
 *
 *   See file AUTHORS in IRC package for additional names of
 *   the programmers. 
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id: m_stats.c,v 1.2 2001/02/10 19:00:43 jpinto Exp $
 */
#include "m_commands.h"  /* m_pass prototype */
#include "channel.h"
#include "class.h"       /* report_classes */
#include "client.h"      /* Client */
#include "common.h"      /* TRUE/FALSE */
#include "dline_conf.h"  /* report_dlines */
#include "irc_string.h"  /* strncpy_irc */
#include "ircd.h"        /* me */
#include "listener.h"    /* show_ports */
#include "msg.h"         /* Message */
#include "mtrie_conf.h"  /* report_mtrie_conf_links */
#include "m_gline.h"     /* report_glines */
#include "numeric.h"     /* ERR_xxx */
#include "scache.h"      /* list_scache */
#include "send.h"        /* sendto_one */
#include "s_bsd.h"       /* highest_fd */
#include "s_conf.h"      /* ConfItem, report_configured_links */
#include "s_debug.h"     /* send_usage */
#include "s_misc.h"      /* serv_info */
#include "s_serv.h"      /* hunt_server, show_servers */
#include "s_stats.h"     /* tstats */
#include "s_user.h"      /* show_opers */
#include "rhc.h"         /* rhc_stats */
#include "sgline.h"		 /* report_sglines */
#include "sqline.h"		 /* report_sqlines */
#include "svline.h"		 /* report_svlines */
#include "zline.h"		 /* report_zlines */
#include "stline.h"              /* report_stlines */

#include <string.h>

/*
 * m_functions execute protocol messages on this server:
 *
 *      cptr    is always NON-NULL, pointing to a *LOCAL* client
 *              structure (with an open socket connected!). This
 *              identifies the physical socket where the message
 *              originated (or which caused the m_function to be
 *              executed--some m_functions may call others...).
 *
 *      sptr    is the source of the message, defined by the
 *              prefix part of the message if present. If not
 *              or prefix not found, then sptr==cptr.
 *
 *              (!IsServer(cptr)) => (cptr == sptr), because
 *              prefixes are taken *only* from servers...
 *
 *              (IsServer(cptr))
 *                      (sptr == cptr) => the message didn't
 *                      have the prefix.
 *
 *                      (sptr != cptr && IsServer(sptr) means
 *                      the prefix specified servername. (?)
 *
 *                      (sptr != cptr && !IsServer(sptr) means
 *                      that message originated from a remote
 *                      user (not local).
 *
 *              combining
 *
 *              (!IsServer(sptr)) means that, sptr can safely
 *              taken as defining the target structure of the
 *              message in this server.
 *
 *      *Always* true (if 'parse' and others are working correct):
 *
 *      1)      sptr->from == cptr  (note: cptr->from == cptr)
 *
 *      2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
 *              *cannot* be a local connection, unless it's
 *              actually cptr!). [MyConnect(x) should probably
 *              be defined as (x == x->from) --msa ]
 *
 *      parc    number of variable parameter strings (if zero,
 *              parv is allowed to be NULL)
 *
 *      parv    a NULL terminated list of parameter pointers,
 *
 *                      parv[0], sender (prefix string), if not present
 *                              this points to an empty string.
 *                      parv[1]...parv[parc-1]
 *                              pointers to additional parameters
 *                      parv[parc] == NULL, *always*
 *
 *              note:   it is guaranteed that parv[0]..parv[parc-1] are all
 *                      non-NULL pointers.
 */

static int report_channel_stats(struct Client *sptr);

/*
 * m_stats - STATS message handler
 *      parv[0] = sender prefix
 *      parv[1] = statistics selector (defaults to Message frequency)
 *      parv[2] = server name (current server defaulted, if omitted)
 *
 *      Currently supported are:
 *              M = Message frequency (the old stat behaviour)
 *              L = Local Link statistics
 *              C = Report C and N configuration lines
 *
 *
 * m_stats/stats_conf
 *    Report N/C-configuration lines from this server. This could
 *    report other configuration lines too, but converting the
 *    status back to "char" is a bit akward--not worth the code
 *    it needs...
 *
 *    Note:   The info is reported in the order the server uses
 *            it--not reversed as in ircd.conf!
 */
static const char* Lformat = ":%s %d %s %s %u %u %u %u %u :%u %u %s";

int m_stats(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
{
  struct Message* mptr;
  struct Client*  acptr;
  char            l_stat = parc > 1 ? parv[1][0] : '\0';
  int             i;
  int             doall = 0;
  int             wilds = 0;
  int             ignore_request = 0;
  int             valid_stats = 0;
  char*           name;

  if(!IsClient(sptr))
    {
      return 0;
    }

  if(!IsAnOper(sptr))
    {
      sendto_one(sptr, form_str(ERR_NOPRIVILEGES), MENAME(sptr), parv[0]);
	  return -1;
    }

  if (IsSAdmin(sptr) && (hunt_server(cptr,sptr,":%s STATS %s :%s",2,parc,parv)!=HUNTED_ISME))
    return 0;

  if (parc > 2)
    {
      name = parv[2];
      if (!irccmp(name, me.name))
        doall = 2;
      else if (match(name, me.name))
        doall = 1;
      if (strchr(name, '*') || strchr(name, '?'))
        wilds = 1;
    }
  else
    name = me.name;

  switch (l_stat)
    {
    case 'L': /* Show me 'lost' users --hardman */
        if (IsAdmin(sptr)) {
        int i = 0;
         for (acptr = GlobalClientList; acptr; acptr = acptr->next) {
        if (!acptr->user)
           continue;
        if (IsService(acptr) || IsStealth(acptr) ||  IsSAdmin(acptr) ||
           IsIdentified(acptr))
           continue;
        
        if (acptr->user->channel != NULL)
           continue;

        sendto_one(sptr,":%s NOTICE %s :%s!%s@%s hasn't any channel and isn't identified.", me.name, parv[0], acptr->name, acptr->username, acptr->host);
        i++;
}
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);

        }
        break;

    case 'l' :
      /*
       * 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.
       */
      for (i = 0; i <= highest_fd; i++)
        {
          if (!(acptr = local[i]))
            continue;

			
          if (!doall && wilds && !match(name, acptr->name))
            continue;
          if (!(doall || wilds) && irccmp(name, acptr->name))
            continue;

          /* I've added a sanity test to the "CurrentTime - acptr->since"
           * occasionally, acptr->since is larger than CurrentTime.
           * The code in parse.c "randomly" increases the "since",
           * which means acptr->since is larger then CurrentTime at times,
           * this gives us very high odd number.. 
           * So, I am going to return 0 for ->since if this happens.
           * - Dianora
           */
          /* trust opers not on this server */
          /* if(IsAnOper(sptr)) */

          /* Don't trust opers not on this server */
#ifdef HIDE_SERVERS_IPS
          if(MyClient(sptr) && IsAnOper(sptr) && !IsServer(acptr) &&
	    !IsConnecting(acptr) && !IsHandshake(acptr)) 
#else
          if(MyClient(sptr) && IsAnOper(sptr))
#endif
            {
              sendto_one(sptr, Lformat, me.name,
                     RPL_STATSLINKINFO, parv[0],
                     get_client_name(acptr, MASK_IP),
                     (int)DBufLength(&acptr->sendQ),
                     (int)acptr->sendM, (int)acptr->sendK,
                     (int)acptr->receiveM, (int)acptr->receiveK,
                     CurrentTime - acptr->firsttime,
                     (CurrentTime > acptr->since) ? (CurrentTime - acptr->since):0,
                     IsServer(acptr) ? show_capabilities(acptr) : "-");
              }
            else
              {
                if(IsServer(acptr))
                  sendto_one(sptr, Lformat, me.name,
                     RPL_STATSLINKINFO, parv[0],
                     get_client_name(acptr, HIDEME),
                     (int)DBufLength(&acptr->sendQ),
                     (int)acptr->sendM, (int)acptr->sendK,
                     (int)acptr->receiveM, (int)acptr->receiveK,
                     CurrentTime - acptr->firsttime,
                     (CurrentTime > acptr->since) ? (CurrentTime - acptr->since):0,
                     IsServer(acptr) ? show_capabilities(acptr) : "-");
                 else
                  sendto_one(sptr, Lformat, me.name,
                     RPL_STATSLINKINFO, parv[0],
                     (IsUpper(lstat)) ?
                     get_client_name(acptr, MASK_IP) :
                     get_client_name(acptr, FALSE),
                     (int)DBufLength(&acptr->sendQ),
                     (int)acptr->sendM, (int)acptr->sendK,
                     (int)acptr->receiveM, (int)acptr->receiveK,
                     CurrentTime - acptr->firsttime,
                     (CurrentTime > acptr->since) ? (CurrentTime - acptr->since):0,
                     IsServer(acptr) ? show_capabilities(acptr) : "-");
              }
        }
      valid_stats++;
      break;
    case 'C' : case 'c' :
      report_configured_links(sptr, CONF_CONNECT_SERVER|CONF_NOCONNECT_SERVER);
      valid_stats++;
      break;

    case 'B' : case 'b' :
      sendto_one(sptr,":%s NOTICE %s :Use stats I instead", me.name, parv[0]);
      break;

    case 'D': case 'd':
      report_dlines(sptr);
      valid_stats++;
      break;

    case 'E' : case 'e' :
      sendto_one(sptr,":%s NOTICE %s :Use stats I instead", me.name, parv[0]);
      break;

    case 'F' : case 'f' :
      report_channel_stats(sptr);
      break;

    case 'g' :
      report_sglines(sptr);
      valid_stats++;
      break;


    case 'G' :
      report_glines(sptr, NULL);
      valid_stats++;
      break;

    case 'H' : case 'h' :
      report_configured_links(sptr, CONF_HUB|CONF_LEAF);
      valid_stats++;
      break;

    case 'I' : case 'i' :
      report_mtrie_conf_links(sptr, CONF_CLIENT);
      valid_stats++;
      break;

    case 'k' :
      report_temp_klines(sptr);
      valid_stats++;
      break;

    case 'K' :
      if(parc > 3)
        report_matching_host_klines(sptr,parv[3]);
      else
        report_mtrie_conf_links(sptr, CONF_KILL);
      valid_stats++;
      break;

    case 'M' :
      for (mptr = msgtab; mptr->cmd; mptr++)
          sendto_one(sptr, form_str(RPL_STATSCOMMANDS),
                     me.name, parv[0], mptr->cmd,
                     mptr->count, mptr->bytes);
      valid_stats++;
      break;
    case 'm' :
      if (IsSAdmin(sptr))
      report_vhostrules(sptr);
      valid_stats++;
      break;

    case 'o' : case 'O' :
      report_configured_links(sptr, CONF_OPS);
      valid_stats++;
      break;

    case 'P' :
      show_ports(sptr);
      valid_stats++;
      break;

    case 'p' :
      show_opers(sptr);
      valid_stats++;
      break;

    case 'Q' :
      report_qlines(sptr);
      valid_stats++;
      break;

    case 'q' :
      report_sqlines(sptr);
      valid_stats++;
      break;	  

    case 'R' : case 'r' :
      send_usage(sptr,parv[0]);
      valid_stats++;
      break;
    case 'S' : 
      if (IsSAdmin(sptr))
      report_spamrules(sptr);
      valid_stats++;
      break;
    case 's':
      if (IsSAdmin(sptr))
      list_scache(cptr,sptr,parc,parv);
      valid_stats++;
      break;
    case 't' :
      report_stlines(sptr);
      valid_stats++;
      break;
    case 'T' :
      tstats(sptr, parv[0]);
      valid_stats++;
      break;

    case 'U' :
      report_specials(sptr,CONF_ULINE,RPL_STATSULINE);
      valid_stats++;
      break;

    case 'u' :
      {
        time_t now;
        
        now = CurrentTime - me.since;
        sendto_one(sptr, form_str(RPL_STATSUPTIME), me.name, parv[0],
                   now/86400, (now/3600)%24, (now/60)%60, now%60);
        sendto_one(sptr, form_str(RPL_STATSCONN), me.name, parv[0],
                   MaxConnectionCount, MaxClientCount,
                   Count.totalrestartcount);
        valid_stats++;
        break;
      }

    case 'V' :
      if (IsSAdmin(sptr)) {
          show_servers(sptr);
          valid_stats++;
      }
      break;

    case 'v' :
        if (IsAdmin(sptr)) {
        int i = 0;
         for (acptr = GlobalClientList; acptr; acptr = acptr->next) {
        if (!acptr->user)
           continue;
        if (IsVipUser(acptr)) {
        sendto_one(sptr,":%s NOTICE %s :%s!%s@%s is a vip user.", me.name, parv[0], acptr->name, acptr->username, acptr->host);
        i++;
        }
        }
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);
        }
        valid_stats++;
        break;
    case 'w':
      rhc_stats(sptr);
      valid_stats++;
      break;
    case 'x' :
        report_specials(sptr,CONF_XLINE,RPL_STATSXLINE);
        valid_stats++;
      break;
	  
    case 'X' :        
		report_configured_links(sptr, CONF_MISSING);
        valid_stats++;
      break;;
	  

    case 'Y' : case 'y' :
        report_classes(sptr);
        valid_stats++;
      break;
	  
    case 'Z' :
      report_zlines(sptr);
      valid_stats++;
      break;

    case 'z' :
      count_memory(sptr, parv[0]);
      valid_stats++;
      break;

    case '?':
      if (!(IsHub) || IsSAdmin(sptr))
      serv_info(sptr, parv[0]);
      valid_stats++;
      break;

    case '!':
        if (IsSAdmin(sptr)) {
        int i = 0;
        for (acptr = GlobalClientList; acptr; acptr = acptr->next)
            if (IsAnOper(acptr) && ++i)
                sendto_one(sptr,":%s NOTICE %s :%s!%s@%s has +o mode (%s)", me.name, parv[0], acptr->name, acptr->username, acptr->realhost, acptr->user->server);
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);
        }
        break;
    case '@':
        if (IsSAdmin(sptr)) {
        int i = 0;
        for (acptr = GlobalClientList; acptr; acptr = acptr->next)
            if (IsGringo(acptr) && ++i)
                sendto_one(sptr,":%s NOTICE %s :%s!%s@%s has +E mode", me.name, parv[0], acptr->name, acptr->username, acptr->realhost);
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);
        }
        break;
    case '#': 
        if (IsSAdmin(sptr)) {
        int i = 0;
        for (acptr = GlobalClientList; acptr; acptr = acptr->next)
            if (IsStealth(acptr) && ++i)
                sendto_one(sptr,":%s NOTICE %s :%s!%s@%s has +S mode", me.name, parv[0], acptr->name, acptr->username, acptr->realhost);
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);
        }
        break;
    case '$':
        if (IsSAdmin(sptr)) {
        int i = 0;
        for (acptr = GlobalClientList; acptr; acptr = acptr->next)
            if (IsSAdmin(acptr) && ++i)
                sendto_one(sptr,":%s NOTICE %s :%s!%s@%s has +a mode", me.name, parv[0], acptr->name, acptr->username, acptr->realhost);
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);
        }
        break;
    case '%':
        if (IsSAdmin(sptr)) {
        int i = 0;
        for (acptr = GlobalClientList; acptr; acptr = acptr->next)
             if (IsVAny(acptr) && ++i)
                 sendto_one(sptr,":%s NOTICE %s :%s!%s@%s has +%s mode (%s)", me.name, parv[0], acptr->name, acptr->username, acptr->realhost, IsVCop(acptr)?"x":IsVAdmin(acptr)?"X":"NADA?", acptr->user->vs?acptr->user->vs:"not on virtual?");
        sendto_one(sptr,":%s NOTICE %s :%d total.", me.name, parv[0], i);
        }
        break;
    case '^':
        if (IsSAdmin(sptr)) {
            flush_server_connections();
        }
        break;
    default :
      l_stat = '*';
      break;
    }
  sendto_one(sptr, form_str(RPL_ENDOFSTATS), me.name, parv[0], l_stat);

  /* personally, I don't see why opers need to see stats requests
   * at all. They are just "noise" to an oper, and users can't do
   * any damage with stats requests now anyway. So, why show them?
   * -Dianora
   */

  if (valid_stats)
    {
      if ( (l_stat == 'L') || (l_stat == 'l') )
        {
          sendto_realops_flags(FLAGS_SPY,
                "STATS %c requested by %s (%s@%s) [%s] on %s%s",
                l_stat,
                sptr->name,
                sptr->username,
                sptr->host,
                MENAME(sptr),
                parc > 2 ? parv[2] : "<no recipient>",
                ignore_request > 0 ? " [request ignored]" : "\0"
                );
        }
      else
        {
          sendto_realops_flags(FLAGS_SPY,
                "STATS %c requested by %s (%s@%s) [%s]%s",
                l_stat,
                sptr->name,
                sptr->username,
                sptr->host,
                MENAME(sptr),
                ignore_request > 0 ? " [request ignored]" : "\0"
                );
        }
    }

  return 0;
}

static int report_channel_stats(struct Client *sptr)
{
  struct Channel *chptr;
  int total_opless = 0;
  int total = 0;
  int opped;

  for(chptr = channel; chptr; chptr = chptr->nextch)
  {
    struct SLink *m;
    opped = 0;
    
    for (m = chptr->members; m; m = m->next)
    {
      if(m->flags & CHFL_CHANOP)
      {
        opped++;
	break;
      }
    }

    if(!opped)
      total_opless++;

    total++;
  }

  sendto_one(sptr, ":%s NOTICE %s :Total channels: %d.  Opless channels: %d. Hacked: %d.  Ignored: %d",
             me.name, sptr->name, total, total_opless, total_hackops,
	     total_ignoreops);

  return 1;
}
