/************************************************************************
 *   IRC - Internet Relay Chat, src/m_who.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_whois.c,v 1.2 2001/01/25 21:44:12 jpinto Exp $
 */

#include "conf_vars.h"
#include "m_commands.h"
#include "client.h"
#include "channel.h"
#include "hash.h"
#include "struct.h"
#include "ircd.h"
#include "numeric.h"
#include "s_serv.h"
#include "send.h"
#include "m_silence.h"
#include "list.h"
#include "irc_string.h"
#include "stline.h"

#include <string.h>

static char buf[BUFSIZE];

/*
 * 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.
 */

/*
** m_whois
**      parv[0] = sender prefix
**      parv[1] = nickname masklist
*/
int     m_whois(struct Client *cptr,
                struct Client *sptr,
                int parc,
                char *parv[])
{
  static anUser UnknownUser =
  {
    NULL,       /* next */
    NULL,       /* channel */
    NULL,       /* invited */
	NULL,		/* silence */
    NULL,       /* away */
    0,          /* last */
    1,          /* refcount */
    0,          /* joined */
    "<Unknown>"         /* server */
  };
  static char rpl_oper[] = "an IRC Operator";
  static char rpl_sadmin[] = "an IRC Operator - Services Administrator";
  static char rpl_admin[] = "an IRC Operator - Server Administrator";   
  static char rpl_tadmin[] = "an IRC Operator - Technical Administrator";
  static char rpl_nadmin[] = "an IRC Operator - Network Administrator"; 
  
  Link  *lp;
  anUser        *user;
  struct Client *acptr, *a2cptr;
  aChannel *chptr;
  char  *nick, *name;
  /* char  *tmp; */
  char  *p = NULL;
  int   found, len, mlen;
  static time_t last_used=0L;
  char *nick_match=NULL, *user_match=NULL, *host_match=NULL, *server_match=NULL;
  int found_mode;
  int hits = 0;
  int sameVserver = 0;

  if (parc < 2)
    {
      sendto_one(sptr, form_str(ERR_NONICKNAMEGIVEN),
                 MENAME(cptr), parv[0]);
      return 0;
    }

  if(parc > 2)
    {
      if (hunt_server(cptr,sptr,":%s WHOIS %s :%s", 1,parc,parv) !=
          HUNTED_ISME)
        return 0;
      parv[1] = parv[2];
    }

  if(!IsAnOper(sptr) && !MyConnect(sptr)) /* pace non local requests */
    {
      if((last_used + WHOIS_WAIT) > CurrentTime)
        {
          /* Unfortunately, returning anything to a non local
           * request =might= increase sendq to be usable in a split hack
           * Sorry gang ;-( - Dianora
           */
          return 0;
        }
      else
        {
          last_used = CurrentTime;
        }
    }

  /* Multiple whois from remote hosts, can be used
   * to flood a server off. One could argue that multiple whois on
   * local server could remain. Lets think about that, for now
   * removing it totally. 
   * -Dianora 
   */

  /*  for (tmp = parv[1]; (nick = strtoken(&p, tmp, ",")); tmp = NULL) */
  nick = parv[1];
  p = strchr(parv[1],',');
  if(p)
    *p = '\0';

    {
      int       invis, member, wilds;
      found = 0;
      (void)collapse(nick);
      wilds = (nick[0]=='$' || strchr(nick, '?') || strchr(nick, '*'));
      /*
      ** We're no longer allowing remote users to generate
      ** requests with wildcards.
      */
      if (wilds) /* no wilds at all --hardman */
        {
          sendto_one(sptr, form_str(ERR_NOSUCHNICK),
                     MENAME(cptr), parv[0], nick);
          return 0;
        }
      /*        continue; */

      /* If the nick doesn't have any wild cards in it,
       * then just pick it up from the hash table
       * - Dianora 
       */

      if(!wilds)
        {
          acptr = hash_find_client(nick,(struct Client *)NULL);
          if (!acptr || IsServer(acptr))
            {
              sendto_one(sptr, form_str(ERR_NOSUCHNICK),
                         MENAME(cptr), parv[0], nick);

              sendto_one(sptr, form_str(RPL_ENDOFWHOIS),
                         MENAME(cptr), parv[0], parv[1]);
						 
              return 0;
              /*              continue; */
            }
          if(!IsPerson(acptr))
            {
              sendto_one(sptr, form_str(RPL_ENDOFWHOIS),
                         MENAME(cptr), parv[0], parv[1]);
              return 0;
            }
            /*      continue; */

          user = acptr->user ? acptr->user : &UnknownUser;
          name = (!*acptr->name) ? "?" : acptr->name;
          invis = IsInvisible(acptr);
          member = (user->channel) ? 1 : 0;

          a2cptr = find_server(user->server);
          
          sendto_one(sptr, form_str(RPL_WHOISUSER), MENAME(cptr),
        			parv[0], name,
                    acptr->username, acptr->host,
					(IsSAdmin(sptr) && !IsRoot(acptr)) || match(sptr->name, acptr->name) ? acptr->realhost : "*",
					acptr->info);

          mlen = strlen(MENAME(cptr)) + strlen(parv[0]) + 6 +
            strlen(name);
			
          *buf = '\0';			
		  
		  if(((!IsPrivate(acptr) || IsSAdmin(sptr)) || (acptr==sptr))
		  && !IsStealth(acptr))
          for (len = 0, *buf = '\0', lp = user->channel; lp;
               lp = lp->next)
            {
              chptr = lp->value.chptr;
              if ((PubChannel(chptr) || IsMember(sptr, chptr) || IsSAdmin(sptr)))
                {
                  if (len + strlen(chptr->chname)
                      > (size_t) BUFSIZE - 4 - mlen)
                    {
                      sendto_one(sptr,
                                 ":%s %d %s %s :%s",
                                 MENAME(cptr),
                                 RPL_WHOISCHANNELS,
                                 parv[0], name, buf);
                      *buf = '\0';
                      len = 0;
                    }

		  found_mode = user_channel_mode(acptr, chptr);
#ifdef HIDE_OPS
		  if(is_chan_op(sptr,chptr))
#endif
		    {
		      if(found_mode & CHFL_CHANOP)
			*(buf + len++) = '@';
		      else if (found_mode & CHFL_VOICE)
			*(buf + len++) = '+';
		    }
                  if (len)
                    *(buf + len) = '\0';
                  (void)strcpy(buf + len, chptr->chname);
                  len += strlen(chptr->chname);
                  if ((!PubChannel(chptr)) && (!IsMember(sptr, chptr))) {
                      (void)strcat(buf + len, "(+s)");
                      len += 4;
                  }
                  (void)strcat(buf + len, " ");
                  len++;
                }
            }
          if (buf[0] != '\0')
            sendto_one(sptr, form_str(RPL_WHOISCHANNELS),
                       MENAME(cptr), parv[0], name, buf);
         
#ifdef SERVERHIDE
          if (!(IsAnOper(sptr) || acptr == sptr))
            sendto_one(sptr, form_str(RPL_WHOISSERVER),
                       MENAME(cptr), parv[0], name, NetworkName,
                       NETWORK_DESC);
          else
#endif
          if (!(acptr->user->vs)) {
#if 0
          sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, user->server,
                     a2cptr?a2cptr->info:"*Not On This Net*");
#endif
        /* not on a vserver.. send RandomHost so --fbs
         * and if I'm SA then I'll se the real user's server :)
         */
         if (IsSAdmin(sptr)) {
          sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, RandomHost,
                     user->server);
         } else {
         sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, RandomHost,
                     NetworkDesc);
         }

         } else {   /* return VSERVER info */
         if (IsSAdmin(sptr)) {
          sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, acptr->user->vs,
                     user->server);
         } else {
          sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, acptr->user->vs,
                     acptr->user->vsd ? acptr->user->vsd : vgetdesc(acptr->user->vs));
          }
       }
          if (IsIdentified(acptr))
            sendto_one(sptr, form_str(RPL_WHOISIDENTIFIED),
                       MENAME(cptr), parv[0], name);

          if (IsHelper(acptr))
        	sendto_one(sptr, form_str(RPL_WHOISHELPOP),
                           MENAME(cptr), parv[0], name);
					   
          if (user->away)
            sendto_one(sptr, form_str(RPL_AWAY), MENAME(cptr),
                       parv[0], name, user->away);
	

  		  if (IsNetAdmin(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),   
                           MENAME(cptr), parv[0], name, rpl_nadmin);
                  else if (IsTechAdmin(acptr)) 
                  sendto_one(sptr, form_str(RPL_WHOISOPERATOR),   
                           MENAME(cptr), parv[0], name, rpl_tadmin);                                            
          else if (IsSAdmin(acptr)) { }
