/* m_who.c - Because s_user.c was just crazy.
 *   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 softwmare; 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_who.c,v 1.1.1.1 2005/06/27 03:02:32 sheik Exp $ */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "inet.h"
#include "msg.h"
#include "channel.h"
#include <sys/stat.h>
#include <utmp.h>
#include <fcntl.h>
#include "h.h"

/* Internally defined stuffs */
SOpts wsopts;
int build_searchopts(aClient *, int, char **);
int chk_who(aClient *, int);

/* Externally defined stuffs */
extern int user_modes[];

extern Link *find_channel_link(Link *, aChannel *);
extern unsigned int cidr_to_netmask(unsigned int);

int build_searchopts(aClient *sptr, int parc, char *parv[])
{
  static char *who_oper_help[] =
  {
      "/WHO [+|-][acghilmnstuCM] [args]",
      "Flags are specified like channel modes,",
      "The flags cghimnsu all have arguments",
      "Flags are set to a positive check by +, a negative check by -",
      "The flags work as follows:",
      "Flag a: user is away",
      "Flag c <channel>: user is on <channel>,",
      "                  no wildcards accepted",
      "Flag g <gcos/realname>: user has string <gcos> in their GCOS,",
      "                        wildcards accepted, oper only",
      "Flag h <host>: user has string <host> in their hostname,",
      "               wildcards accepted",
      "Flag i <ip>: user is from <ip>, wildcards and cidr accepted,",
      "Flag m <usermodes>: user has <usermodes> set on them",
      "Flag n <nick>: user has string <nick> in their nickname,",
      "               wildcards accepted",
      "Flag s <server>: user is on server <server>,",
      "                 wildcards not accepted",
      "Flag t <seconds>: (+t) show nicks in use for more than or equal to <seconds> seconds",
      "                  (-t) show nicks in use for less than <seconds> seconds",
      "Flag u <user>: user has string <user> in their username,",
      "               wildcards accepted",
      "Flag T <type>: user is of type <type>, where type is assigned",
      "               by services.",
      "Behavior flags:",
      "Flag C: show first visible channel user is in",
      "Flag M: check for user in channels I am a member of",
      "Flag I: always show IPs instead of hosts",
      NULL
  };

  static char *who_user_help[] =
  {
      "/WHO [+|-][achmnsuCM] [args]",
      "Flags are specified like channel modes,",
      "The flags cghimnsu all have arguments",
      "Flags are set to a positive check by +, a negative check by -",
      "The flags work as follows:",
      "Flag a: user is away",
      "Flag c <channel>: user is on <channel>,",
      "                  no wildcards accepted",
      "Flag h <host>: user has string <host> in their hostname,",
      "               wildcards accepted",
      "Flag m <usermodes>: user has <usermodes> set on them,",
      "                    only usermodes o/O/a/A will return a result",
      "Flag n <nick>: user has string <nick> in their nickname,",
      "               wildcards accepted",
      "Flag s <server>: user is on server <server>,",
      "                 wildcards not accepted",
      "Flag u <user>: user has string <user> in their username,",
      "               wildcards accepted",
      "Behavior flags:",
      "Flag C: show first visible channel user is in",
      "Flag M: check for user in channels I am a member of",
      NULL
  };

  char *flags, change=1, *s, *err;
  int args=1, i, rval;

  memset((char *)&wsopts, '\0', sizeof(SOpts));
  /* if we got no extra arguments, send them the help. yeech. */
  /* if it's /who ?, send them the help */
  if(parc < 1 || parv[0][0]=='?')
  {
      /* So we don't confuse users with flags they cannot use,
         a different /who ? output will be given to users and
         opers -srd */

      char **ptr = NULL;

      if (!IsAnOper(sptr))
       ptr = who_user_help;
      else
       ptr = who_oper_help;

      for (; *ptr; ptr++)
	  sendto_one(sptr, getreply(RPL_COMMANDSYNTAX), me.name,
		     sptr->name, *ptr);
      sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, "?","WHO");
      return 0;
  }
  /* backwards compatibility */
  else if(parv[0][0]=='0' && parv[0][1]==0)
  {
      if(parc>1 && *parv[1]=='o')
      {
	  wsopts.check_umode=1;
	  wsopts.umode_plus=1;
	  wsopts.umodes=UMODE_o;
      }
      wsopts.host_plus=1;
      wsopts.host="*";
      return 1;
  }
  /* if the first argument isn't a list of stuff */
  else if(parv[0][0]!='+' && parv[0][0]!='-')
  {
      if(parv[0][0]=='#' || parv[0][0]=='&')
      {
	  wsopts.channel=find_channel(parv[0],NullChn);
	  if(wsopts.channel==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_NOSUCHCHANNEL), me.name,
			 sptr->name, parv[0]);
	      return 0;
	  }
      }
      else
      {
	  /* If the arguement has a . in it, treat it as an
	   * address. Otherwise treat it as a nick. -Rak */
	  if (strchr(parv[0], '.'))
	  {
	      wsopts.host_plus=1;
	      wsopts.host=parv[0];
	  }
	  else
	  {
	      wsopts.nick_plus=1;
	      wsopts.nick=parv[0];
	  }
      }
      return 1;
  }
  /* now walk the list (a lot like set_mode) and set arguments
   * as appropriate. */
  flags=parv[0];
  while(*flags)
  {
      switch(*flags)
      {
      case '+':
      case '-':
	  change=(*flags=='+' ? 1 : 0);
	  break;
      case 'a':
	  if(change)
	      wsopts.away_plus=1; /* they want here people */
	  else
	      wsopts.away_plus=0;
	  wsopts.check_away=1;
	  break;
      case 'C':
	  wsopts.show_chan = change;
	  break;
      case 'M':
	  wsopts.search_chan = change;
	  break;
      case 'c':
	  if(parv[args]==NULL || !change)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
			 sptr->name, "WHO", "who");
	      return 0;
	  }
	  if(*parv[args] == '@' || *parv[args] == '+')
	  {
	      char *cname = parv[args] + 1;

              if(*parv[args] == '@')
	      {
		  wsopts.channelflags = CHFL_CHANOP;
		  if(*cname == '+')
		  {
		      wsopts.channelflags |= CHFL_VOICE;
		      cname++;
		  }
	      }
	      else
		  wsopts.channelflags = CHFL_VOICE;

	      wsopts.channel=find_channel(cname, NullChn);
	  }
	  else
	  {
	      wsopts.channelflags = 0;
	      wsopts.channel=find_channel(parv[args],NullChn);
	  }
 
	  if(wsopts.channel==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_NOSUCHCHANNEL), me.name,
			 sptr->name, parv[args]);
	      return 0;
	  }
	  wsopts.chan_plus=change;
	  args++;
	  break;
      case 'g':
          if(parv[args]==NULL)
          {
              sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
                         sptr->name, "WHO", "who");
              return 0;
          }
          else if(!IsAnOper(sptr))
          {
              sendto_one(sptr, getreply(ERR_NOPRIVILEGES), me.name, parv[0]);
              return 0;
          }
          wsopts.gcos=parv[args];
          wsopts.gcos_plus=change;
          args++;
          break;
      case 'h':
	  if(parv[args]==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
			 sptr->name, "WHO", "who");
	      return 0;
	  }
	  wsopts.host=parv[args];
	  wsopts.host_plus=change;
	  args++;
	  break;
       case 't':
          if(parv[args]==NULL || (rval = strtol(parv[args], &err, 0)) == 0 || *err != '\0')
          {
              sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
                         sptr->name, "WHO", "who");
              return 0;
          }
          else if(!IsAnOper(sptr))
          {
              sendto_one(sptr, getreply(ERR_NOPRIVILEGES), me.name, parv[0]);
              return 0;
          }
          wsopts.ts = rval;
          wsopts.ts_value = change ? 2 : 1;
          args++;
          break; 
       case 'T':
          if(parv[args]==NULL || (rval = strtol(parv[args], &err, 0)) == 0 || *err != '\0')
          {
              sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
                         sptr->name, "WHO", "who");
              return 0;
          }
          else if(!IsAnOper(sptr))
          {
              sendto_one(sptr, getreply(ERR_NOPRIVILEGES), me.name, parv[0]);
              return 0;
          }
          wsopts.client_type = rval;
          wsopts.client_type_plus = change ? 1 : 0;
          args++;
          break;
      case 'I':
          if(!IsAnOper(sptr))
          {
              sendto_one(sptr, getreply(ERR_NOPRIVILEGES), me.name, parv[0]);
              return 0;
          }
          wsopts.ip_show = change;
          break; 
      case 'i':
          if(parv[args]==NULL)
          {
              sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
                         sptr->name, "WHO", "who");
              return 0;
          }
          else if(!IsAnOper(sptr))
          {
              sendto_one(sptr, getreply(ERR_NOPRIVILEGES), me.name, parv[0]);
              return 0;
          }
          else
          {
              char *cpos;
          
              if((cpos = strchr(parv[args], '/'))) 
              {  
                  char *err;
                  unsigned int maskval, ipval;
          
                  *(cpos++) = '\0';
              
                  ipval = inet_addr(parv[args]);
                  maskval = strtol(cpos, &err, 10);
                  if(ipval == 0xFFFFFFFF || *err != '\0' ||
                     maskval < 1 || maskval > 32)
                  {
                      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name, sptr->name,
                         "WHO", "who");
                      return 0;
                  }
              
                  maskval = htonl(cidr_to_netmask(maskval));
                  ipval &= maskval;
           
                  wsopts.cidr4_plus = change;
                  wsopts.cidr4_mask = maskval;
                  wsopts.cidr4_ip = ipval;
                  args++;
              }
              else
              {
                  wsopts.ip=parv[args];
                  wsopts.ip_plus=change;
                  args++;
              }
          }
          break;
      case 'm':
	  if(parv[args]==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
			 sptr->name, "WHO", "who");
	      return 0;
	  }
	  s=parv[args];
	  while(*s)
	  {
	      for(i=1;user_modes[i]!=0x0;i+=2)
	      {
		  if(*s==(char)user_modes[i])
		  {
		      wsopts.umodes|=user_modes[i-1];
		      break;
		  }
	      }
	      s++;
	  }
	  if(!IsAnOper(sptr)) /* only let users search for +/-oOaA */
	      wsopts.umodes=(wsopts.umodes&(UMODE_o|UMODE_O|UMODE_a|UMODE_A));
	  wsopts.umode_plus=change;
	  if(wsopts.umodes)
	      wsopts.check_umode=1;
	  args++;
	  break;
      case 'n':
	  if(parv[args]==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
			 sptr->name, "WHO", "who");
	      return 0;
	  }
	  wsopts.nick=parv[args];
	  wsopts.nick_plus=change;
	  args++;
	  break;
      case 's':
	  if(parv[args]==NULL || !change)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
			 sptr->name, "WHO", "who");
	      return 0;
	  }
	  wsopts.server=find_server(parv[args],NULL);
	  if(wsopts.server==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_NOSUCHSERVER), me.name,
			 sptr->name, parv[args]);
	      return 0;
	  }
	  wsopts.serv_plus=change;
	  args++;
	  break;
      case 'u':
	  if(parv[args]==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
			 sptr->name, "WHO", "who");
	      return 0;
	  }
	  wsopts.user=parv[args];
	  wsopts.user_plus=change;
	  args++;
	  break;
      default:
	  sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name,
		     sptr->name, "WHO", "who");
	  return 0;

      }
      flags++;
  }
  
  /* if we specified search_chan, we _must_ specify something useful 
   * to go with it. Specifying a channel makes no sense, and no params make no 
   * sense either, as does specifying a nick.
   */
  
  if(wsopts.search_chan && !(wsopts.check_away || wsopts.gcos_plus || 
			     wsopts.host_plus || wsopts.check_umode || 
			     wsopts.serv_plus || wsopts.nick_plus || 
			     wsopts.user_plus || wsopts.ts_value || 
                 wsopts.client_type_plus || wsopts.ip_plus))
  {
      if(parv[args]==NULL || wsopts.channel || wsopts.nick ||
	 parv[args][0] == '#' || parv[args][0] == '&')
      {
	  sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name, sptr->name, "WHO",
                 "who");
	  return 0;
      }

      if (strchr(parv[args], '.'))
      {
	  wsopts.host_plus=1;
	  wsopts.host=parv[args];
      }
      else
      {
	  sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name, sptr->name, "WHO",
                 "who");
	  return 0;
      }
  } 
  else /* can't show_chan if nothing else is set! */
      if(wsopts.show_chan && !(wsopts.check_away || wsopts.gcos_plus || 
			       wsopts.host_plus || wsopts.check_umode || 
			       wsopts.serv_plus || wsopts.nick_plus || 
			       wsopts.user_plus || wsopts.ts_value || 
                   wsopts.client_type_plus || wsopts.ip_plus || 
                   wsopts.chan_plus || wsopts.cidr4_mask))
      {
	  if(parv[args]==NULL)
	  {
	      sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name, sptr->name, "WHO",
                     "who");
	      return 0;
	  }
	  
	  if (strchr(parv[args], '.'))
	  {
	      wsopts.host_plus=1;
	      wsopts.host=parv[args];
	  }
	  else
	  {
	      wsopts.nick_plus=1;
	      wsopts.nick=parv[args];
	  }
      }

  /* hey cool, it all worked! */
  return 1;
}

