|
/* m_whox.c - Add WHOX support to /WHO |
|
* 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. |
|
*/ |
|
|
|
/* based heavily on the original m_who.c. Due to the complexity of this |
|
* command, it's easier to override it entirely rather than attempting |
|
* to bolt on WHOX functionality (most of the code would need to be copied |
|
* anyway). */ |
|
|
|
#include "unrealircd.h" |
|
|
|
#define RPL_WHOSPCRPL 354 |
|
#define MSG_WHO "WHO" |
|
|
|
static int whox(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[]); |
|
|
|
ModuleHeader MOD_HEADER(m_whox) = { |
|
"whox", |
|
"1.0", |
|
"WHOX support for /who", |
|
"3.2-b8-1", |
|
NULL |
|
}; |
|
|
|
MOD_INIT(m_whox) |
|
{ |
|
return MOD_SUCCESS; |
|
} |
|
|
|
MOD_LOAD(m_whox) |
|
{ |
|
if (!CmdoverrideAdd(modinfo->handle, "WHO", whox)) |
|
return MOD_FAILED; |
|
|
|
if (!IsupportAdd(modinfo->handle, "WHOX", NULL)) |
|
return MOD_FAILED; |
|
|
|
return MOD_SUCCESS; |
|
} |
|
|
|
MOD_UNLOAD(m_whox) |
|
{ |
|
return MOD_SUCCESS; |
|
} |
|
|
|
static void do_channel_who(aClient *sptr, aChannel *channel, char *mask); |
|
static void make_who_status(aClient *, aClient *, aChannel *, Member *, char *, int); |
|
static void do_other_who(aClient *sptr, char *mask); |
|
static void send_who_reply(aClient *, aClient *, char *, char *, char *); |
|
static char *first_visible_channel(aClient *, aClient *, int *); |
|
static int parse_who_options(aClient *, int, char **); |
|
static void who_sendhelp(aClient *); |
|
|
|
#define WF_OPERONLY 0x01 /**< only show opers */ |
|
#define WF_ONCHANNEL 0x02 /**< we're on the channel we're /who'ing */ |
|
#define WF_WILDCARD 0x04 /**< a wildcard /who */ |
|
#define WF_REALHOST 0x08 /**< want real hostnames */ |
|
#define WF_IP 0x10 /**< want IP addresses */ |
|
|
|
static int who_flags; |
|
|
|
#define WHO_CANTSEE 0x01 /**< set if we can't see them */ |
|
#define WHO_CANSEE 0x02 /**< set if we can */ |
|
#define WHO_OPERSEE 0x04 /**< set if we only saw them because we're an oper */ |
|
|
|
#define FVC_HIDDEN 0x01 |
|
|
|
#define WHO_WANT 1 |
|
#define WHO_DONTWANT 2 |
|
#define WHO_DONTCARE 0 |
|
|
|
struct { |
|
int want_away; |
|
int want_channel; |
|
char *channel; /**< if they want one */ |
|
int want_gecos; |
|
char *gecos; |
|
int want_server; |
|
char *server; |
|
int want_host; |
|
char *host; |
|
int want_nick; |
|
char *nick; |
|
int want_user; |
|
char *user; |
|
int want_ip; |
|
char *ip; |
|
int want_port; |
|
int port; |
|
int want_umode; |
|
int umodes_dontwant; |
|
int umodes_want; |
|
int common_channels_only; |
|
int whox_querytype; |
|
char whox_fields[13]; |
|
} wfl; |
|
|
|
/** The /who command: retrieves information from users. */ |
|
static int whox(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[]) |
|
{ |
|
aChannel *target_channel; |
|
char *mask = parv[1]; |
|
char star[] = "*"; |
|
int i = 0; |
|
|
|
if (!MyClient(sptr)) |
|
return 0; |
|
|
|
who_flags = 0; |
|
memset(&wfl, 0, sizeof(wfl)); |
|
|
|
if (parc > 1) |
|
{ |
|
i = parse_who_options(sptr, parc - 1, parv + 1); |
|
if (i < 0) |
|
{ |
|
sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask); |
|
return 0; |
|
} |
|
} |
|
|
|
if (parc-i < 2 || strcmp(parv[1 + i], "0") == 0) |
|
mask = star; |
|
else |
|
mask = parv[1 + i]; |
|
|
|
collapse(mask); |
|
|
|
if (!i && parc > 2 && *parv[2] == 'o') |
|
who_flags |= WF_OPERONLY; |
|
else if (!i && parc > 2) |
|
{ |
|
i = parse_who_options(sptr, parc - 2, parv + 2); |
|
if (i < 0) |
|
{ |
|
sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask); |
|
return 0; |
|
} |
|
} |
|
if (*mask == '\0') |
|
{ |
|
/* no mask given */ |
|
sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, "*"); |
|
return 0; |
|
} |
|
|
|
if ((target_channel = find_channel(mask, NULL)) != NULL) |
|
{ |
|
do_channel_who(sptr, target_channel, mask); |
|
sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask); |
|
return 0; |
|
} |
|
|
|
if (wfl.channel && wfl.want_channel == WHO_WANT && |
|
(target_channel = find_channel(wfl.channel, NULL)) != NULL) |
|
{ |
|
do_channel_who(sptr, target_channel, mask); |
|
sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask); |
|
return 0; |
|
} |
|
else |
|
{ |
|
do_other_who(sptr, mask); |
|
sendto_one(sptr, getreply(RPL_ENDOFWHO), me.name, sptr->name, mask); |
|
return 0; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void who_sendhelp(aClient *sptr) |
|
{ |
|
char *who_help[] = { |
|
"/WHO [+|-][achmnsuM][%tcuihsnfdlar[,<querytype>]] [args] [mask]", |
|
"Flags are specified like channel modes, the flags chmnsu 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, only o is allowed", |
|
"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 M: check for user in channels I am a member of", |
|
"Fields begin with the % character. If any fields are specified, the reply", |
|
"uses the 354 numeric instead of 352. Fields not specified are omitted from", |
|
"the reply, while those specified are outputted in the order listed here.", |
|
"The fields work as follows:", |
|
"Field t: outputs the number in <querytype> in reply", |
|
"Field c: channel", |
|
"Field u: username", |
|
"Field i: IP address", |
|
"Field h: host", |
|
"Field s: server", |
|
"Field n: nickname", |
|
"Field f: status", |
|
"Field d: hop count", |
|
"Field l: idle time (0 for users on other servers)", |
|
"Field a: services account name (0 if none)", |
|
"Field r: realname", |
|
NULL |
|
}; |
|
|
|
char *who_oper_help[] = { |
|
"/WHO [+|-][acghimnsuMRI][%tcuihsnfdlar[,<querytype>]] [args] [mask]", |
|
"Flags are specified like channel modes, the flags chigmnsu 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", |
|
"Flag h <host>: user has string <host> in their hostname,", |
|
" wildcards accepted", |
|
"Flag i <ip>: user has string <ip> in their IP address,", |
|
" wildcards accepted", |
|
"Flag p <port>: user is connecting on port <port>,", |
|
" local connections only", |
|
"Flag m <usermodes>: user has <usermodes> set", |
|
"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 M: check for user in channels I am a member of", |
|
"Flag R: show users' real hostnames", |
|
"Flag I: show users' IP addresses", |
|
"Fields begin with the % character. If any fields are specified, the reply", |
|
"uses the 354 numeric instead of 352. Fields not specified are omitted from", |
|
"the reply, while those specified are outputted in the order listed here.", |
|
"The fields work as follows:", |
|
"Field t: outputs the number in <querytype> in reply", |
|
"Field c: channel", |
|
"Field u: username", |
|
"Field i: IP address", |
|
"Field h: host", |
|
"Field s: server", |
|
"Field n: nickname", |
|
"Field f: status", |
|
"Field d: hop count", |
|
"Field l: idle time (0 for users on other servers)", |
|
"Field a: services account name (0 if none)", |
|
"Field r: realname", |
|
NULL |
|
}; |
|
char **s; |
|
|
|
if (IsOper(sptr)) |
|
s = who_oper_help; |
|
else |
|
s = who_help; |
|
|
|
for (; *s; s++) |
|
sendto_one(sptr, getreply(RPL_LISTSYNTAX), me.name, sptr->name, *s); |
|
} |
|
|
|
#define WHO_ADD 1 |
|
#define WHO_DEL 2 |
|
|
|
static int parse_who_options(aClient *sptr, int argc, char **argv) |
|
{ |
|
char *s = argv[0]; |
|
int what = WHO_ADD; |
|
int i = 1; |
|
|
|
/* A few helper macro's because this is used a lot, added during recode by Syzop. */ |
|
|
|
/** function requiress a parameter: check if there's one, if not: return -1. */ |
|
#define REQUIRE_PARAM() { if (i >= argc) { \ |
|
who_sendhelp(sptr); \ |
|
return -1; \ |
|
} } while(0); |
|
/** set option 'x' depending on 'what' (add/want or del/dontwant) */ |
|
#define SET_OPTION(x) { if (what == WHO_ADD) \ |
|
x = WHO_WANT; \ |
|
else \ |
|
x = WHO_DONTWANT; \ |
|
} while(0); |
|
/** Eat a param, set the param in memory and set the option to want or dontwant */ |
|
#define DOIT(x,y) { REQUIRE_PARAM(); x = argv[i]; SET_OPTION(y); i++; } while(0); |
|
if (*s != '-' && *s != '+' && *s != '%') |
|
return 0; |
|
|
|
while (*s) |
|
{ |
|
switch (*s) |
|
{ |
|
case '+': |
|
what = WHO_ADD; |
|
break; |
|
case '-': |
|
what = WHO_DEL; |
|
break; |
|
case 'a': |
|
SET_OPTION(wfl.want_away); |
|
break; |
|
case 'c': |
|
DOIT(wfl.channel, wfl.want_channel); |
|
break; |
|
case 'g': |
|
REQUIRE_PARAM() |
|
if (!IsOper(sptr)) |
|
break; /* oper-only */ |
|
wfl.gecos = argv[i]; |
|
SET_OPTION(wfl.want_gecos); |
|
i++; |
|
break; |
|
case 's': |
|
DOIT(wfl.server, wfl.want_server); |
|
break; |
|
case 'h': |
|
DOIT(wfl.host, wfl.want_host); |
|
break; |
|
case 'i': |
|
REQUIRE_PARAM() |
|
if (!IsOper(sptr)) |
|
break; /* oper-only */ |
|
wfl.ip = argv[i]; |
|
SET_OPTION(wfl.want_ip); |
|
i++; |
|
break; |
|
case 'n': |
|
DOIT(wfl.nick, wfl.want_nick); |
|
break; |
|
case 'u': |
|
DOIT(wfl.user, wfl.want_user); |
|
break; |
|
case 'm': |
|
REQUIRE_PARAM() |
|
{ |
|
char *s = argv[i]; |
|
int *umodes; |
|
|
|
if (what == WHO_ADD) |
|
umodes = &wfl.umodes_want; |
|
else |
|
umodes = &wfl.umodes_dontwant; |
|
|
|
while (*s) |
|
{ |
|
int i; |
|
for (i = 0; i <= Usermode_highest; i++) |
|
if (*s == Usermode_Table[i].flag) |
|
{ |
|
*umodes |= Usermode_Table[i].mode; |
|
break; |
|
} |
|
s++; |
|
} |
|
|
|
if (!IsOper(sptr)) |
|
*umodes = *umodes & UMODE_OPER; /* these are usermodes regular users may search for. just oper now. */ |
|
if (*umodes == 0) |
|
return -1; |
|
} |
|
i++; |
|
break; |
|
case 'p': |
|
REQUIRE_PARAM() |
|
if (!IsOper(sptr)) |
|
break; /* oper-only */ |
|
wfl.port = atoi(argv[i]); |
|
SET_OPTION(wfl.want_port); |
|
i++; |
|
break; |
|
case 'M': |
|
SET_OPTION(wfl.common_channels_only); |
|
break; |
|
case 'R': |
|
if (!IsOper(sptr)) |
|
break; |
|
if (what == WHO_ADD) |
|
who_flags |= WF_REALHOST; |
|
else |
|
who_flags &= ~WF_REALHOST; |
|
break; |
|
case 'I': |
|
if (!IsOper(sptr)) |
|
break; |
|
if (what == WHO_ADD) |
|
who_flags |= WF_IP; |
|
|
|
else |
|
who_flags &= ~WF_IP; |
|
break; |
|
case '%': |
|
s++; |
|
goto process_whox; |
|
default: |
|
who_sendhelp(sptr); |
|
return -1; |
|
} |
|
s++; |
|
} |
|
|
|
return i; |
|
process_whox: |
|
while (*s) |
|
{ |
|
switch (*s) |
|
{ |
|
case 't': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[1] = 1; |
|
break; |
|
case 'c': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[2] = 1; |
|
break; |
|
case 'u': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[3] = 1; |
|
break; |
|
case 'i': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[4] = 1; |
|
break; |
|
case 'h': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[5] = 1; |
|
break; |
|
case 's': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[6] = 1; |
|
break; |
|
case 'n': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[7] = 1; |
|
break; |
|
case 'f': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[8] = 1; |
|
break; |
|
case 'd': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[9] = 1; |
|
break; |
|
case 'l': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[10] = 1; |
|
break; |
|
case 'a': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[11] = 1; |
|
break; |
|
case 'r': |
|
wfl.whox_fields[0] = 1; |
|
wfl.whox_fields[12] = 1; |
|
break; |
|
case ',': |
|
s++; |
|
goto process_querytype; |
|
default: |
|
who_sendhelp(sptr); |
|
return -1; |
|
} |
|
s++; |
|
} |
|
|
|
return i; |
|
process_querytype: |
|
/* s points to a string representing an int querytype (could be empty) */ |
|
wfl.whox_querytype = strtol(s, NULL, 10); |
|
|
|
return i; |
|
#undef REQUIRE_PARAM |
|
#undef SET_OPTION |
|
#undef DOIT |
|
} |
|
|
|
static int can_see(aClient *sptr, aClient *acptr, aChannel *channel) |
|
{ |
|
int ret = 0; |
|
int i=0; |
|
Hook *h; |
|
char has_common_chan = 0; |
|
do { |
|
/* can only see people */ |
|
if (!IsPerson(acptr)) |
|
return WHO_CANTSEE; |
|
|
|
/* can only see opers if thats what they want */ |
|
if (who_flags & WF_OPERONLY) |
|
{ |
|
if (!IsOper(acptr)) |
|
return ret | WHO_CANTSEE; |
|
if (IsHideOper(acptr)) { |
|
if (IsOper(sptr)) |
|
ret |= WHO_OPERSEE; |
|
else |
|
return ret | WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people who are away */ |
|
if ((wfl.want_away == WHO_WANT && !acptr->user->away) || |
|
(wfl.want_away == WHO_DONTWANT && acptr->user->away)) |
|
return WHO_CANTSEE; |
|
|
|
/* if they only want people on a certain channel. */ |
|
if (wfl.want_channel != WHO_DONTCARE) |
|
{ |
|
aChannel *chan = find_channel(wfl.channel, NULL); |
|
if (!chan && wfl.want_channel == WHO_WANT) |
|
return WHO_CANTSEE; |
|
if ((wfl.want_channel == WHO_WANT) && !IsMember(acptr, chan)) |
|
return WHO_CANTSEE; |
|
if ((wfl.want_channel == WHO_DONTWANT) && IsMember(acptr, chan)) |
|
return WHO_CANTSEE; |
|
} |
|
|
|
/* if they only want people with a certain gecos */ |
|
if (wfl.want_gecos != WHO_DONTCARE) |
|
{ |
|
if (((wfl.want_gecos == WHO_WANT) && match(wfl.gecos, acptr->info)) || |
|
((wfl.want_gecos == WHO_DONTWANT) && !match(wfl.gecos, acptr->info))) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people with a certain server */ |
|
if (wfl.want_server != WHO_DONTCARE) |
|
{ |
|
if (((wfl.want_server == WHO_WANT) && stricmp(wfl.server, acptr->user->server)) || |
|
((wfl.want_server == WHO_DONTWANT) && !stricmp(wfl.server, acptr->user->server))) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people with a certain host */ |
|
if (wfl.want_host != WHO_DONTCARE) |
|
{ |
|
char *host; |
|
|
|
if (IsOper(sptr)) |
|
host = acptr->user->realhost; |
|
else |
|
host = GetHost(acptr); |
|
|
|
if (((wfl.want_host == WHO_WANT) && match(wfl.host, host)) || |
|
((wfl.want_host == WHO_DONTWANT) && !match(wfl.host, host))) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people with a certain IP */ |
|
if (wfl.want_ip != WHO_DONTCARE) |
|
{ |
|
char *ip; |
|
|
|
ip = acptr->ip; |
|
if (!ip) |
|
return WHO_CANTSEE; |
|
|
|
if (((wfl.want_ip == WHO_WANT) && match(wfl.ip, ip)) || |
|
((wfl.want_ip == WHO_DONTWANT) && !match(wfl.ip, ip))) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people connecting on a certain port */ |
|
if (wfl.want_port != WHO_DONTCARE) |
|
{ |
|
int port; |
|
|
|
if (!MyClient(acptr)) |
|
return WHO_CANTSEE; |
|
|
|
port = acptr->local->listener->port; |
|
|
|
if (((wfl.want_port == WHO_WANT) && wfl.port != port) || |
|
((wfl.want_port == WHO_DONTWANT) && wfl.port == port)) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people with a certain nick.. */ |
|
if (wfl.want_nick != WHO_DONTCARE) |
|
{ |
|
if (((wfl.want_nick == WHO_WANT) && match(wfl.nick, acptr->name)) || |
|
((wfl.want_nick == WHO_DONTWANT) && !match(wfl.nick, acptr->name))) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people with a certain username */ |
|
if (wfl.want_user != WHO_DONTCARE) |
|
{ |
|
if (((wfl.want_user == WHO_WANT) && match(wfl.user, acptr->user->username)) || |
|
((wfl.want_user == WHO_DONTWANT) && !match(wfl.user, acptr->user->username))) |
|
{ |
|
return WHO_CANTSEE; |
|
} |
|
} |
|
|
|
/* if they only want people with a certain umode */ |
|
if (wfl.umodes_want) |
|
{ |
|
if (!(acptr->umodes & wfl.umodes_want) || (!IsOper(sptr) && (acptr->umodes & UMODE_HIDEOPER))) |
|
return WHO_CANTSEE; |
|
} |
|
|
|
if (wfl.umodes_dontwant) |
|
{ |
|
if ((acptr->umodes & wfl.umodes_dontwant) && (!(acptr->umodes & UMODE_HIDEOPER) || IsOper(sptr))) |
|
return WHO_CANTSEE; |
|
} |
|
|
|
/* if they only want common channels */ |
|
if (wfl.common_channels_only) |
|
{ |
|
if (!has_common_channels(sptr, acptr)) |
|
return WHO_CANTSEE; |
|
has_common_chan = 1; |
|
} |
|
|
|
if (channel) |
|
{ |
|
int member = who_flags & WF_ONCHANNEL; |
|
|
|
if (SecretChannel(channel) || HiddenChannel(channel)) |
|
{ |
|
/* if they aren't on it.. they can't see it */ |
|
if (!(who_flags & WF_ONCHANNEL)) |
|
break; |
|
} |
|
if (IsInvisible(acptr) && !member) |
|
break; |
|
|
|
if (!user_can_see_member(sptr, acptr, channel)) |
|
continue; /* invisible (eg: due to delayjoin) */ |
|
} |
|
else |
|
{ |
|
/* a user/mask who */ |
|
|
|
/* If the common channel info hasn't been set, set it now */ |
|
if (!wfl.common_channels_only) |
|
has_common_chan = has_common_channels(sptr, acptr); |
|
|
|
if (IsInvisible(acptr) && !has_common_chan) |
|
{ |
|
/* don't show them unless it's an exact match |
|
or it is the user requesting the /who */ |
|
if ((who_flags & WF_WILDCARD) && sptr != acptr) |
|
break; |
|
} |
|
} |
|
|
|
/* phew.. show them. */ |
|
return WHO_CANSEE; |
|
} while (0); |
|
|
|
/* if we get here, it's oper-dependant. */ |
|
if (IsOper(sptr)) |
|
return ret | WHO_OPERSEE | WHO_CANSEE; |
|
else |
|
{ |
|
if (sptr == acptr) |
|
return ret | WHO_CANSEE; |
|
else |
|
return ret | WHO_CANTSEE; |
|
} |
|
} |
|
|
|
static void do_channel_who(aClient *sptr, aChannel *channel, char *mask) |
|
{ |
|
Member *cm = channel->members; |
|
if (IsMember(sptr, channel) || ValidatePermissionsForPath("override:see:who:onchannel",sptr,NULL,channel,NULL)) |
|
who_flags |= WF_ONCHANNEL; |
|
|
|
for (cm = channel->members; cm; cm = cm->next) |
|
{ |
|
aClient *acptr = cm->cptr; |
|
char status[32]; |
|
int cansee; |
|
if ((cansee = can_see(sptr, acptr, channel)) & WHO_CANTSEE) |
|
continue; |
|
|
|
make_who_status(sptr, acptr, channel, cm, status, cansee); |
|
send_who_reply(sptr, acptr, channel->chname, status, ""); |
|
} |
|
} |
|
|
|
static void make_who_status(aClient *sptr, aClient *acptr, aChannel *channel, |
|
Member *cm, char *status, int cansee) |
|
{ |
|
int i = 0; |
|
Hook *h; |
|
|
|
if (acptr->user->away) |
|
status[i++] = 'G'; |
|
else |
|
status[i++] = 'H'; |
|
|
|
if (IsARegNick(acptr)) |
|
status[i++] = 'r'; |
|
|
|
for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next) |
|
{ |
|
int ret = (*(h->func.intfunc))(sptr, acptr, channel, cm, status, cansee); |
|
if (ret != 0) |
|
status[i++] = (char)ret; |
|
} |
|
|
|
if (IsOper(acptr) && (!IsHideOper(acptr) || sptr == acptr || IsOper(sptr))) |
|
status[i++] = '*'; |
|
|
|
if (IsOper(acptr) && (IsHideOper(acptr) && sptr != acptr && IsOper(sptr))) |
|
status[i++] = '!'; |
|
|
|
if (cansee & WHO_OPERSEE) |
|
status[i++] = '?'; |
|
|
|
if (cm) |
|
{ |
|
if (SupportNAMESX(sptr)) |
|
{ |
|
#ifdef PREFIX_AQ |
|
if (cm->flags & CHFL_CHANOWNER) |
|
status[i++] = '~'; |
|
if (cm->flags & CHFL_CHANPROT) |
|
status[i++] = '&'; |
|
#endif |
|
if (cm->flags & CHFL_CHANOP) |
|
status[i++] = '@'; |
|
if (cm->flags & CHFL_HALFOP) |
|
status[i++] = '%'; |
|
if (cm->flags & CHFL_VOICE) |
|
status[i++] = '+'; |
|
} else { |
|
#ifdef PREFIX_AQ |
|
if (cm->flags & CHFL_CHANOWNER) |
|
status[i++] = '~'; |
|
else if (cm->flags & CHFL_CHANPROT) |
|
status[i++] = '&'; |
|
else |
|
#endif |
|
if (cm->flags & CHFL_CHANOP) |
|
status[i++] = '@'; |
|
else if (cm->flags & CHFL_HALFOP) |
|
status[i++] = '%'; |
|
else if (cm->flags & CHFL_VOICE) |
|
status[i++] = '+'; |
|
} |
|
} |
|
|
|
status[i] = '\0'; |
|
} |
|
|
|
static void do_other_who(aClient *sptr, char *mask) |
|
{ |
|
int oper = IsOper(sptr); |
|
|
|
if (strchr(mask, '*') || strchr(mask, '?')) |
|
{ |
|
int i = 0; |
|
/* go through all users.. */ |
|
aClient *acptr; |
|
who_flags |= WF_WILDCARD; |
|
|
|
list_for_each_entry(acptr, &client_list, client_node) |
|
{ |
|
int cansee; |
|
char status[20]; |
|
char *channel; |
|
int flg; |
|
|
|
if (!IsPerson(acptr)) |
|
continue; |
|
if (!oper) { |
|
/* non-opers can only search on nick here */ |
|
if (match(mask, acptr->name)) |
|
continue; |
|
} else { |
|
/* opers can search on name, ident, virthost, ip and realhost. |
|
* Yes, I like readable if's -- Syzop. |
|
*/ |
|
if (!match(mask, acptr->name) || !match(mask, acptr->user->realhost) || |
|
!match(mask, acptr->user->username)) |
|
goto matchok; |
|
if (IsHidden(acptr) && !match(mask, acptr->user->virthost)) |
|
goto matchok; |
|
if (acptr->ip && !match(mask, acptr->ip)) |
|
goto matchok; |
|
/* nothing matched... */ |
|
continue; |
|
} |
|
matchok: |
|
if ((cansee = can_see(sptr, acptr, NULL)) & WHO_CANTSEE) |
|
continue; |
|
if (WHOLIMIT && !IsOper(sptr) && ++i > WHOLIMIT) |
|
{ |
|
sendto_one(sptr, rpl_str(ERR_WHOLIMEXCEED), me.name, sptr->name, WHOLIMIT); |
|
return; |
|
} |
|
|
|
channel = first_visible_channel(sptr, acptr, &flg); |
|
make_who_status(sptr, acptr, NULL, NULL, status, cansee); |
|
send_who_reply(sptr, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : ""); |
|
} |
|
} |
|
else |
|
{ |
|
/* just a single client (no wildcards detected) */ |
|
aClient *acptr = find_client(mask, NULL); |
|
int cansee; |
|
char status[20]; |
|
char *channel; |
|
int flg; |
|
|
|
if (!acptr) |
|
return; |
|
|
|
if ((cansee = can_see(sptr, acptr, NULL)) == WHO_CANTSEE) |
|
return; |
|
|
|
channel = first_visible_channel(sptr, acptr, &flg); |
|
make_who_status(sptr, acptr, NULL, NULL, status, cansee); |
|
send_who_reply(sptr, acptr, channel, status, (flg & FVC_HIDDEN) ? "~" : ""); |
|
} |
|
} |
|
|
|
static void send_who_reply(aClient *sptr, aClient *acptr, |
|
char *channel, char *status, char *xstat) |
|
{ |
|
char *stat; |
|
char *host; |
|
char *ip; |
|
char *account; |
|
int flat = (FLAT_MAP && !IsOper(sptr)) ? 1 : 0; |
|
char *server; |
|
int hops; |
|
char hopsstr[12] = {0}; |
|
char querytype[12] = {0}; /* ensure we can comfortably fit the string representation of an int + trailing null */ |
|
char idle[12] = {0}; |
|
|
|
/* helper macro for WHOX replies */ |
|
#define MAYBE_BLANK(n, x) wfl.whox_fields[n] ? " " : "", wfl.whox_fields[n] ? (x) : "" |
|
#define MAYBE_BLANK_COLON(n, x) wfl.whox_fields[n] ? " :" : "", wfl.whox_fields[n] ? (x) : "" |
|
|
|
stat = malloc(strlen(status) + strlen(xstat) + 1); |
|
sprintf(stat, "%s%s", status, xstat); |
|
|
|
if (IsOper(sptr)) |
|
{ |
|
if (who_flags & WF_REALHOST) |
|
host = acptr->user->realhost; |
|
else if (who_flags & WF_IP) |
|
host = (acptr->ip ? acptr->ip : acptr->user->realhost); |
|
else |
|
host = GetHost(acptr); |
|
} |
|
else |
|
host = GetHost(acptr); |
|
|
|
if (!IsOper(sptr) && IsHidden(acptr)) |
|
ip = "255.255.255.255"; |
|
else |
|
ip = (acptr->ip ? acptr->ip : "255.255.255.255"); |
|
|
|
if (IsULine(acptr) && !IsOper(sptr) && !ValidatePermissionsForPath("map:ulines",sptr,acptr,NULL,NULL) && HIDE_ULINES) |
|
{ |
|
server = "hidden"; |
|
hops = 0; |
|
} |
|
else |
|
{ |
|
server = acptr->user->server; |
|
hops = flat ? 0 : acptr->hopcount; |
|
} |
|
|
|
account = (*acptr->user->svid == '*') ? "0" : acptr->user->svid; |
|
snprintf(querytype, 12, "%d", wfl.whox_querytype); |
|
snprintf(hopsstr, 12, "%d", hops); |
|
|
|
/* only show idle time if they're local and aren't an oper with +I */ |
|
if (MyConnect(acptr) && (IsOper(sptr) || !(acptr->umodes & UMODE_HIDLE))) |
|
snprintf(idle, 12, "%d", TStime() - acptr->local->last); |
|
else |
|
idle[0] = '0'; |
|
|
|
if (wfl.whox_fields[0]) |
|
sendto_one(sptr, ":%s 354 %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", me.name, sptr->name, |
|
MAYBE_BLANK(1, querytype), |
|
MAYBE_BLANK(2, channel), |
|
MAYBE_BLANK(3, acptr->user->username), |
|
MAYBE_BLANK(4, ip), |
|
MAYBE_BLANK(5, host), |
|
MAYBE_BLANK(6, server), |
|
MAYBE_BLANK(7, acptr->name), |
|
MAYBE_BLANK(8, stat), |
|
MAYBE_BLANK(9, hopsstr), |
|
MAYBE_BLANK(10, idle), |
|
MAYBE_BLANK(11, account), |
|
MAYBE_BLANK_COLON(12, acptr->info) |
|
); |
|
else |
|
sendto_one(sptr, getreply(RPL_WHOREPLY), me.name, sptr->name, |
|
channel, /* channel name */ |
|
acptr->user->username, /* user name */ |
|
host, /* hostname */ |
|
server, /* server name */ |
|
acptr->name, /* nick */ |
|
stat, /* status */ |
|
hops, /* hops */ |
|
acptr->info /* realname */ |
|
); |
|
|
|
free(stat); |
|
|
|
#undef MAYBE_BLANK |
|
#undef MAYBE_BLANK_COLON |
|
} |
|
|
|
static char *first_visible_channel(aClient *sptr, aClient *acptr, int *flg) |
|
{ |
|
Membership *lp; |
|
|
|
*flg = 0; |
|
|
|
for (lp = acptr->user->channel; lp; lp = lp->next) |
|
{ |
|
aChannel *chptr = lp->chptr; |
|
Hook *h; |
|
int ret = EX_ALLOW; |
|
int operoverride = 0; |
|
int showchannel = 0; |
|
|
|
/* Note that the code below is almost identical to the one in /WHOIS */ |
|
|
|
if (ShowChannel(sptr, chptr)) |
|
showchannel = 1; |
|
|
|
for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next) |
|
{ |
|
int n = (*(h->func.intfunc))(sptr, acptr, chptr); |
|
/* Hook return values: |
|
* EX_ALLOW means 'yes is ok, as far as modules are concerned' |
|
* EX_DENY means 'hide this channel, unless oper overriding' |
|
* EX_ALWAYS_DENY means 'hide this channel, always' |
|
* ... with the exception that we always show the channel if you /WHOIS yourself |
|
*/ |
|
if (n == EX_DENY) |
|
{ |
|
ret = EX_DENY; |
|
} |
|
else if (n == EX_ALWAYS_DENY) |
|
{ |
|
ret = EX_ALWAYS_DENY; |
|
break; |
|
} |
|
} |
|
|
|
if (ret == EX_DENY) |
|
showchannel = 0; |
|
|
|
if (!showchannel && (ValidatePermissionsForPath("override:see:who:secret",sptr,NULL,chptr,NULL) || ValidatePermissionsForPath("override:see:whois",sptr,NULL,chptr,NULL))) |
|
{ |
|
showchannel = 1; /* OperOverride */ |
|
operoverride = 1; |
|
} |
|
|
|
if ((ret == EX_ALWAYS_DENY) && (acptr != sptr)) |
|
continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */ |
|
|
|
if (acptr == sptr) |
|
showchannel = 1; |
|
|
|
if (operoverride) |
|
*flg |= FVC_HIDDEN; |
|
|
|
if (showchannel) |
|
return chptr->chname; |
|
} |
|
|
|
/* no channels that they can see */ |
|
return "*"; |
|
} |