#if 0
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),
                           MENAME(cptr), parv[0], name, rpl_sadmin);                                            
          else if (IsAdmin(acptr) && MyClient(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),  
                           MENAME(cptr), parv[0], name, rpl_admin);
          else if (MyOper(acptr) || IsSAdmin(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR), 
                           MENAME(cptr), parv[0], name, rpl_oper); */
#endif
          else if (IsVAny(acptr)) {
                 sameVserver = (sptr->user->vs && acptr->user->vs && match(sptr->user->vs, acptr->user->vs));
                 if (IsVAdmin(acptr) && sameVserver)
                     sendto_one(sptr, form_str(RPL_WHOISOPERATOR),  
                               MENAME(cptr), parv[0], name, rpl_admin);
                 else if (IsVCop(acptr) && sameVserver)
                     sendto_one(sptr, form_str(RPL_WHOISOPERATOR), 
                               MENAME(cptr), parv[0], name, rpl_oper);
          }

#define MyVAny(x) (IsVAny((x)) && sameVserver)

#ifdef WHOIS_NOTICE
          if (
          ((acptr->umodes & FLAGS_SPY) && !is_silenced(sptr, acptr))
          && ((IsSAdmin(acptr) || IsVipUser(acptr) && !IsSAdmin(sptr))
          || (!IsSAdmin(acptr) && !IsSAdmin(sptr) && MyVAny(acptr)))
          )
            sendto_one(acptr,
                       ":%s NOTICE %s :*** Notice -- %s (%s@%s) is doing a /whois on you.",
                       MyClient(acptr)?MENAME(sptr):me.name, acptr->name, parv[0], sptr->username,
                       sptr->host);