/* these four are used by chk_who to check gcos/nick/user/host
 * respectively 
 * as well as ip -srd */

int (*gchkfn)(char *, char *);
int (*nchkfn)(char *, char *);
int (*uchkfn)(char *, char *);
int (*hchkfn)(char *, char *);
int (*ichkfn)(char *, char *);

int chk_who(aClient *ac, int showall)
{
    if(!IsClient(ac))
	return 0;
    if(IsInvisible(ac) && !showall)
	return 0;

    if(wsopts.client_type_plus &&
	wsopts.client_type != ac->user->servicetype)
	return 0;

    if(wsopts.check_umode)
	if((wsopts.umode_plus && 
	    !((ac->umode&wsopts.umodes)==wsopts.umodes)) ||
	   (!wsopts.umode_plus && ((ac->umode&wsopts.umodes)==wsopts.umodes)))
	    return 0;

    if(wsopts.check_away)
	if((wsopts.away_plus && ac->user->away==NULL) ||
	   (!wsopts.away_plus && ac->user->away!=NULL))
	    return 0;

    /* while this is wasteful now, in the future
     * when clients contain pointers to their servers
     * of origin, this'll become a 4 byte check instead of a mycmp
     * -wd */
    /* welcome to the future... :) - lucas */
    if(wsopts.serv_plus)
    {
	if(wsopts.server != ac->uplink)
	    return 0;
	/* don't let people find hidden opers via /who +s server */
	if(IsUmodeW(ac) && !showall)
	    return 0;
    }
    /* we only call match once, since if the first condition
     * isn't true, most (all?) compilers will never try the
     * second...phew :) */
    if(wsopts.user!=NULL)
	if((wsopts.user_plus && uchkfn(wsopts.user, ac->user->username)) ||
	   (!wsopts.user_plus && !uchkfn(wsopts.user, ac->user->username)))
	    return 0;

    if(wsopts.nick!=NULL)
	if((wsopts.nick_plus && nchkfn(wsopts.nick, ac->name)) ||
	   (!wsopts.nick_plus && !nchkfn(wsopts.nick, ac->name)))
	    return 0;
    
    if(wsopts.host!=NULL)
	if((wsopts.host_plus && hchkfn(wsopts.host, ac->user->host)) ||
	   (!wsopts.host_plus && !hchkfn(wsopts.host, ac->user->host)))
	    return 0;

    if(wsopts.cidr4_plus)
	if((ac->ip.s_addr & wsopts.cidr4_mask) != wsopts.cidr4_ip)
	    return 0;
    
    if(wsopts.ip_plus)
	if(ichkfn(wsopts.ip, ac->hostip))
	    return 0;
    
    if(wsopts.gcos!=NULL)
	if((wsopts.gcos_plus && gchkfn(wsopts.gcos, ac->info)) ||
	   (!wsopts.gcos_plus && !gchkfn(wsopts.gcos, ac->info)))
	    return 0;

    /*
     * For the below options, a value of two means '+', 
     * a value of 1 means '-', and a value of 0 means
     * not speficied. 
     */

    if(wsopts.ts_value == 2 && /* +t */
        NOW - ac->tsinfo < wsopts.ts)
        return 0;
    else if(wsopts.ts_value == 1 && /* -t */
        NOW - ac->tsinfo >= wsopts.ts)
        return 0;

    return 1;
}

