/************************************************************************
 *   IRC - Internet Relay Chat, modules/m_who.c
 *
 *   Copyright (C) 2000-2003 TR-IRCD Development
 *
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Co 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 2, 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.
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include "s_conf.h"
#include "language.h"
#include "usermode.h"
#include "h.h"

static struct Message _msgtab[] = {
    {MSG_WHO, 0, MAXPARA, M_SLOW, 0L,
     m_unregistered, m_who, m_who, m_ignore, m_ignore}
};

#ifndef STATIC_MODULES

char *_version = "$Revision: 1.4 $";

void _modinit(void)
{
    mod_add_cmd(_msgtab);
}

void _moddeinit(void)
{
    mod_del_cmd(_msgtab);
}
#else
void m_who_init(void)
{
    mod_add_cmd(_msgtab);
}
#endif

    /* We do not need link traversal here, because users
     * do not exist in the root channel, and it is already
     * set to +tnL, after cleaning. -TimeMr14C
     */

SOpts wsopts;
int build_searchopts(aClient *, int, char **);
int chk_who(aClient *, int);

int build_searchopts(aClient *sptr, int parc, char *parv[])
{
    static char *who_help[] = {
	"/WHO [+|-][acghmnsu] [args]",
	"Flags are specified like channel modes, the flags cgmnsu 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 accepted,",
	"Flag f <fakehost>:       user has string <fakehost> in their",
	"                         hostname output.",
	"Flag m <usermodes>:      user has <usermodes> set on them,",
	"                         only o/A/a for nonopers",
	"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",
	"Flag L <name>:           user has string <name> as their language,",
	NULL
    };
    char *flags, change = 1, *s;
    int args = 1;

    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] == '?') {
    char **ptr = who_help;

	for (; *ptr; ptr++)
	    send_me_numeric(sptr, RPL_COMMANDSYNTAX, *ptr);
	send_me_numeric(sptr, RPL_ENDOFWHO, "?");
	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]);
	    if (wsopts.channel == NULL) {
		send_me_numeric(sptr, ERR_NOSUCHCHANNEL, 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];

    wsopts.langnum = 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':
		if (parv[args] == NULL || !change) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.channel = find_channel(parv[args]);
		if (wsopts.channel == NULL) {
		    send_me_numeric(sptr, ERR_NOSUCHCHANNEL, parv[args]);
		    return 0;
		}
		wsopts.chan_plus = change;
		args++;
		break;
	    case 'f':
		if (parv[args] == NULL) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.fakehost = parv[args];
		wsopts.fhost_plus = change;
		args++;
		break;
	    case 'g':
		if (parv[args] == NULL || !IsAnOper(sptr)) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.gcos = parv[args];
		wsopts.gcos_plus = change;
		args++;
		break;
	    case 'h':
		if (parv[args] == NULL) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.host = parv[args];
		wsopts.host_plus = change;
		args++;
		break;
	    case 'i':
		if (parv[args] == NULL || !IsAnOper(sptr)) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.ip = parv[args];
		wsopts.ip_plus = change;
		args++;
		break;
	    case 'L':
		if (parv[args] == NULL) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.lang = parv[args];
		wsopts.langnum = lang_parse(parv[args]);
		wsopts.lang_plus = change;
		args++;
		break;
	    case 'm':
		if (parv[args] == NULL) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		s = parv[args];
		while (*s) {
		    if (umodetab[(int) *s].in_use)
			wsopts.umodes |= umodetab[(int) *s].type;
		    s++;
		}
		if (!IsAnOper(sptr))
		    wsopts.umodes = (wsopts.umodes & (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) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.nick = parv[args];
		wsopts.nick_plus = change;
		args++;
		break;
	    case 's':
		if (parv[args] == NULL || !change) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.server = find_server(parv[args]);
		if (wsopts.server == NULL) {
		    send_me_numeric(sptr, ERR_NOSUCHSERVER, parv[args]);
		    return 0;
		}
		wsopts.serv_plus = change;
		args++;
		break;
	    case 'u':
		if (parv[args] == NULL) {
		    send_me_numeric(sptr, ERR_WHOSYNTAX);
		    return 0;
		}
		wsopts.user = parv[args];
		wsopts.user_plus = change;
		args++;
		break;
	}
	flags++;
    }

    return 1;
}