#endif /* #ifdef WHOIS_NOTICE */


          if ((acptr->user
#ifdef SERVERHIDE
              && IsAnOper(sptr)
#endif
              && MyConnect(acptr)))
            sendto_one(sptr, form_str(RPL_WHOISIDLE),
                       MENAME(cptr), parv[0], name,
                       CurrentTime - user->last,
                       acptr->firsttime);
					   
						   
          sendto_one(sptr, form_str(RPL_ENDOFWHOIS), MENAME(cptr), parv[0], parv[1]);
          return 0;
          /*      continue; */
        }

      /* wild is true so here we go */
	  if(nick[0]=='$') /* server name match */
		server_match = &nick[1];
	  else
		{
		  host_match = strchr(nick,'@');
		  if(host_match)
			{
			  if(*host_match)
				*(host_match++) = '\0';						  
			  user_match=nick;		  		  
			  if(host_match=='\0')
				host_match="*";		  			
			  if(user_match=='\0')
			  user_match="*";		  
			}
		  else
			nick_match = nick;
		}
		
	  				
      for (acptr = GlobalClientList; acptr;
           acptr = acptr->next)
        {
          if (IsServer(acptr))
            continue;
          /*
           * I'm always last :-) and acptr->next == NULL!!
           */
          if (IsMe(acptr))
            break;
          /*
           * 'Rules' established for sending a WHOIS reply:
           *
           *
           * - if wildcards are being used dont send a reply if
           *   the querier isnt any common channels and the
           *   client in question is invisible and wildcards are
           *   in use (allow exact matches only);
           *
           * - only send replies about common or public channels
           *   the target user(s) are on;
           */

/* If its an unregistered client, ignore it, it can
   be "seen" on a /trace anyway  -Dianora */

          if(!IsRegistered(acptr))
            continue;

          user = acptr->user ? acptr->user : &UnknownUser;
          name = (!*acptr->name) ? "?" : acptr->name;
		  
		  if(  (server_match && !match(server_match, user->server))
		    || (nick_match && !match(nick, name)) 
			|| (host_match && !match(host_match, acptr->realhost))
			|| (host_match && !match(host_match, acptr->host))
			|| (user_match && !match(user_match, acptr->username))
			)
        	  continue;
			  
		  ++hits;
			  
          a2cptr = find_server(user->server);
          
          sendto_one(sptr, form_str(RPL_WHOISUSER), MENAME(cptr),
                    parv[0], name,
                    acptr->username, 
					acptr->host,
					(IsSAdmin(sptr) && !IsRoot(acptr)) || match(sptr->name, acptr->name) ? acptr->realhost : "*",
					acptr->info);
					
          found = 1;
          mlen = strlen(MENAME(cptr)) + strlen(parv[0]) + 6 +
            strlen(name);
          for (len = 0, *buf = '\0', lp = user->channel; lp;
               lp = lp->next)
            {
              chptr = lp->value.chptr;
              if (PubChannel(chptr) || IsMember(sptr, chptr) || IsSAdmin(sptr)) //don't let opers see +s channels --hardman
                {
                  if (len + strlen(chptr->chname)
                      > (size_t) BUFSIZE - 4 - mlen)
                    {
                      sendto_one(sptr,
                                 ":%s %d %s %s :%s",
                                 MENAME(cptr),
                                 RPL_WHOISCHANNELS,
                                 parv[0], name, buf);
                      *buf = '\0';
                      len = 0;
                    }
		  found_mode = user_channel_mode(acptr, chptr);
#ifdef HIDE_OPS
                  if(is_chan_op(sptr,chptr))
#endif
		     {
		       if (found_mode & CHFL_CHANOP)
			 *(buf + len++) = '@';
		       else if (found_mode & CHFL_VOICE)
			 *(buf + len++) = '+';
		     }
                  if (len)
                    *(buf + len) = '\0';
                  (void)strcpy(buf + len, chptr->chname);
                  len += strlen(chptr->chname);
                  (void)strcat(buf + len, " ");
                  len++;
                }
            }
          if (buf[0] != '\0')
            sendto_one(sptr, form_str(RPL_WHOISCHANNELS),
                       MENAME(cptr), parv[0], name, buf);
         
#ifdef SERVERHIDE
          if (!(IsAnOper(sptr) || acptr == sptr))
            sendto_one(sptr, form_str(RPL_WHOISSERVER),
                       MENAME(cptr), parv[0], name, NetworkName,
                       NETWORK_DESC);
          else    
#endif
          if (!(acptr->user->vs))
          sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, user->server,
                     a2cptr?a2cptr->info:"*Not On This Net*");
          else
          sendto_one(sptr, form_str(RPL_WHOISSERVER),
                     MENAME(cptr), parv[0], name, acptr->user->vs,
                     acptr->user->vsd ? acptr->user->vsd : "");

          if (user->away)
            sendto_one(sptr, form_str(RPL_AWAY), MENAME(cptr),
                       parv[0], name, user->away);



  		  if (IsNetAdmin(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),   
                           MENAME(cptr), parv[0], name, rpl_nadmin);
		  else if (IsTechAdmin(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),   
                           MENAME(cptr), parv[0], name, rpl_tadmin);                                            
          else if (IsSAdmin(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),   
                           MENAME(cptr), parv[0], name, rpl_sadmin);                                            