inline char *first_visible_channel(aClient *cptr, aClient *sptr)
{
    Link *lp;
    int secret = 0;
    aChannel *chptr = NULL;
    static char chnbuf[CHANNELLEN + 2];

    if(cptr->user->channel)
    {
	if(IsAdmin(sptr))
	{
	    chptr = cptr->user->channel->value.chptr;
	    if(!(ShowChannel(sptr, chptr)))
		secret = 1;
	}
	else
	{
	    for(lp = cptr->user->channel; lp; lp = lp->next)
	    {
		if(ShowChannel(sptr, lp->value.chptr))
		    break;
	    }
	    if(lp)
		chptr = lp->value.chptr;
	}

	if(chptr)
	{
	    if(!secret)
		return chptr->chname;
	    ircsprintf(chnbuf, "%%%s", chptr->chname);
	    return chnbuf;
	}
    }
    return "*";
}

/* allow lusers only 200 replies from /who */
#define MAXWHOREPLIES 200
#define WHO_HOPCOUNT(s, a) ( ( (IsULine((a)) || IsUmodeW((a))) && !IsAnOper((s)) ) ? 0 : a->hopcount)
#define WHO_SERVER(s ,a) ((IsUmodeW((a)) && !IsAnOper((s))) ? HiddenServName : a->user->server)
#define WHO_HOST(a) ((wsopts.ip_show) ? (a)->hostip : (a)->user->host)
#define WHO_REALHOST(a) ((wsopts.ip_show) ? (a)->hostip : (a)->user->realhost)
int m_who(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    aClient *ac;
    chanMember *cm;
    Link *lp;
    int shown=0, i=0, showall=IsAnOper(sptr);
    char status[4];

    /* drop nonlocal clients */
    if(!MyClient(sptr))
	return 0;
    
    if(!build_searchopts(sptr, parc-1, parv+1))
	return 0; /* /who was no good */
    
    if(wsopts.gcos!=NULL && (strchr(wsopts.gcos, '?'))==NULL &&
       (strchr(wsopts.gcos, '*'))==NULL)
	gchkfn=mycmp;
    else
	gchkfn=match;
    if(wsopts.nick!=NULL && (strchr(wsopts.nick, '?'))==NULL &&
       (strchr(wsopts.nick, '*'))==NULL)
	nchkfn=mycmp;
    else
	nchkfn=match;
    if(wsopts.user!=NULL && (strchr(wsopts.user, '?'))==NULL &&
       (strchr(wsopts.user, '*'))==NULL)
	uchkfn=mycmp;
    else
	uchkfn=match;
    if(wsopts.host!=NULL && (strchr(wsopts.host, '?'))==NULL &&
       (strchr(wsopts.host, '*'))==NULL)
	hchkfn=mycmp;
    else
	hchkfn=match;

    if(wsopts.ip!=NULL && (strchr(wsopts.ip, '?'))==NULL &&
       (strchr(wsopts.ip, '*'))==NULL)
	ichkfn=mycmp;
    else
	ichkfn=match;

    if(wsopts.channel!=NULL)
    {
	if(IsMember(sptr,wsopts.channel))
	    showall=1;
	else if(SecretChannel(wsopts.channel) && IsAdmin(sptr))
	    showall=1;
	else if(!SecretChannel(wsopts.channel) && IsAnOper(sptr))
	    showall=1;
	else
	    showall=0;
	if(showall || !SecretChannel(wsopts.channel))
	{
	    for(cm=wsopts.channel->members; cm; cm=cm->next)
	    {
		ac=cm->cptr;
		i=0;
		if(!chk_who(ac,showall))
		    continue;
		/* If we have channel flags set, verify they match */
		if(wsopts.channelflags && ((cm->flags & wsopts.channelflags) == 0))
		    continue;
		/* get rid of the pidly stuff first */
		/* wow, they passed it all, give them the reply...
		 * IF they haven't reached the max, or they're an oper */
		status[i++]=(ac->user->away==NULL ? 'H' : 'G');
		status[i]=(IsAnOper(ac) ? '*' : ((IsInvisible(ac) &&
						  IsOper(sptr)) ? '%' : 0));
		status[((status[i]) ? ++i : i)]=((cm->flags&CHFL_CHANOP) ? '@'
						 : ((cm->flags&CHFL_VOICE) ? 
						    '+' : 0));
		status[++i]=0;
		sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name,
			   wsopts.channel->chname, ac->user->username,
			   WHO_HOST(ac), WHO_SERVER(sptr, ac), ac->name, status,
			   WHO_HOPCOUNT(sptr, ac),
			   ac->info);
	    }
	}
	sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name,
		   wsopts.channel->chname, "WHO");
	return 0;
    }
    /* if (for whatever reason) they gave us a nick with no
     * wildcards, just do a find_person, bewm! */
    else if(nchkfn==mycmp)
    {
	ac=find_person(wsopts.nick,NULL);
	if(ac!=NULL)
	{
	    if(!chk_who(ac,1))
	    {
		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name,
			   wsopts.host!=NULL ? wsopts.host : wsopts.nick, "WHO");
		return 0;
	    }
	    else
	    {
		status[0]=(ac->user->away==NULL ? 'H' : 'G');
		status[1]=(IsAnOper(ac) ? '*' : (IsInvisible(ac) &&
						 IsAnOper(sptr) ? '%' : 0));
		status[2]=0;
		sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name,
			   wsopts.show_chan ? first_visible_channel(ac, sptr)
			    : "*", ac->user->username, IsAnOper(sptr) ? WHO_REALHOST(ac) : WHO_HOST(ac),
			   WHO_SERVER(sptr, ac), ac->name, status,
			   WHO_HOPCOUNT(sptr, ac),
			   ac->info);
		sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name,
			   wsopts.host!=NULL ? wsopts.host : wsopts.nick, "WHO");
		return 0;
	    }
	}
	sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name,
		   wsopts.host!=NULL ? wsopts.host : wsopts.nick, "WHO");
	return 0;
    }
    
    if(wsopts.search_chan)
    {
	for(lp = sptr->user->channel; lp; lp = lp->next)
	{
	    for(cm = lp->value.chptr->members; cm; cm = cm->next)
	    {
		ac = cm->cptr;
		if(!chk_who(ac, 1))
		    continue;
		
		if(shown==MAXWHOREPLIES && !IsAnOper(sptr))
		{
		    sendto_one(sptr, getreply(ERR_WHOLIMEXCEED), me.name,
			       sptr->name, MAXWHOREPLIES, "WHO");
		    break;
		}
		
		i = 0;
		status[i++]=(ac->user->away==NULL ? 'H' : 'G');
		status[i]=(IsAnOper(ac) ? '*' : ((IsInvisible(ac) &&
						  IsOper(sptr)) ? '%' : 0));
		status[((status[i]) ? ++i : i)]=((cm->flags&CHFL_CHANOP) ? 
						 '@' : ((cm->flags&CHFL_VOICE)
							? '+' : 0));
		status[++i]=0;
		sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name,
			   lp->value.chptr->chname, ac->user->username,
			    IsAnOper(sptr) ? WHO_REALHOST(ac) : WHO_HOST(ac),WHO_SERVER(sptr, ac), ac->name,
			   status, WHO_HOPCOUNT(sptr, ac), ac->info);
		shown++;
	    }
	}
    }
    else
    {
	for(ac=client;ac;ac=ac->next)
	{
	    if(!chk_who(ac,showall))
		continue;
	    /* wow, they passed it all, give them the reply...
	     * IF they haven't reached the max, or they're an oper */
	    if(shown==MAXWHOREPLIES && !IsAnOper(sptr))
	    {
		sendto_one(sptr, getreply(ERR_WHOLIMEXCEED), me.name, 
			   sptr->name, MAXWHOREPLIES, "WHO");
		break; /* break out of loop so we can send end of who */
	    }
	    status[0]=(ac->user->away==NULL ? 'H' : 'G');
	    status[1]=(IsAnOper(ac) ? '*' : (IsInvisible(ac) && 
					     IsAnOper(sptr) ? '%' : 0));
	    status[2]=0;
	    sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name,
		       wsopts.show_chan ? first_visible_channel(ac, sptr) :
		      "*", ac->user->username, IsAnOper(sptr) ? WHO_REALHOST(ac) : WHO_HOST(ac),
		       WHO_SERVER(sptr, ac), ac->name, status,
		       WHO_HOPCOUNT(sptr, ac), ac->info);
	    shown++;
	}
    }
    sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name,
	       (wsopts.host!=NULL ? wsopts.host :
		(wsopts.nick!=NULL ? wsopts.nick :
		 (wsopts.user!=NULL ? wsopts.user :
		  (wsopts.gcos!=NULL ? wsopts.gcos :
		   (wsopts.server!=NULL ? wsopts.server->name :
		    "*"))))), "WHO");
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1