1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-08 03:43:13 +02:00
Files
unrealircd/src/modules/whox.c
T
Bram Matthys b19b70e876 Speed up invisibility checks for delayjoin mode (and when not used too).
This adds user_can_see_member_fast() which is used in at least 3 places
now, more places may follow later. It has extra paramters for membership
and membership modes that is very likely already looked up by the caller
(or if not, it is worth doing so by the caller).

This is work in progress so if everything crashes or people mysteriously
seem not present in channels (or the other way around) i would not be
surprised :D.
2023-05-15 16:58:51 +02:00

981 lines
26 KiB
C

/* cmd_whox.c / WHOX.
* based on code from charybdis and ircu.
* was originally made for tircd and modified to work with u4.
* - 2018 i <ircd@servx.org>
* - 2019 Bram Matthys (Syzop) <syzop@vulnscan.org>
* License: GPLv2 or later
*/
#include "unrealircd.h"
/* Module header */
ModuleHeader MOD_HEADER
= {
"whox",
"5.0",
"command /who",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Defines */
#define FIELD_CHANNEL 0x0001
#define FIELD_HOP 0x0002
#define FIELD_FLAGS 0x0004
#define FIELD_HOST 0x0008
#define FIELD_IP 0x0010
#define FIELD_IDLE 0x0020
#define FIELD_NICK 0x0040
#define FIELD_INFO 0x0080
#define FIELD_SERVER 0x0100
#define FIELD_QUERYTYPE 0x0200 /* cookie for client */
#define FIELD_USER 0x0400
#define FIELD_ACCOUNT 0x0800
#define FIELD_OPLEVEL 0x1000 /* meaningless and stupid, but whatever */
#define FIELD_REALHOST 0x2000
#define FIELD_MODES 0x4000
#define FIELD_REPUTATION 0x8000
#define WMATCH_NICK 0x0001
#define WMATCH_USER 0x0002
#define WMATCH_OPER 0x0004
#define WMATCH_HOST 0x0008
#define WMATCH_INFO 0x0010
#define WMATCH_SERVER 0x0020
#define WMATCH_ACCOUNT 0x0040
#define WMATCH_IP 0x0080
#define WMATCH_MODES 0x0100
#define WMATCH_CONTIME 0x0200
#define RPL_WHOSPCRPL 354
#define WHO_ADD 1
#define WHO_DEL 0
#define HasField(x, y) ((x)->fields & (y))
#define IsMatch(x, y) ((x)->matchsel & (y))
#define IsMarked(x) (moddata_client(x, whox_md).l)
#define SetMark(x) do { moddata_client(x, whox_md).l = 1; } while(0)
#define ClearMark(x) do { moddata_client(x, whox_md).l = 0; } while(0)
/* Structs */
struct who_format
{
int fields;
int matchsel;
int umodes;
int noumodes;
const char *querytype;
int show_realhost;
int show_ip;
time_t contimemin;
time_t contimemax;
};
/* Global variables */
ModDataInfo *whox_md = NULL;
/* Forward declarations */
CMD_FUNC(cmd_whox);
static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt);
static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt);
static void do_who_on_channel(Client *client, Channel *channel,
int member, int operspy, struct who_format *fmt);
static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt);
const char *whox_md_serialize(ModData *m);
void whox_md_unserialize(const char *str, ModData *m);
void whox_md_free(ModData *md);
static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...) __attribute__((format(printf,4,5)));
MOD_INIT()
{
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
if (!CommandAdd(modinfo->handle, "WHO", cmd_whox, MAXPARA, CMD_USER))
{
config_warn("You cannot load both cmd_whox and cmd_who. You should ONLY load the cmd_whox module.");
return MOD_FAILED;
}
memset(&mreq, 0, sizeof(mreq));
mreq.name = "whox";
mreq.type = MODDATATYPE_CLIENT;
mreq.serialize = whox_md_serialize;
mreq.unserialize = whox_md_unserialize;
mreq.free = whox_md_free;
mreq.sync = 0;
whox_md = ModDataAdd(modinfo->handle, mreq);
if (!whox_md)
{
config_error("could not register whox moddata");
return MOD_FAILED;
}
ISupportAdd(modinfo->handle, "WHOX", NULL);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
/** whox module data operations: serialize (rare) */
const char *whox_md_serialize(ModData *m)
{
static char buf[32];
if (m->i == 0)
return NULL; /* not set */
snprintf(buf, sizeof(buf), "%d", m->i);
return buf;
}
/** whox module data operations: unserialize (rare) */
void whox_md_unserialize(const char *str, ModData *m)
{
m->i = atoi(str);
}
/** whox module data operations: free */
void whox_md_free(ModData *md)
{
/* we have nothing to free actually, but we must set to zero */
md->l = 0;
}
/** cmd_whox: standardized "extended" version of WHO.
* The good thing about WHOX is that it allows the client to define what
* output they want to see. Another good thing is that it is standardized
* (ok, actually it isn't... but hey!).
* The bad thing is that it's a lot less flexible than the original WHO
* we had in UnrealIRCd in 3.2.x and 4.0.x and that the WHOX spec defines
* that there are two ways to specify a mask (neither one is logical):
* WHO <mask> <flags>
* WHO <mask> <flags> <mask2>
* In case the latter is present then mask2 overrides the mask
* (and the original 'mask' is ignored)
* Yes, this indeed damn ugly. It shows (yet again) that they should never
* have put the mask before the flags.. what kind of logic was that?
* I wonder if the person who invented this also writes C functions like:
* int (char *param)func
* or types:
* cp mode,time=--preserve
* or creates configuration files like:
* Syzop oper {
* 5 maxlogins;
* I mean... really...? -- Syzop
* So, we will try to abide to the WHOX spec as much as possible but
* try to warn our users in case they use the 'original' UnrealIRCd
* WHO syntax which was WHO [+-]<flags> <mask>.
* Note that we won't catch all cases, but we do our best.
* When in doubt, we will assume WHOX so to not break the spec
* (which again.. doesn't exist... but hey..)
*/
CMD_FUNC(cmd_whox)
{
char *mask;
const char *orig_mask;
char ch; /* Scratch char register */
const char *p; /* Scratch char pointer */
int member;
int operspy = 0;
struct who_format fmt;
const char *s;
char maskcopy[BUFSIZE];
Membership *lp;
Client *acptr;
memset(&fmt, 0, sizeof(fmt));
if (!MyUser(client))
return;
if ((parc < 2))
{
sendnumeric(client, ERR_NEEDMOREPARAMS, "WHO");
return;
}
if ((parc > 3) && parv[3])
orig_mask = parv[3];
else
orig_mask = parv[1];
if (!convert_classical_who_request(client, &parc, parv, &orig_mask, &fmt))
return;
/* Evaluate the flags now, we consider the second parameter
* as "matchFlags%fieldsToInclude,querytype" */
if ((parc > 2) && parv[2] && *parv[2])
{
p = parv[2];
while (((ch = *(p++))) && (ch != '%') && (ch != ','))
{
switch (ch)
{
case 'o': fmt.matchsel |= WMATCH_OPER; continue;
case 'n': fmt.matchsel |= WMATCH_NICK; continue;
case 'u': fmt.matchsel |= WMATCH_USER; continue;
case 'h': fmt.matchsel |= WMATCH_HOST; continue;
case 'i': fmt.matchsel |= WMATCH_IP; continue;
case 'r': fmt.matchsel |= WMATCH_INFO; continue;
case 's': fmt.matchsel |= WMATCH_SERVER; continue;
case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue;
case 'm': fmt.matchsel |= WMATCH_MODES; continue;
case 't': fmt.matchsel |= WMATCH_CONTIME; continue;
case 'R':
if (IsOper(client))
fmt.show_realhost = 1;
continue;
case 'I':
if (IsOper(client))
fmt.show_ip = 1;
continue;
}
}
}
if ((parc > 2) && (s = strchr(parv[2], '%')) != NULL)
{
s++;
for (; *s != '\0'; s++)
{
switch (*s)
{
case 'c': fmt.fields |= FIELD_CHANNEL; break;
case 'd': fmt.fields |= FIELD_HOP; break;
case 'f': fmt.fields |= FIELD_FLAGS; break;
case 'h': fmt.fields |= FIELD_HOST; break;
case 'H': fmt.fields |= FIELD_REALHOST; break;
case 'i': fmt.fields |= FIELD_IP; break;
case 'l': fmt.fields |= FIELD_IDLE; break;
case 'n': fmt.fields |= FIELD_NICK; break;
case 'r': fmt.fields |= FIELD_INFO; break;
case 's': fmt.fields |= FIELD_SERVER; break;
case 't': fmt.fields |= FIELD_QUERYTYPE; break;
case 'u': fmt.fields |= FIELD_USER; break;
case 'a': fmt.fields |= FIELD_ACCOUNT; break;
case 'm': fmt.fields |= FIELD_MODES; break;
case 'o': fmt.fields |= FIELD_OPLEVEL; break;
case 'R': fmt.fields |= FIELD_REPUTATION; break;
case ',':
s++;
fmt.querytype = s;
s += strlen(s);
s--;
break;
}
}
if (BadPtr(fmt.querytype) || (strlen(fmt.querytype) > 3))
fmt.querytype = "0";
}
strlcpy(maskcopy, orig_mask, sizeof maskcopy);
mask = maskcopy;
collapse(mask);
if ((ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) &&
ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL)))
{
operspy = 1;
}
if (fmt.matchsel & WMATCH_MODES)
{
char *s = mask;
int *umodes;
int what = WHO_ADD;
while (*s)
{
Umode *um;
switch (*s)
{
case '+':
what = WHO_ADD;
s++;
break;
case '-':
what = WHO_DEL;
s++;
break;
}
if (!*s)
break;
if (what == WHO_ADD)
umodes = &fmt.umodes;
else
umodes = &fmt.noumodes;
for (um = usermodes; um; um = um->next)
{
if (um->letter == *s)
{
*umodes |= um->mode;
break;
}
}
s++;
}
if (!IsOper(client))
{
/* these are usermodes regular users may search for. just oper now. */
fmt.umodes &= UMODE_OPER;
fmt.noumodes &= UMODE_OPER;
}
}
/* match connect time */
if (fmt.matchsel & WMATCH_CONTIME)
{
char *s = mask;
time_t currenttime = TStime();
fmt.contimemin = 0;
fmt.contimemax = 0;
switch (*s)
{
case '<':
if (*s++)
fmt.contimemin = currenttime - config_checkval(s, CFG_TIME);
break;
case '>':
if (*s++)
fmt.contimemax = currenttime - config_checkval(s, CFG_TIME);
break;
}
}
/* '/who #some_channel' */
if (IsChannelName(mask))
{
Channel *channel = NULL;
/* List all users on a given channel */
if ((channel = find_channel(orig_mask)) != NULL)
{
if (IsMember(client, channel) || operspy)
do_who_on_channel(client, channel, 1, operspy, &fmt);
else if (!SecretChannel(channel))
do_who_on_channel(client, channel, 0, operspy, &fmt);
}
sendnumeric(client, RPL_ENDOFWHO, mask);
return;
}
if (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) ||
ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL))
{
operspy = 1;
}
/* '/who 0' for a global list. this forces clients to actually
* request a full list. I presume its because of too many typos
* with "/who" ;) --fl
*/
if (!strcmp(mask, "0"))
who_global(client, NULL, 0, &fmt);
else
who_global(client, mask, operspy, &fmt);
sendnumeric(client, RPL_ENDOFWHO, mask);
}
/* do_match
* inputs - pointer to client requesting who
* - pointer to client to do who on
* - char * mask to match
* - format options
* output - 1 if match, 0 if no match
* side effects - NONE
*/
static int do_match(Client *client, Client *acptr, char *mask, struct who_format *fmt)
{
if (mask == NULL)
return 1;
/* default */
if (fmt->matchsel == 0 && (match_simple(mask, acptr->name) ||
match_simple(mask, acptr->user->username) ||
match_simple(mask, GetHost(acptr)) ||
(IsOper(client) &&
(match_simple(mask, acptr->user->realhost) ||
(acptr->ip &&
match_simple(mask, acptr->ip))))))
{
return 1;
}
/* match nick */
if (IsMatch(fmt, WMATCH_NICK) && match_simple(mask, acptr->name))
return 1;
/* match username */
if (IsMatch(fmt, WMATCH_USER) && match_simple(mask, acptr->user->username))
return 1;
/* match server */
if (IsMatch(fmt, WMATCH_SERVER) && IsOper(client) && match_simple(mask, acptr->user->server))
return 1;
/* match hostname */
if (IsMatch(fmt, WMATCH_HOST) && (match_simple(mask, GetHost(acptr)) ||
(IsOper(client) && (match_simple(mask, acptr->user->realhost) ||
(acptr->ip && match_simple(mask, acptr->ip))))))
{
return 1;
}
/* match realname */
if (IsMatch(fmt, WMATCH_INFO) && match_simple(mask, acptr->info))
return 1;
/* match ip address */
if (IsMatch(fmt, WMATCH_IP) && IsOper(client) && acptr->ip &&
match_user(mask, acptr, MATCH_CHECK_IP))
return 1;
/* match account */
if (IsMatch(fmt, WMATCH_ACCOUNT) && IsLoggedIn(acptr) && match_simple(mask, acptr->user->account))
{
return 1;
}
/* match usermodes */
if (IsMatch(fmt, WMATCH_MODES) && (fmt->umodes || fmt->noumodes))
{
long umodes = acptr->umodes;
if ((acptr->umodes & UMODE_HIDEOPER) && !IsOper(client))
umodes &= ~UMODE_OPER; /* pretend -o if +H */
/* Now check 'umodes' (not acptr->umodes),
* If multiple conditions are specified it is an
* AND condition and not an OR.
*/
if (((umodes & fmt->umodes) == fmt->umodes) &&
((umodes & fmt->noumodes) == 0))
{
return 1;
}
}
/* match connect time */
if (IsMatch(fmt, WMATCH_CONTIME) && MyConnect(acptr) && (fmt->contimemin || fmt->contimemax))
{
if (fmt->contimemin && (acptr->local->creationtime > fmt->contimemin))
return 1;
if (fmt->contimemax && (acptr->local->creationtime < fmt->contimemax))
return 1;
}
return 0;
}
/* who_common_channel
* inputs - pointer to client requesting who
* - pointer to channel.
* - char * mask to match
* - int if oper on a server or not
* - pointer to int maxmatches
* - format options
* output - NONE
* side effects - lists matching clients on specified channel,
* marks matched clients.
*
* NOTE: only call this from who_global() due to client marking!
*/
static void who_common_channel(Client *client, Channel *channel,
char *mask, int *maxmatches, struct who_format *fmt)
{
Membership *us;
Member *cm;
Client *acptr;
Hook *h;
int i = 0;
us = find_membership_link(client->user->channel, channel);
if (!us)
abort(); /* impossible, we are in who_common_channel... */
for (cm = channel->members; cm; cm = cm->next)
{
acptr = cm->client;
if (IsMarked(acptr))
continue;
if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
continue;
if (!user_can_see_member_fast(client, acptr, channel, cm, us->member_modes))
continue;
SetMark(acptr);
if (*maxmatches > 0)
{
if (do_match(client, acptr, mask, fmt))
{
do_who(client, acptr, NULL, fmt);
--(*maxmatches);
}
}
}
}
/*
* who_global
*
* inputs - pointer to client requesting who
* - char * mask to match
* - int if oper on a server or not
* - int if operspy or not
* - format options
* output - NONE
* side effects - do a global scan of all clients looking for match
* this is slightly expensive on EFnet ...
* marks assumed cleared for all clients initially
* and will be left cleared on return
*/
static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt)
{
Client *hunted = NULL;
Client *acptr;
int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
/* If searching for a nick explicitly, then include it later on in the result: */
if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0)))
hunted = find_user(mask, NULL);
/* Initialize the markers to zero */
list_for_each_entry(acptr, &client_list, client_node)
ClearMark(acptr);
/* First, if not operspy, then list all matching clients on common channels */
if (!operspy)
{
Membership *lp;
for (lp = client->user->channel; lp; lp = lp->next)
who_common_channel(client, lp->channel, mask, &maxmatches, fmt);
}
/* Second, list all matching visible clients. */
list_for_each_entry(acptr, &client_list, client_node)
{
if (!IsUser(acptr))
continue;
if (IsInvisible(acptr) && !operspy && (client != acptr) && (acptr != hunted))
continue;
if (IsMarked(acptr))
continue;
if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
continue;
if (maxmatches > 0)
{
if (do_match(client, acptr, mask, fmt))
{
do_who(client, acptr, NULL, fmt);
--maxmatches;
}
}
}
if (maxmatches <= 0)
sendnumeric(client, ERR_TOOMANYMATCHES, "WHO", "output too large, truncated");
}
/*
* do_who_on_channel
*
* inputs - pointer to client requesting who
* - pointer to channel to do who on
* - The "real name" of this channel
* - int if client is a server oper or not
* - int if client is member or not
* - format options
* output - NONE
* side effects - do a who on given channel
*/
static void do_who_on_channel(Client *client, Channel *channel,
int member, int operspy, struct who_format *fmt)
{
Membership *us = find_membership_link(client->user->channel, channel);
Member *cm;
Hook *h;
int i = 0;
for (cm = channel->members; cm; cm = cm->next)
{
Client *acptr = cm->client;
if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
continue;
if (!operspy && !user_can_see_member_fast(client, acptr, channel, cm, us ? us->member_modes : NULL))
continue;
if (member || !IsInvisible(acptr))
do_who(client, acptr, channel, fmt);
}
}
/*
* append_format
*
* inputs - pointer to buffer
* - size of buffer
* - pointer to position
* - format string
* - arguments for format
* output - NONE
* side effects - position incremented, possibly beyond size of buffer
* this allows detecting overflow
*/
static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...)
{
size_t max, result;
va_list ap;
max = *pos >= bufsize ? 0 : bufsize - *pos;
va_start(ap, fmt);
result = vsnprintf(buf + *pos, max, fmt, ap);
va_end(ap);
*pos += result;
}
/*
* show_ip() - asks if the true IP should be shown when source is
* asking for info about target
*
* Inputs - client who is asking
* - acptr who do we want the info on
* Output - returns 1 if clear IP can be shown, otherwise 0
* Side Effects - none
*/
static int show_ip(Client *client, Client *acptr)
{
if (IsServer(acptr))
return 0;
else if ((client != NULL) && (MyConnect(client) && !IsOper(client)) && (client == acptr))
return 1;
else if (IsHidden(acptr) && ((client != NULL) && !IsOper(client)))
return 0;
else
return 1;
}
/*
* do_who
*
* inputs - pointer to client requesting who
* - pointer to client to do who on
* - channel or NULL
* - format options
* output - NONE
* side effects - do a who on given person
*/
static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt)
{
char status[20];
char str[510 + 1];
size_t pos;
int hide = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
int i = 0;
Hook *h;
if (acptr->user->away)
status[i++] = 'G';
else
status[i++] = 'H';
if (IsRegNick(acptr))
status[i++] = 'r';
if (IsSecureConnect(acptr))
status[i++] = 's';
for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
{
int ret = (*(h->func.intfunc))(client, acptr, NULL, NULL, status, 0);
if (ret != 0)
status[i++] = (char)ret;
}
if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
status[i++] = '*';
if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client)))
status[i++] = '!';
if (channel)
{
Membership *lp;
if ((lp = find_membership_link(acptr->user->channel, channel)))
{
if (!(fmt->fields || HasCapability(client, "multi-prefix")))
{
/* Standard NAMES reply (single character) */
char c = mode_to_prefix(*lp->member_modes);
if (c)
status[i++] = c;
}
else
{
/* NAMES reply with all rights included (multi-prefix / NAMESX) */
strcpy(&status[i], modes_to_prefix(lp->member_modes));
i += strlen(&status[i]);
}
}
}
status[i] = '\0';
if (fmt->fields == 0)
{
char *host;
if (fmt->show_realhost)
host = acptr->user->realhost;
else if (fmt->show_ip)
host = GetIP(acptr);
else
host = GetHost(acptr);
sendnumeric(client, RPL_WHOREPLY,
channel ? channel->name : "*",
acptr->user->username, host,
hide ? "*" : acptr->user->server,
acptr->name, status, hide ? 0 : acptr->hopcount, acptr->info);
} else
{
str[0] = '\0';
pos = 0;
append_format(str, sizeof str, &pos, ":%s %d %s", me.name, RPL_WHOSPCRPL, client->name);
if (HasField(fmt, FIELD_QUERYTYPE))
append_format(str, sizeof str, &pos, " %s", fmt->querytype);
if (HasField(fmt, FIELD_CHANNEL))
append_format(str, sizeof str, &pos, " %s", channel ? channel->name : "*");
if (HasField(fmt, FIELD_USER))
append_format(str, sizeof str, &pos, " %s", acptr->user->username);
if (HasField(fmt, FIELD_IP))
{
if (show_ip(client, acptr) && acptr->ip)
append_format(str, sizeof str, &pos, " %s", acptr->ip);
else
append_format(str, sizeof str, &pos, " %s", "255.255.255.255");
}
if (HasField(fmt, FIELD_HOST) || HasField(fmt, FIELD_REALHOST))
{
if (IsOper(client) && HasField(fmt, FIELD_REALHOST))
append_format(str, sizeof str, &pos, " %s", acptr->user->realhost);
else
append_format(str, sizeof str, &pos, " %s", GetHost(acptr));
}
if (HasField(fmt, FIELD_SERVER))
append_format(str, sizeof str, &pos, " %s", hide ? "*" : acptr->user->server);
if (HasField(fmt, FIELD_NICK))
append_format(str, sizeof str, &pos, " %s", acptr->name);
if (HasField(fmt, FIELD_FLAGS))
append_format(str, sizeof str, &pos, " %s", status);
if (HasField(fmt, FIELD_MODES))
{
if (IsOper(client))
{
const char *umodes = get_usermode_string(acptr);
if (*umodes == '+')
umodes++;
append_format(str, sizeof str, &pos, " %s", umodes);
} else {
append_format(str, sizeof str, &pos, " %s", "*");
}
}
if (HasField(fmt, FIELD_HOP))
append_format(str, sizeof str, &pos, " %d", hide ? 0 : acptr->hopcount);
if (HasField(fmt, FIELD_IDLE))
{
append_format(str, sizeof str, &pos, " %d",
(int)((MyUser(acptr) && !hide_idle_time(client, acptr)) ? (TStime() - acptr->local->idle_since) : 0));
}
if (HasField(fmt, FIELD_ACCOUNT))
append_format(str, sizeof str, &pos, " %s", IsLoggedIn(acptr) ? acptr->user->account : "0");
if (HasField(fmt, FIELD_OPLEVEL))
append_format(str, sizeof str, &pos, " %s", (channel && check_channel_access(acptr, channel, "hoaq")) ? "999" : "n/a");
if (HasField(fmt, FIELD_REPUTATION))
{
if (IsOper(client))
append_format(str, sizeof str, &pos, " %d", GetReputation(acptr));
else
append_format(str, sizeof str, &pos, " %s", "*");
}
if (HasField(fmt, FIELD_INFO))
append_format(str, sizeof str, &pos, " :%s", acptr->info);
sendto_one(client, NULL, "%s", str);
}
}
/* Yeah, this is fun. Thank you WHOX !!! */
static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt)
{
const char *p;
static char pbuf1[512], pbuf2[512];
int points;
/* Figure out if the user is doing a 'classical' UnrealIRCd request,
* which can be recognized as follows:
* 1) Always a + or - as 1st character for the 1st parameter.
* 2) Unlikely to have a % (percent sign) in the 2nd parameter
* 3) Unlikely to have a 3rd parameter
* On the other hand WHOX requests are:
* 4) never going to have a '*', '?' or '.' as 2nd parameter
* 5) never going to have a '+' or '-' as 1st character in 2nd parameter
* Additionally, WHOX requests are useless - and thus unlikely -
* to search for a mask mask starting with + or - except when:
* 6) searching for 'm' (mode)
* 7) or 'r' (info, realname)
* ..for which this would be a meaningful request.
* The end result is that we can do quite some useful heuristics
* except for some corner cases.
*/
if (((*parv[1] == '+') || (*parv[1] == '-')) &&
(!parv[2] || !strchr(parv[2], '%')) &&
(*parc < 4))
{
/* Conditions 1-3 of above comment are met, now we deal
* with conditions 4-7.
*/
if (parv[2] &&
!strchr(parv[2], '*') && !strchr(parv[2], '?') &&
!strchr(parv[2], '.') &&
!strchr(parv[2], '+') && !strchr(parv[2], '-') &&
(strchr(parv[2], 'm') || strchr(parv[2], 'r')))
{
/* 'WHO +something m" or even
* 'WHO +c #something' (which includes 'm')
* could mean either WHO or WHOX style
*/
} else {
/* If we get here then it's an classical
* UnrealIRCd-style WHO request which has
* the order: WHO <options> <mask>
*/
char oldrequest[256];
snprintf(oldrequest, sizeof(oldrequest), "WHO %s%s%s",
parv[1], parv[2] ? " " : "", parv[2] ? parv[2] : "");
if (parv[2])
{
const char *swap = parv[1];
parv[1] = parv[2];
parv[2] = swap;
} else {
/* A request like 'WHO +I' or 'WHO +R' */
parv[2] = parv[1];
parv[1] = "*";
parv[3] = NULL;
*parc = 3;
}
/* Ok, that was the first step, now we need to convert the
* flags since they have changed a little as well:
* Flag a: user is away << no longer exists
* Flag c <channel>: user is on <channel> << no longer exists
* Flag g <gcos/realname>: user has string <gcos> in his/her GCOS << now called 'r'
* Flag h <host>: user has string <host> in his/her hostname << no change
* Flag i <ip>: user has string <ip> in his/her IP address << no change
* Flag m <usermodes>: user has <usermodes> set << behavior change
* Flag n <nick>: user has string <nick> in his/her nickname << no change
* Flag s <server>: user is on server <server> << no change
* Flag u <user>: user has string <user> in his/her username << no change
* Behavior flags:
* Flag M: check for user in channels I am a member of << no longer exists
* Flag R: show users' real hostnames << no change (re-added)
* Flag I: show users' IP addresses << no change (re-added)
*/
if (strchr(parv[2], 'a'))
{
sendnotice(client, "WHO request '%s' failed: flag 'a' no longer exists with WHOX.", oldrequest);
return 0;
}
if (strchr(parv[2], 'c'))
{
sendnotice(client, "WHO request '%s' failed: flag 'c' no longer exists with WHOX.", oldrequest);
return 0;
}
if (strchr(parv[2], 'g'))
{
char *w;
strlcpy(pbuf2, parv[2], sizeof(pbuf2));
for (w = pbuf2; *w; w++)
{
if (*w == 'g')
{
*w = 'r';
break;
}
}
parv[2] = pbuf2;
}
/* "WHO -m xyz" (now: xyz -m) should become "WHO -xyz m"
* Wow, this seems overly complex, but okay...
*/
points = 0;
for (p = parv[2]; *p; p++)
{
if (*p == '-')
points = 1;
else if (*p == '+')
points = 0;
else if (points && (*p == 'm'))
{
points = 2;
break;
}
}
if (points == 2)
{
snprintf(pbuf1, sizeof(pbuf1), "-%s", parv[1]);
parv[1] = pbuf1;
}
if ((*parv[2] == '+') || (*parv[2] == '-'))
parv[2] = parv[2]+1; /* strip '+'/'-' prefix, which does not exist in WHOX */
sendnotice(client, "WHO request '%s' changed to match new WHOX syntax: 'WHO %s %s'",
oldrequest, parv[1], parv[2]);
*orig_mask = parv[1];
}
}
return 1;
}