/*        else if (IsAdmin(acptr) && MyClient(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR),  
                           MENAME(cptr), parv[0], name, rpl_admin);
          else if (MyOper(acptr) || IsSAdmin(acptr))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR), 
                           MENAME(cptr), parv[0], name, rpl_oper); */
          else if ((IsVAdmin(acptr)) && acptr->user->vs && sptr->user->vs && (match(acptr->user->vs, sptr->user->vs)))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR), 
                           MENAME(cptr), parv[0], name, rpl_admin);
          else if ((IsVCop(acptr)) && acptr->user->vs && sptr->user->vs && (match(acptr->user->vs, sptr->user->vs)))
                sendto_one(sptr, form_str(RPL_WHOISOPERATOR), 
                           MENAME(cptr), parv[0], name, rpl_oper);

#ifdef WHOIS_NOTICE
          if ((MyOper(acptr)) && ((acptr)->umodes & FLAGS_SPY) &&
              (!IsSAdmin(sptr) && MyClient(sptr)))
            sendto_one(acptr,
                       ":%s NOTICE %s :*** Notice -- %s (%s@%s) is doing a /whois on you.",
                       MENAME(cptr), acptr->name, parv[0], sptr->username,
                       sptr->host);
#endif /* #ifdef WHOIS_NOTICE */

          if ((acptr->user
#ifdef SERVERHIDE
              && IsAnOper(sptr) 
#endif                 
              && MyConnect(acptr)))
            sendto_one(sptr, form_str(RPL_WHOISIDLE),
                       MENAME(cptr), parv[0], name,
                       CurrentTime - user->last,
                       acptr->firsttime);

		if(hits>50)
		  {
  			sendto_one(sptr,":%s NOTICE %s :Aborting /whois output as flood prevention",
                                MENAME(cptr), sptr->name);                     
			break;
		  }
        }
		
      if (!found)	  
        sendto_one(sptr, form_str(ERR_NOSUCHNICK),
                   MENAME(cptr), parv[0], nick);
	  else
		sendto_one(sptr,":%s NOTICE %s :This /whois matched \2%i\2 user(s)", 
                        MENAME(cptr), sptr->name,hits);
      /*
      if (p)
        p[-1] = ',';
        */
    }
  sendto_one(sptr, form_str(RPL_ENDOFWHOIS), MENAME(cptr), parv[0], parv[1]);
  
  return 0;
}