/*
 * these four are used by chk_who to check gcos/nick/user/host/ip/fakehost
 * respectively 
 */

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

int chk_who(aClient *ac, int showall)
{
    if (!IsPerson(ac))
	return 0;
    if (IsInvisible(ac) && !showall)
	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 irc_strcmp
     * -wd 
     *
     * welcome to the future... :) 
     * -lucas 
     */
    if (wsopts.serv_plus)
	if (wsopts.server != ac->servptr)
	    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.fakehost != NULL)
	if ((wsopts.fhost_plus && fchkfn(wsopts.fakehost, ac->user->fakehost))
	    || (!wsopts.fhost_plus && !fchkfn(wsopts.fakehost, ac->user->fakehost)))
	    return 0;

    if (wsopts.ip != NULL)
	if ((wsopts.ip_plus && ichkfn(wsopts.ip, ac->hostip)) ||
	    (!wsopts.ip_plus && !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;
    if (wsopts.langnum)
	if ((wsopts.lang_plus && lchkfn(wsopts.langnum, ac->lang)) ||
	    (!wsopts.lang_plus && !lchkfn(wsopts.langnum, ac->lang)))
	    return 0;

    return 1;
}

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

    if (cptr->user->channel.head) {
	if (IsSeeHidden(sptr)) {
	    chptr = cptr->user->channel.head->data;
	    if (!(ShowChannelNames(sptr, chptr)))
		secret = 1;
	} else {
	    for (lp = cptr->user->channel.head; lp; lp = lp->next) {
		if (ShowChannelNames(sptr, (aChannel *) lp->data))
		    break;
	    }
	    if (lp)
		chptr = lp->data;
	}
	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)) && !IsAnOper((s))) ? 0 : a->hopcount)

