unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

whox.c (26662B)

      1 /* cmd_whox.c / WHOX.
      2  * based on code from charybdis and ircu.
      3  * was originally made for tircd and modified to work with u4.
      4  * - 2018 i <ircd@servx.org>
      5  * - 2019 Bram Matthys (Syzop) <syzop@vulnscan.org>
      6  * License: GPLv2 or later
      7  */
      8 
      9 #include "unrealircd.h"
     10 
     11 /* Module header */
     12 ModuleHeader MOD_HEADER
     13   = {
     14 	"whox",
     15 	"5.0",
     16 	"command /who",
     17 	"UnrealIRCd Team",
     18 	"unrealircd-6",
     19     };
     20 
     21 
     22 /* Defines */
     23 #define FIELD_CHANNEL	0x0001
     24 #define FIELD_HOP	0x0002
     25 #define FIELD_FLAGS	0x0004
     26 #define FIELD_HOST	0x0008
     27 #define FIELD_IP	0x0010
     28 #define FIELD_IDLE	0x0020
     29 #define FIELD_NICK	0x0040
     30 #define FIELD_INFO	0x0080
     31 #define FIELD_SERVER	0x0100
     32 #define FIELD_QUERYTYPE	0x0200 /* cookie for client */
     33 #define FIELD_USER	0x0400
     34 #define FIELD_ACCOUNT	0x0800
     35 #define FIELD_OPLEVEL	0x1000 /* meaningless and stupid, but whatever */
     36 #define FIELD_REALHOST	0x2000
     37 #define FIELD_MODES	0x4000
     38 #define FIELD_REPUTATION	0x8000
     39 
     40 #define WMATCH_NICK	0x0001
     41 #define WMATCH_USER	0x0002
     42 #define WMATCH_OPER	0x0004
     43 #define WMATCH_HOST	0x0008
     44 #define WMATCH_INFO	0x0010
     45 #define WMATCH_SERVER	0x0020
     46 #define WMATCH_ACCOUNT	0x0040
     47 #define WMATCH_IP	0x0080
     48 #define WMATCH_MODES	0x0100
     49 #define WMATCH_CONTIME	0x0200
     50 
     51 #define RPL_WHOSPCRPL	354
     52 
     53 #define WHO_ADD 1
     54 #define WHO_DEL 0
     55 
     56 #define HasField(x, y) ((x)->fields & (y))
     57 #define IsMatch(x, y) ((x)->matchsel & (y))
     58 
     59 #define IsMarked(x)           (moddata_client(x, whox_md).l)
     60 #define SetMark(x)            do { moddata_client(x, whox_md).l = 1; } while(0)
     61 #define ClearMark(x)          do { moddata_client(x, whox_md).l = 0; } while(0)
     62 
     63 /* Structs */
     64 struct who_format
     65 {
     66 	int fields;
     67 	int matchsel;
     68 	int umodes;
     69 	int noumodes;
     70 	const char *querytype;
     71 	int show_realhost;
     72 	int show_ip;
     73 	time_t contimemin;
     74 	time_t contimemax;
     75 };
     76 
     77 /* Global variables */
     78 ModDataInfo *whox_md = NULL;
     79 
     80 /* Forward declarations */
     81 CMD_FUNC(cmd_whox);
     82 static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt);
     83 static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt);
     84 static void do_who_on_channel(Client *client, Channel *channel,
     85                               int member, int operspy, struct who_format *fmt);
     86 static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt);
     87 const char *whox_md_serialize(ModData *m);
     88 void whox_md_unserialize(const char *str, ModData *m);
     89 void whox_md_free(ModData *md);
     90 static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...) __attribute__((format(printf,4,5)));
     91 
     92 MOD_INIT()
     93 {
     94 	ModDataInfo mreq;
     95 
     96 	MARK_AS_OFFICIAL_MODULE(modinfo);
     97 
     98 	if (!CommandAdd(modinfo->handle, "WHO", cmd_whox, MAXPARA, CMD_USER))
     99 	{
    100 		config_warn("You cannot load both cmd_whox and cmd_who. You should ONLY load the cmd_whox module.");
    101 		return MOD_FAILED;
    102 	}
    103 
    104 	memset(&mreq, 0, sizeof(mreq));
    105 	mreq.name = "whox";
    106 	mreq.type = MODDATATYPE_CLIENT;
    107 	mreq.serialize = whox_md_serialize;
    108 	mreq.unserialize = whox_md_unserialize;
    109 	mreq.free = whox_md_free;
    110 	mreq.sync = 0;
    111 	whox_md = ModDataAdd(modinfo->handle, mreq);
    112 	if (!whox_md)
    113 	{
    114 		config_error("could not register whox moddata");
    115 		return MOD_FAILED;
    116 	}
    117 
    118 	ISupportAdd(modinfo->handle, "WHOX", NULL);
    119 	return MOD_SUCCESS;
    120 }
    121 
    122 MOD_LOAD()
    123 {
    124 	return MOD_SUCCESS;
    125 }
    126 
    127 MOD_UNLOAD()
    128 {
    129 	return MOD_SUCCESS;
    130 }
    131 
    132 /** whox module data operations: serialize (rare) */
    133 const char *whox_md_serialize(ModData *m)
    134 {
    135 	static char buf[32];
    136 	if (m->i == 0)
    137 		return NULL; /* not set */
    138 	snprintf(buf, sizeof(buf), "%d", m->i);
    139 	return buf;
    140 }
    141 
    142 /** whox module data operations: unserialize (rare) */
    143 void whox_md_unserialize(const char *str, ModData *m)
    144 {
    145 	m->i = atoi(str);
    146 }
    147 
    148 /** whox module data operations: free */
    149 void whox_md_free(ModData *md)
    150 {
    151 	/* we have nothing to free actually, but we must set to zero */
    152 	md->l = 0;
    153 }
    154 
    155 /** cmd_whox: standardized "extended" version of WHO.
    156  * The good thing about WHOX is that it allows the client to define what
    157  * output they want to see. Another good thing is that it is standardized
    158  * (ok, actually it isn't... but hey!).
    159  * The bad thing is that it's a lot less flexible than the original WHO
    160  * we had in UnrealIRCd in 3.2.x and 4.0.x and that the WHOX spec defines
    161  * that there are two ways to specify a mask (neither one is logical):
    162  * WHO <mask> <flags>
    163  * WHO <mask> <flags> <mask2>
    164  * In case the latter is present then mask2 overrides the mask
    165  * (and the original 'mask' is ignored)
    166  * Yes, this indeed damn ugly. It shows (yet again) that they should never
    167  * have put the mask before the flags.. what kind of logic was that?
    168  * I wonder if the person who invented this also writes C functions like:
    169  * int (char *param)func
    170  * or types:
    171  * cp mode,time=--preserve
    172  * or creates configuration files like:
    173  * Syzop oper {
    174  * 5 maxlogins;
    175  * I mean... really...? -- Syzop
    176  * So, we will try to abide to the WHOX spec as much as possible but
    177  * try to warn our users in case they use the 'original' UnrealIRCd
    178  * WHO syntax which was WHO [+-]<flags> <mask>.
    179  * Note that we won't catch all cases, but we do our best.
    180  * When in doubt, we will assume WHOX so to not break the spec
    181  * (which again.. doesn't exist... but hey..)
    182  */
    183 CMD_FUNC(cmd_whox)
    184 {
    185 	char *mask;
    186 	const char *orig_mask;
    187 	char ch; /* Scratch char register */
    188 	const char *p; /* Scratch char pointer */
    189 	int member;
    190 	int operspy = 0;
    191 	struct who_format fmt;
    192 	const char *s;
    193 	char maskcopy[BUFSIZE];
    194 	Membership *lp;
    195 	Client *acptr;
    196 
    197 	memset(&fmt, 0, sizeof(fmt));
    198 
    199 	if (!MyUser(client))
    200 		return;
    201 
    202 	if ((parc < 2))
    203 	{
    204 		sendnumeric(client, ERR_NEEDMOREPARAMS, "WHO");
    205 		return;
    206 	}
    207 
    208 	if ((parc > 3) && parv[3])
    209 		orig_mask = parv[3];
    210 	else
    211 		orig_mask = parv[1];
    212 
    213 	if (!convert_classical_who_request(client, &parc, parv, &orig_mask, &fmt))
    214 		return;
    215 
    216 	/* Evaluate the flags now, we consider the second parameter
    217 	 * as "matchFlags%fieldsToInclude,querytype" */
    218 
    219 	if ((parc > 2) && parv[2] && *parv[2])
    220 	{
    221 		p = parv[2];
    222 		while (((ch = *(p++))) && (ch != '%') && (ch != ','))
    223 		{
    224 			switch (ch)
    225 			{
    226 				case 'o': fmt.matchsel |= WMATCH_OPER; continue;
    227 				case 'n': fmt.matchsel |= WMATCH_NICK; continue;
    228 				case 'u': fmt.matchsel |= WMATCH_USER; continue;
    229 				case 'h': fmt.matchsel |= WMATCH_HOST; continue;
    230 				case 'i': fmt.matchsel |= WMATCH_IP; continue;
    231 				case 'r': fmt.matchsel |= WMATCH_INFO; continue;
    232 				case 's': fmt.matchsel |= WMATCH_SERVER; continue;
    233 				case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue;
    234 				case 'm': fmt.matchsel |= WMATCH_MODES; continue;
    235 				case 't': fmt.matchsel |= WMATCH_CONTIME; continue;
    236 				case 'R':
    237 					if (IsOper(client))
    238 						fmt.show_realhost = 1;
    239 					continue;
    240 				case 'I':
    241 					if (IsOper(client))
    242 						fmt.show_ip = 1;
    243 					continue;
    244 			}
    245 		}
    246 	}
    247 
    248 	if ((parc > 2) && (s = strchr(parv[2], '%')) != NULL)
    249 	{
    250 		s++;
    251 		for (; *s != '\0'; s++)
    252 		{
    253 			switch (*s)
    254 			{
    255 				case 'c': fmt.fields |= FIELD_CHANNEL; break;
    256 				case 'd': fmt.fields |= FIELD_HOP; break;
    257 				case 'f': fmt.fields |= FIELD_FLAGS; break;
    258 				case 'h': fmt.fields |= FIELD_HOST; break;
    259 				case 'H': fmt.fields |= FIELD_REALHOST; break;
    260 				case 'i': fmt.fields |= FIELD_IP; break;
    261 				case 'l': fmt.fields |= FIELD_IDLE; break;
    262 				case 'n': fmt.fields |= FIELD_NICK; break;
    263 				case 'r': fmt.fields |= FIELD_INFO; break;
    264 				case 's': fmt.fields |= FIELD_SERVER; break;
    265 				case 't': fmt.fields |= FIELD_QUERYTYPE; break;
    266 				case 'u': fmt.fields |= FIELD_USER; break;
    267 				case 'a': fmt.fields |= FIELD_ACCOUNT; break;
    268 				case 'm': fmt.fields |= FIELD_MODES; break;
    269 				case 'o': fmt.fields |= FIELD_OPLEVEL; break;
    270 				case 'R': fmt.fields |= FIELD_REPUTATION; break;
    271 				case ',':
    272 					s++;
    273 					fmt.querytype = s;
    274 					s += strlen(s);
    275 					s--;
    276 					break;
    277 			}
    278 		}
    279 		if (BadPtr(fmt.querytype) || (strlen(fmt.querytype) > 3))
    280 			fmt.querytype = "0";
    281 	}
    282 
    283 	strlcpy(maskcopy, orig_mask, sizeof maskcopy);
    284 	mask = maskcopy;
    285 
    286 	collapse(mask);
    287 
    288 	if ((ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) &&
    289 	     ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL)))
    290 	{
    291 		operspy = 1;
    292 	}
    293 
    294 	if (fmt.matchsel & WMATCH_MODES)
    295 	{
    296 		char *s = mask;
    297 		int *umodes;
    298 		int what = WHO_ADD;
    299 	
    300 		while (*s)
    301 		{
    302 			Umode *um;
    303 
    304 			switch (*s)
    305 			{
    306 				case '+':
    307 					what = WHO_ADD;
    308 					s++;
    309 					break;
    310 				case '-':
    311 					what = WHO_DEL;
    312 					s++;
    313 					break;
    314 			}
    315 
    316 			if (!*s)
    317 				break;
    318 
    319 			if (what == WHO_ADD)
    320 				umodes = &fmt.umodes;
    321 			else
    322 				umodes = &fmt.noumodes;
    323 
    324 			for (um = usermodes; um; um = um->next)
    325 			{
    326 				if (um->letter == *s)
    327 				{
    328 					*umodes |= um->mode;
    329 					break;
    330 				}
    331 			}
    332 			s++;
    333 		}
    334 
    335 		if (!IsOper(client))
    336 		{
    337 			/* these are usermodes regular users may search for. just oper now. */
    338 			fmt.umodes &= UMODE_OPER;
    339 			fmt.noumodes &= UMODE_OPER;
    340 		}
    341 	}
    342 
    343 	/* match connect time */
    344 	if (fmt.matchsel & WMATCH_CONTIME)
    345 	{
    346 		char *s = mask;
    347 		time_t currenttime = TStime();
    348 
    349 		fmt.contimemin = 0;
    350 		fmt.contimemax = 0;
    351 
    352 		switch (*s)
    353 		{
    354 			case '<':
    355 				if (*s++)
    356 					fmt.contimemin = currenttime - config_checkval(s, CFG_TIME);
    357 				break;
    358 			case '>':
    359 				if (*s++)
    360 					fmt.contimemax = currenttime - config_checkval(s, CFG_TIME);
    361 				break;
    362 		}
    363 	}
    364 
    365 	/* '/who #some_channel' */
    366 	if (IsChannelName(mask))
    367 	{
    368 		Channel *channel = NULL;
    369 
    370 		/* List all users on a given channel */
    371 		if ((channel = find_channel(orig_mask)) != NULL)
    372 		{
    373 			if (IsMember(client, channel) || operspy)
    374 				do_who_on_channel(client, channel, 1, operspy, &fmt);
    375 			else if (!SecretChannel(channel))
    376 				do_who_on_channel(client, channel, 0, operspy, &fmt);
    377 		}
    378 
    379 		sendnumeric(client, RPL_ENDOFWHO, mask);
    380 		return;
    381 	}
    382 
    383 	if (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) ||
    384                 ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL))
    385 	{
    386 		operspy = 1;
    387 	}
    388 
    389 	/* '/who 0' for a global list.  this forces clients to actually
    390 	 * request a full list.  I presume its because of too many typos
    391 	 * with "/who" ;) --fl
    392 	 */
    393 	if (!strcmp(mask, "0"))
    394 		who_global(client, NULL, 0, &fmt);
    395 	else
    396 		who_global(client, mask, operspy, &fmt);
    397 
    398 	sendnumeric(client, RPL_ENDOFWHO, mask);
    399 }
    400 
    401 /* do_match
    402  * inputs	- pointer to client requesting who
    403  *		- pointer to client to do who on
    404  *		- char * mask to match
    405  *		- format options
    406  * output	- 1 if match, 0 if no match
    407  * side effects	- NONE
    408  */
    409 static int do_match(Client *client, Client *acptr, char *mask, struct who_format *fmt)
    410 {
    411 	if (mask == NULL)
    412 		return 1;
    413 
    414 	/* default */
    415 	if (fmt->matchsel == 0 && (match_simple(mask, acptr->name) ||
    416 		match_simple(mask, acptr->user->username) ||
    417 		match_simple(mask, GetHost(acptr)) ||
    418 		(IsOper(client) &&
    419 		(match_simple(mask, acptr->user->realhost) ||
    420 		(acptr->ip &&
    421 		match_simple(mask, acptr->ip))))))
    422 	{
    423 		return 1;
    424 	}
    425 
    426 	/* match nick */
    427 	if (IsMatch(fmt, WMATCH_NICK) && match_simple(mask, acptr->name))
    428 		return 1;
    429 
    430 	/* match username */
    431 	if (IsMatch(fmt, WMATCH_USER) && match_simple(mask, acptr->user->username))
    432 		return 1;
    433 
    434 	/* match server */
    435 	if (IsMatch(fmt, WMATCH_SERVER) && IsOper(client) && match_simple(mask, acptr->user->server))
    436 		return 1;
    437 
    438 	/* match hostname */
    439 	if (IsMatch(fmt, WMATCH_HOST) && (match_simple(mask, GetHost(acptr)) ||
    440 		(IsOper(client) && (match_simple(mask, acptr->user->realhost) ||
    441 		(acptr->ip && match_simple(mask, acptr->ip))))))
    442 	{
    443 		return 1;
    444 	}
    445 
    446 	/* match realname */
    447 	if (IsMatch(fmt, WMATCH_INFO) && match_simple(mask, acptr->info))
    448 		return 1;
    449 
    450 	/* match ip address */
    451 	if (IsMatch(fmt, WMATCH_IP) && IsOper(client) && acptr->ip &&
    452 		match_user(mask, acptr, MATCH_CHECK_IP))
    453 		return 1;
    454 
    455 	/* match account */
    456 	if (IsMatch(fmt, WMATCH_ACCOUNT) && IsLoggedIn(acptr) && match_simple(mask, acptr->user->account))
    457 	{
    458 		return 1;
    459 	}
    460 
    461 	/* match usermodes */
    462 	if (IsMatch(fmt, WMATCH_MODES) && (fmt->umodes || fmt->noumodes))
    463 	{
    464 		long umodes = acptr->umodes;
    465 		if ((acptr->umodes & UMODE_HIDEOPER) && !IsOper(client))
    466 			umodes &= ~UMODE_OPER; /* pretend -o if +H */
    467 		/* Now check 'umodes' (not acptr->umodes),
    468 		 * If multiple conditions are specified it is an
    469 		 * AND condition and not an OR.
    470 		 */
    471 		if (((umodes & fmt->umodes) == fmt->umodes) &&
    472 		    ((umodes & fmt->noumodes) == 0))
    473 		{
    474 			return 1;
    475 		}
    476 	}
    477 
    478 	/* match connect time */
    479 	if (IsMatch(fmt, WMATCH_CONTIME) && MyConnect(acptr) && (fmt->contimemin || fmt->contimemax))
    480 	{
    481 		if (fmt->contimemin && (acptr->local->creationtime > fmt->contimemin))
    482 			return 1;
    483 
    484 		if (fmt->contimemax && (acptr->local->creationtime < fmt->contimemax))
    485 			return 1;
    486 	}
    487 
    488 	return 0;
    489 }
    490 
    491 /* who_common_channel
    492  * inputs		- pointer to client requesting who
    493  *			- pointer to channel.
    494  *			- char * mask to match
    495  *			- int if oper on a server or not
    496  *			- pointer to int maxmatches
    497  *			- format options
    498  * output		- NONE
    499  * side effects 	- lists matching clients on specified channel,
    500  *			  marks matched clients.
    501  *
    502  * NOTE: only call this from who_global() due to client marking!
    503  */
    504 
    505 static void who_common_channel(Client *client, Channel *channel,
    506 	char *mask, int *maxmatches, struct who_format *fmt)
    507 {
    508 	Member *cm = channel->members;
    509 	Client *acptr;
    510 	Hook *h;
    511 	int i = 0;
    512 
    513 	for (cm = channel->members; cm; cm = cm->next)
    514 	{
    515 		acptr = cm->client;
    516 
    517 		if (IsMarked(acptr))
    518 			continue;
    519 
    520 		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
    521 			continue;
    522 
    523 		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
    524 		{
    525 			i = (*(h->func.intfunc))(acptr,channel);
    526 			if (i != 0)
    527 				break;
    528 		}
    529 
    530 		if (i != 0 && !(check_channel_access(client, channel, "hoaq")) && !(check_channel_access(acptr, channel, "hoaq") || check_channel_access(acptr,channel, "v")))
    531 			continue;
    532 
    533 		SetMark(acptr);
    534 
    535 		if (*maxmatches > 0)
    536 		{
    537 			if (do_match(client, acptr, mask, fmt))
    538 			{
    539 				do_who(client, acptr, NULL, fmt);
    540 				--(*maxmatches);
    541 			}
    542 		}
    543 	}
    544 }
    545 
    546 /*
    547  * who_global
    548  *
    549  * inputs		- pointer to client requesting who
    550  *			- char * mask to match
    551  *			- int if oper on a server or not
    552  *			- int if operspy or not
    553  *			- format options
    554  * output		- NONE
    555  * side effects		- do a global scan of all clients looking for match
    556  *			  this is slightly expensive on EFnet ...
    557  *			  marks assumed cleared for all clients initially
    558  *			  and will be left cleared on return
    559  */
    560 
    561 static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt)
    562 {
    563 	Client *hunted = NULL;
    564 	Client *acptr;
    565 	int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT;
    566 
    567 	/* If searching for a nick explicitly, then include it later on in the result: */
    568 	if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0)))
    569 		hunted = find_user(mask, NULL);
    570 
    571 	/* Initialize the markers to zero */
    572 	list_for_each_entry(acptr, &client_list, client_node)
    573 		ClearMark(acptr);
    574 
    575 	/* First, if not operspy, then list all matching clients on common channels */
    576 	if (!operspy)
    577 	{
    578 		Membership *lp;
    579 
    580 		for (lp = client->user->channel; lp; lp = lp->next)
    581 			who_common_channel(client, lp->channel, mask, &maxmatches, fmt);
    582 	}
    583 
    584 	/* Second, list all matching visible clients. */
    585 	list_for_each_entry(acptr, &client_list, client_node)
    586 	{
    587 		if (!IsUser(acptr))
    588 			continue;
    589 
    590 		if (IsInvisible(acptr) && !operspy && (client != acptr) && (acptr != hunted))
    591 			continue;
    592 
    593 		if (IsMarked(acptr))
    594 			continue;
    595 
    596 		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
    597 			continue;
    598 
    599 		if (maxmatches > 0)
    600 		{
    601 			if (do_match(client, acptr, mask, fmt))
    602  			{
    603 				do_who(client, acptr, NULL, fmt);
    604 				--maxmatches;
    605  			}
    606  		}
    607 	}
    608 
    609 	if (maxmatches <= 0)
    610 		sendnumeric(client, ERR_TOOMANYMATCHES, "WHO", "output too large, truncated");
    611 }
    612 
    613 /*
    614  * do_who_on_channel
    615  *
    616  * inputs		- pointer to client requesting who
    617  *			- pointer to channel to do who on
    618  *			- The "real name" of this channel
    619  *			- int if client is a server oper or not
    620  *			- int if client is member or not
    621  *			- format options
    622  * output		- NONE
    623  * side effects		- do a who on given channel
    624  */
    625 
    626 static void do_who_on_channel(Client *client, Channel *channel,
    627 	int member, int operspy, struct who_format *fmt)
    628 {
    629 	Member *cm = channel->members;
    630 	Hook *h;
    631 	int i = 0;
    632 
    633 	for (cm = channel->members; cm; cm = cm->next)
    634 	{
    635 		Client *acptr = cm->client;
    636 
    637 		if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr))
    638 			continue;
    639 
    640 		for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
    641 		{
    642 			i = (*(h->func.intfunc))(acptr,channel);
    643 			if (i != 0)
    644 				break;
    645 		}
    646 
    647 		if (!operspy && (acptr != client) && i != 0 && !(check_channel_access(client, channel, "hoaq")) && !(check_channel_access(acptr, channel, "hoaq") || check_channel_access(acptr,channel, "v")))
    648 			continue;
    649 
    650 		if (member || !IsInvisible(acptr))
    651 			do_who(client, acptr, channel, fmt);
    652 	}
    653 }
    654 
    655 /*
    656  * append_format
    657  *
    658  * inputs		- pointer to buffer
    659  *			- size of buffer
    660  *			- pointer to position
    661  *			- format string
    662  *			- arguments for format
    663  * output		- NONE
    664  * side effects 	- position incremented, possibly beyond size of buffer
    665  *			  this allows detecting overflow
    666  */
    667 
    668 static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...)
    669 {
    670 	size_t max, result;
    671 	va_list ap;
    672 
    673 	max = *pos >= bufsize ? 0 : bufsize - *pos;
    674 	va_start(ap, fmt);
    675 	result = vsnprintf(buf + *pos, max, fmt, ap);
    676 	va_end(ap);
    677 	*pos += result;
    678 }
    679 
    680 /*
    681  * show_ip()		- asks if the true IP should be shown when source is
    682  *			  asking for info about target
    683  *
    684  * Inputs		- client who is asking
    685  *			- acptr who do we want the info on
    686  * Output		- returns 1 if clear IP can be shown, otherwise 0
    687  * Side Effects		- none
    688  */
    689 
    690 static int show_ip(Client *client, Client *acptr)
    691 {
    692 	if (IsServer(acptr))
    693 		return 0;
    694 	else if ((client != NULL) && (MyConnect(client) && !IsOper(client)) && (client == acptr))
    695 		return 1;
    696 	else if (IsHidden(acptr) && ((client != NULL) && !IsOper(client)))
    697 		return 0;
    698 	else
    699 		return 1;
    700 }
    701 
    702 /*
    703  * do_who
    704  *
    705  * inputs	- pointer to client requesting who
    706  *		- pointer to client to do who on
    707  *		- channel or NULL
    708  *		- format options
    709  * output	- NONE
    710  * side effects - do a who on given person
    711  */
    712 
    713 static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt)
    714 {
    715 	char status[20];
    716 	char str[510 + 1];
    717 	size_t pos;
    718 	int hide = (FLAT_MAP && !IsOper(client)) ? 1 : 0;
    719 	int i = 0;
    720 	Hook *h;
    721 
    722 	if (acptr->user->away)
    723  		status[i++] = 'G';
    724  	else
    725  		status[i++] = 'H';
    726 
    727 	if (IsRegNick(acptr))
    728 		status[i++] = 'r';
    729 
    730 	if (IsSecureConnect(acptr))
    731 		status[i++] = 's';
    732 
    733 	for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next)
    734 	{
    735 		int ret = (*(h->func.intfunc))(client, acptr, NULL, NULL, status, 0);
    736 		if (ret != 0)
    737 			status[i++] = (char)ret;
    738 	}
    739 
    740 	if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client)))
    741 		status[i++] = '*';
    742 
    743 	if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client)))
    744 		status[i++] = '!';
    745 
    746 	if (channel)
    747 	{
    748 		Membership *lp;
    749 
    750 		if ((lp = find_membership_link(acptr->user->channel, channel)))
    751 		{
    752 			if (!(fmt->fields || HasCapability(client, "multi-prefix")))
    753 			{
    754 				/* Standard NAMES reply (single character) */
    755 				char c = mode_to_prefix(*lp->member_modes);
    756 				if (c)
    757 					status[i++] = c;
    758 			}
    759 			else
    760 			{
    761 				/* NAMES reply with all rights included (multi-prefix / NAMESX) */
    762 				strcpy(&status[i], modes_to_prefix(lp->member_modes));
    763 				i += strlen(&status[i]);
    764 			}
    765 		}
    766 	}
    767 
    768 	status[i] = '\0';
    769  
    770 	if (fmt->fields == 0)
    771 	{
    772 		char *host;
    773 		if (fmt->show_realhost)
    774 			host = acptr->user->realhost;
    775 		else if (fmt->show_ip)
    776 			host = GetIP(acptr);
    777 		else
    778 			host = GetHost(acptr);
    779 		sendnumeric(client, RPL_WHOREPLY,
    780 			channel ? channel->name : "*",
    781 			acptr->user->username, host,
    782 			hide ? "*" : acptr->user->server,
    783 			acptr->name, status, hide ? 0 : acptr->hopcount, acptr->info);
    784 	} else
    785 	{
    786 		str[0] = '\0';
    787 		pos = 0;
    788 		append_format(str, sizeof str, &pos, ":%s %d %s", me.name, RPL_WHOSPCRPL, client->name);
    789 		if (HasField(fmt, FIELD_QUERYTYPE))
    790 			append_format(str, sizeof str, &pos, " %s", fmt->querytype);
    791 		if (HasField(fmt, FIELD_CHANNEL))
    792 			append_format(str, sizeof str, &pos, " %s", channel ? channel->name : "*");
    793 		if (HasField(fmt, FIELD_USER))
    794 			append_format(str, sizeof str, &pos, " %s", acptr->user->username);
    795 		if (HasField(fmt, FIELD_IP))
    796 		{
    797 			if (show_ip(client, acptr) && acptr->ip)
    798 				append_format(str, sizeof str, &pos, " %s", acptr->ip);
    799 			else
    800 				append_format(str, sizeof str, &pos, " %s", "255.255.255.255");
    801 		}
    802 
    803 		if (HasField(fmt, FIELD_HOST) || HasField(fmt, FIELD_REALHOST))
    804 		{
    805 			if (IsOper(client) && HasField(fmt, FIELD_REALHOST))
    806 				append_format(str, sizeof str, &pos, " %s", acptr->user->realhost);
    807 			else
    808 				append_format(str, sizeof str, &pos, " %s", GetHost(acptr));
    809 		}
    810 
    811 		if (HasField(fmt, FIELD_SERVER))
    812 			append_format(str, sizeof str, &pos, " %s", hide ? "*" : acptr->user->server);
    813 		if (HasField(fmt, FIELD_NICK))
    814 			append_format(str, sizeof str, &pos, " %s", acptr->name);
    815 		if (HasField(fmt, FIELD_FLAGS))
    816 			append_format(str, sizeof str, &pos, " %s", status);
    817 		if (HasField(fmt, FIELD_MODES))
    818 		{
    819 			if (IsOper(client))
    820 			{
    821 				const char *umodes = get_usermode_string(acptr);
    822 				if (*umodes == '+')
    823 					umodes++;
    824 				append_format(str, sizeof str, &pos, " %s", umodes);
    825 			} else {
    826 				append_format(str, sizeof str, &pos, " %s", "*");
    827 			}
    828 		}
    829 		if (HasField(fmt, FIELD_HOP))
    830 			append_format(str, sizeof str, &pos, " %d", hide ? 0 : acptr->hopcount);
    831 		if (HasField(fmt, FIELD_IDLE))
    832 		{
    833 			append_format(str, sizeof str, &pos, " %d",
    834 				(int)((MyUser(acptr) && !hide_idle_time(client, acptr)) ? (TStime() - acptr->local->idle_since) : 0));
    835 		}
    836 		if (HasField(fmt, FIELD_ACCOUNT))
    837 			append_format(str, sizeof str, &pos, " %s", IsLoggedIn(acptr) ? acptr->user->account : "0");
    838 		if (HasField(fmt, FIELD_OPLEVEL))
    839 			append_format(str, sizeof str, &pos, " %s", (channel && check_channel_access(acptr, channel, "hoaq")) ? "999" : "n/a");
    840 		if (HasField(fmt, FIELD_REPUTATION))
    841 		{
    842 			if (IsOper(client))
    843 				append_format(str, sizeof str, &pos, " %d", GetReputation(acptr));
    844 			else
    845 				append_format(str, sizeof str, &pos, " %s", "*");
    846 		}
    847 		if (HasField(fmt, FIELD_INFO))
    848 			append_format(str, sizeof str, &pos, " :%s", acptr->info);
    849 
    850 		sendto_one(client, NULL, "%s", str);
    851 	}
    852 }
    853 
    854 /* Yeah, this is fun. Thank you WHOX !!! */
    855 static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt)
    856 {
    857 	const char *p;
    858 	static char pbuf1[512], pbuf2[512];
    859 	int points;
    860 
    861 	/* Figure out if the user is doing a 'classical' UnrealIRCd request,
    862 	 * which can be recognized as follows:
    863 	 * 1) Always a + or - as 1st character for the 1st parameter.
    864 	 * 2) Unlikely to have a % (percent sign) in the 2nd parameter
    865 	 * 3) Unlikely to have a 3rd parameter
    866 	 * On the other hand WHOX requests are:
    867 	 * 4) never going to have a '*', '?' or '.' as 2nd parameter
    868 	 * 5) never going to have a '+' or '-' as 1st character in 2nd parameter
    869 	 * Additionally, WHOX requests are useless - and thus unlikely -
    870 	 * to search for a mask mask starting with + or -  except when:
    871 	 * 6) searching for 'm' (mode)
    872 	 * 7) or 'r' (info, realname)
    873 	 * ..for which this would be a meaningful request.
    874 	 * The end result is that we can do quite some useful heuristics
    875 	 * except for some corner cases.
    876 	 */
    877 	if (((*parv[1] == '+') || (*parv[1] == '-')) &&
    878 	    (!parv[2] || !strchr(parv[2], '%')) &&
    879 	    (*parc < 4))
    880 	{
    881 		/* Conditions 1-3 of above comment are met, now we deal
    882 		 * with conditions 4-7.
    883 		 */
    884 		if (parv[2] &&
    885 		    !strchr(parv[2], '*') && !strchr(parv[2], '?') &&
    886 		    !strchr(parv[2], '.') &&
    887 		    !strchr(parv[2], '+') && !strchr(parv[2], '-') &&
    888 		    (strchr(parv[2], 'm') || strchr(parv[2], 'r')))
    889 		{
    890 			/* 'WHO +something m" or even
    891 			 * 'WHO +c #something' (which includes 'm')
    892 			 * could mean either WHO or WHOX style
    893 			 */
    894 		} else {
    895 			/* If we get here then it's an classical
    896 			 * UnrealIRCd-style WHO request which has
    897 			 * the order: WHO <options> <mask>
    898 			 */
    899 			char oldrequest[256];
    900 			snprintf(oldrequest, sizeof(oldrequest), "WHO %s%s%s",
    901 			         parv[1], parv[2] ? " " : "", parv[2] ? parv[2] : "");
    902 			if (parv[2])
    903 			{
    904 				const char *swap = parv[1];
    905 				parv[1] = parv[2];
    906 				parv[2] = swap;
    907 			} else {
    908 				/* A request like 'WHO +I' or 'WHO +R' */
    909 				parv[2] = parv[1];
    910 				parv[1] = "*";
    911 				parv[3] = NULL;
    912 				*parc = 3;
    913 			}
    914 
    915 			/* Ok, that was the first step, now we need to convert the
    916 			 * flags since they have changed a little as well:
    917 			 * Flag a: user is away                                            << no longer exists
    918 			 * Flag c <channel>: user is on <channel>                          << no longer exists
    919 			 * Flag g <gcos/realname>: user has string <gcos> in his/her GCOS  << now called 'r'
    920 			 * Flag h <host>: user has string <host> in his/her hostname       << no change
    921 			 * Flag i <ip>: user has string <ip> in his/her IP address         << no change
    922 			 * Flag m <usermodes>: user has <usermodes> set                    << behavior change
    923 			 * Flag n <nick>: user has string <nick> in his/her nickname       << no change
    924 			 * Flag s <server>: user is on server <server>                     << no change
    925 			 * Flag u <user>: user has string <user> in his/her username       << no change
    926 			 * Behavior flags:
    927 			 * Flag M: check for user in channels I am a member of             << no longer exists
    928 			 * Flag R: show users' real hostnames                              << no change (re-added)
    929 			 * Flag I: show users' IP addresses                                << no change (re-added)
    930 			 */
    931 
    932 			if (strchr(parv[2], 'a'))
    933 			{
    934 				sendnotice(client, "WHO request '%s' failed: flag 'a' no longer exists with WHOX.", oldrequest);
    935 				return 0;
    936 			}
    937 			if (strchr(parv[2], 'c'))
    938 			{
    939 				sendnotice(client, "WHO request '%s' failed: flag 'c' no longer exists with WHOX.", oldrequest);
    940 				return 0;
    941 			}
    942 			if (strchr(parv[2], 'g'))
    943 			{
    944 				char *w;
    945 				strlcpy(pbuf2, parv[2], sizeof(pbuf2));
    946 				for (w = pbuf2; *w; w++)
    947 				{
    948 					if (*w == 'g')
    949 					{
    950 						*w = 'r';
    951 						break;
    952 					}
    953 				}
    954 				parv[2] = pbuf2;
    955 			}
    956 
    957 			/* "WHO -m xyz" (now: xyz -m) should become "WHO -xyz m"
    958 			 * Wow, this seems overly complex, but okay...
    959 			 */
    960 			points = 0;
    961 			for (p = parv[2]; *p; p++)
    962 			{
    963 				if (*p == '-')
    964 					points = 1;
    965 				else if (*p == '+')
    966 					points = 0;
    967 				else if (points && (*p == 'm'))
    968 				{
    969 					points = 2;
    970 					break;
    971 				}
    972 			}
    973 			if (points == 2)
    974 			{
    975 				snprintf(pbuf1, sizeof(pbuf1), "-%s", parv[1]);
    976 				parv[1] = pbuf1;
    977 			}
    978 
    979 			if ((*parv[2] == '+') || (*parv[2] == '-'))
    980 				parv[2] = parv[2]+1; /* strip '+'/'-' prefix, which does not exist in WHOX */
    981 
    982 			sendnotice(client, "WHO request '%s' changed to match new WHOX syntax: 'WHO %s %s'",
    983 				oldrequest, parv[1], parv[2]);
    984 			*orig_mask = parv[1];
    985 		}
    986 	}
    987 	return 1;
    988 }