mirror of https://github.com/pissnet/pissircd.git
981 lines
26 KiB
C
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;
|
|
}
|