int m_who(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    struct ChanMember *cm;
    aClient *ac;
    dlink_node *ptr;
    int shown = 0, i = 0, showall = IsSeeHidden(sptr);
    char out[HOSTLEN];
    char status[4];

    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 = irc_strcmp;
    else
	gchkfn = match;
    if (wsopts.nick != NULL && (strchr(wsopts.nick, '?')) == NULL &&
	(strchr(wsopts.nick, '*')) == NULL)
	nchkfn = irc_strcmp;
    else
	nchkfn = match;
    if (wsopts.user != NULL && (strchr(wsopts.user, '?')) == NULL &&
	(strchr(wsopts.user, '*')) == NULL)
	uchkfn = irc_strcmp;
    else
	uchkfn = match;
    if (wsopts.host != NULL && (strchr(wsopts.host, '?')) == NULL &&
	(strchr(wsopts.host, '*')) == NULL)
	hchkfn = irc_strcmp;
    else
	hchkfn = match;

    if (wsopts.fakehost != NULL && (strchr(wsopts.fakehost, '?')) == NULL &&
	(strchr(wsopts.fakehost, '*')) == NULL)
	fchkfn = irc_strcmp;
    else
	fchkfn = match;

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

    if (wsopts.langnum)
	lchkfn = irc_equal;

    if (wsopts.channel != NULL) {
	if (IsMember(sptr, wsopts.channel))
	    showall = 1;
	else if (SecretChannel(wsopts.channel) && IsSeeHidden(sptr))
	    showall = 1;
	else if (NamesChannel(wsopts.channel) && IsSeeHidden(sptr))
	    showall = 1;
	else if (!SecretChannel(wsopts.channel) && !NamesChannel(wsopts.channel) && IsAnOper(sptr))
	    showall = 1;
	else
	    showall = 0;
	if (showall || !SecretChannel(wsopts.channel) || !NamesChannel(wsopts.channel)) {
	    for (ptr = wsopts.channel->members.head; ptr; ptr = ptr->next) {
		cm = ptr->data;
		if (!cm)
		   continue;
		ac = cm->client_p;
		i = 0;
		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 
		 */
		status[i++] = (ac->user->away == NULL ? 'H' : 'G');
		status[i] = (IsAnOper(ac) ? '*' : ((IsInvisible(ac) && IsAnOper(sptr)) ? '%' : 0));
		if (!IsChanHideOps(wsopts.channel) || IsSeeHidden(sptr)) {
		    status[((status[i]) ? ++i : i)] =
			(IsChanUser(ac, wsopts.channel, CHFL_CHANOP)  ? '@' :
			 IsChanUser(ac, wsopts.channel, CHFL_VOICE) ? '+' : 0);
		}
		status[++i] = 0;
		send_me_numeric(sptr, RPL_WHOREPLY, wsopts.channel->chname, ac->user->username,
				IsFake(ac) ? ac->user->fakehost : ac->user->host,
				((WillHideName(ac->from) || (MyClient(ac) && ServerHide.enable)) ?
				 stealth_server(ac->user->server, out) : ac->user->server),
				ac->name, status, WHO_HOPCOUNT(sptr, ac), ac->info);
	    }
	}
	send_me_numeric(sptr, RPL_ENDOFWHO, wsopts.channel->chname);
	return 0;
    }
    /*
     * if (for whatever reason) they gave us a nick with no
     * wildcards, just do a find_person, bewm! 
     */
    else if (nchkfn == irc_strcmp) {
	ac = find_person(wsopts.nick);
	if (ac != NULL) {
	    if (!chk_who(ac, 1)) {
		send_me_numeric(sptr, RPL_ENDOFWHO,
				wsopts.host != NULL ? wsopts.host : wsopts.nick);
		return 0;
	    } else {
		status[0] = (ac->user->away == NULL ? 'H' : 'G');
		status[1] = (IsAnOper(ac) ? '*' : ((IsInvisible(ac) && IsAnOper(sptr)) ? '%' : 0));
		status[2] = 0;
		send_me_numeric(sptr, RPL_WHOREPLY,
				(!IsWhoisHideChan(ac)) ?
				first_visible_channel(ac, sptr) : "*", ac->user->username,
				IsFake(ac) ? ac->user->fakehost : ac->user->host,
				((WillHideName(ac->from) || (MyClient(ac) && ServerHide.enable)) ?
				 stealth_server(ac->user->server, out) : ac->user->server),
				ac->name, status, WHO_HOPCOUNT(sptr, ac), ac->info);
		send_me_numeric(sptr, RPL_ENDOFWHO,
				wsopts.host != NULL ? wsopts.host : wsopts.nick);
		return 0;
	    }
	}
	send_me_numeric(sptr, RPL_ENDOFWHO, wsopts.host != NULL ? wsopts.host : wsopts.nick);
	return 0;
    }

    for (ac = GlobalClientList; 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)) {
	    send_me_numeric(sptr, ERR_WHOLIMEXCEED, MAXWHOREPLIES);
	    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;
	send_me_numeric(sptr, RPL_WHOREPLY,
			(!IsWhoisHideChan(ac)) ? first_visible_channel(ac,
									 sptr) :
			"*", ac->user->username,
			IsFake(ac) ? ac->user->fakehost : ac->user->host,
			((WillHideName(ac->from) || (MyClient(ac) && ServerHide.enable)) ?
			 stealth_server(ac->user->server, out) : ac->user->server),
			ac->name, status, WHO_HOPCOUNT(sptr, ac), ac->info);
	shown++;
    }
    send_me_numeric(sptr, RPL_ENDOFWHO,
		    (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 :
			 (wsopts.fakehost != NULL ? wsopts.fakehost :
			  (wsopts.langnum ? wsopts.lang : "*"))))))));
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1