unrealircd

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

whois.c (21206B)

      1 /*
      2  *   Unreal Internet Relay Chat Daemon, src/modules/whois.c
      3  *   (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team
      4  *   (C) 2003-2021 Bram Matthys and the UnrealIRCd team
      5  *   Moved to modules by Fish (Justin Hammond) in 2001
      6  *
      7  *   This program is free software; you can redistribute it and/or modify
      8  *   it under the terms of the GNU General Public License as published by
      9  *   the Free Software Foundation; either version 1, or (at your option)
     10  *   any later version.
     11  *
     12  *   This program is distributed in the hope that it will be useful,
     13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15  *   GNU General Public License for more details.
     16  *
     17  *   You should have received a copy of the GNU General Public License
     18  *   along with this program; if not, write to the Free Software
     19  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     20  */
     21 
     22 #include "unrealircd.h"
     23 
     24 /* Structs */
     25 ModuleHeader MOD_HEADER
     26   = {
     27 	"whois",	/* Name of module */
     28 	"5.0", /* Version */
     29 	"command /whois", /* Short description of module */
     30 	"UnrealIRCd Team",
     31 	"unrealircd-6",
     32     };
     33 
     34 typedef enum WhoisConfigUser {
     35 	WHOIS_CONFIG_USER_EVERYONE	= 1,
     36 	WHOIS_CONFIG_USER_SELF		= 2,
     37 	WHOIS_CONFIG_USER_OPER		= 3,
     38 } WhoisConfigUser;
     39 #define HIGHEST_WHOIS_CONFIG_USER_VALUE 3 /* adjust this if you edit the enum above !! */
     40 
     41 //this one is in include/struct.h because it needs full API exposure:
     42 //typedef enum WhoisConfigDetails {
     43 //	...
     44 //} WhoisConfigDetails;
     45 //
     46 
     47 typedef struct WhoisConfig WhoisConfig;
     48 struct WhoisConfig {
     49 	WhoisConfig *prev, *next;
     50 	char *name;
     51 	WhoisConfigDetails permissions[HIGHEST_WHOIS_CONFIG_USER_VALUE+1];
     52 };
     53 
     54 /* Global variables */
     55 WhoisConfig *whoisconfig = NULL;
     56 
     57 /* Forward declarations */
     58 WhoisConfigDetails _whois_get_policy(Client *client, Client *target, const char *name);
     59 CMD_FUNC(cmd_whois);
     60 static int whois_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     61 static int whois_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
     62 static void whois_config_setdefaults(void);
     63 
     64 MOD_TEST()
     65 {
     66 	MARK_AS_OFFICIAL_MODULE(modinfo);
     67 	EfunctionAdd(modinfo->handle, EFUNC_WHOIS_GET_POLICY, TO_INTFUNC(_whois_get_policy));
     68 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whois_config_test);
     69 	return MOD_SUCCESS;
     70 }
     71 
     72 MOD_INIT()
     73 {
     74 	MARK_AS_OFFICIAL_MODULE(modinfo);
     75 	CommandAdd(modinfo->handle, "WHOIS", cmd_whois, MAXPARA, CMD_USER);
     76 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whois_config_run);
     77 	whois_config_setdefaults();
     78 	return MOD_SUCCESS;
     79 }
     80 
     81 MOD_LOAD()
     82 {
     83 	return MOD_SUCCESS;
     84 }
     85 
     86 MOD_UNLOAD()
     87 {
     88 	return MOD_SUCCESS;
     89 }
     90 
     91 static WhoisConfig *find_whois_config(const char *name)
     92 {
     93 	WhoisConfig *w;
     94 	for (w = whoisconfig; w; w = w->next)
     95 		if (!strcmp(w->name, name))
     96 			return w;
     97 	return NULL;
     98 }
     99 
    100 /* Lazy helper for whois_config_setdefaults */
    101 static void whois_config_add(const char *name, WhoisConfigUser user, WhoisConfigDetails details)
    102 {
    103 	WhoisConfig *w = find_whois_config(name);
    104 
    105 	if (!w)
    106 	{
    107 		/* New one */
    108 		w = safe_alloc(sizeof(WhoisConfig));
    109 		safe_strdup(w->name, name);
    110 		AddListItem(w, whoisconfig);
    111 	}
    112 	w->permissions[user] = details;
    113 }
    114 
    115 static void whois_config_setdefaults(void)
    116 {
    117 	whois_config_add("basic", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    118 
    119 	whois_config_add("modes", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
    120 	whois_config_add("modes", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    121 
    122 	whois_config_add("realhost", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
    123 	whois_config_add("realhost", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    124 
    125 	whois_config_add("registered-nick", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    126 
    127 	whois_config_add("channels", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
    128 	whois_config_add("channels", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
    129 	whois_config_add("channels", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    130 
    131 	whois_config_add("server", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    132 
    133 	whois_config_add("away", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    134 
    135 	whois_config_add("oper", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
    136 	whois_config_add("oper", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
    137 	whois_config_add("oper", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    138 
    139 	whois_config_add("secure", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
    140 	whois_config_add("secure", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
    141 	whois_config_add("secure", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    142 
    143 	whois_config_add("bot", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    144 
    145 	whois_config_add("services", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    146 
    147 	whois_config_add("reputation", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    148 
    149 	whois_config_add("security-groups", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    150 
    151 	whois_config_add("geo", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    152 
    153 	whois_config_add("certfp", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    154 
    155 	whois_config_add("shunned", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    156 
    157 	whois_config_add("account", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    158 
    159 	whois_config_add("swhois", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL);
    160 
    161 	whois_config_add("idle", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED);
    162 	whois_config_add("idle", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL);
    163 	whois_config_add("idle", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL);
    164 }
    165 
    166 static void whois_free_config(void)
    167 {
    168 }
    169 
    170 static WhoisConfigUser whois_config_user_strtovalue(const char *str)
    171 {
    172 	if (!strcmp(str, "everyone"))
    173 		return WHOIS_CONFIG_USER_EVERYONE;
    174 	if (!strcmp(str, "self"))
    175 		return WHOIS_CONFIG_USER_SELF;
    176 	if (!strcmp(str, "oper"))
    177 		return WHOIS_CONFIG_USER_OPER;
    178 	return 0;
    179 }
    180 
    181 static WhoisConfigDetails whois_config_details_strtovalue(const char *str)
    182 {
    183 	if (!strcmp(str, "full"))
    184 		return WHOIS_CONFIG_DETAILS_FULL;
    185 	if (!strcmp(str, "limited"))
    186 		return WHOIS_CONFIG_DETAILS_LIMITED;
    187 	if (!strcmp(str, "none"))
    188 		return WHOIS_CONFIG_DETAILS_NONE;
    189 	return 0;
    190 }
    191 
    192 static int whois_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    193 {
    194 	int errors = 0;
    195 	ConfigEntry *cep, *cepp;
    196 
    197 	if (type != CONFIG_SET)
    198 		return 0;
    199 
    200 	/* We are only interrested in set::whois-details.. */
    201 	if (!ce || strcmp(ce->name, "whois-details"))
    202 		return 0;
    203 
    204 	for (cep = ce->items; cep; cep = cep->next)
    205 	{
    206 		if (cep->value)
    207 		{
    208 			config_error("%s:%i: set::whois-details::%s item has a value, which is unexpected. Check your syntax!",
    209 				cep->file->filename, cep->line_number, cep->name);
    210 			errors++;
    211 			continue;
    212 		}
    213 		for (cepp = cep->items; cepp; cepp = cepp->next)
    214 		{
    215 			if (!whois_config_user_strtovalue(cepp->name))
    216 			{
    217 				config_error("%s:%i: set::whois-details::%s contains unknown user category called '%s', must be one of: everyone, self, ircop",
    218 					cepp->file->filename, cepp->line_number, cep->name, cepp->name);
    219 				errors++;
    220 				continue;
    221 			} else
    222 			if (!cepp->value || !whois_config_details_strtovalue(cepp->value))
    223 			{
    224 				config_error("%s:%i: set::whois-details::%s contains unknown details type '%s', must be one of: full, limited, none",
    225 					cepp->file->filename, cepp->line_number, cep->name, cepp->name);
    226 				errors++;
    227 				continue;
    228 			} /* else it is good */
    229 		}
    230 	}
    231 
    232 	*errs = errors;
    233 	return errors ? -1 : 1;
    234 }
    235 
    236 static int whois_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
    237 {
    238 	ConfigEntry *cep, *cepp;
    239 
    240 	if (type != CONFIG_SET)
    241 		return 0;
    242 
    243 	/* We are only interrested in set::whois-details.. */
    244 	if (!ce || strcmp(ce->name, "whois-details"))
    245 		return 0;
    246 
    247 	for (cep = ce->items; cep; cep = cep->next)
    248 	{
    249 		WhoisConfig *w = find_whois_config(cep->name);
    250 		if (!w)
    251 		{
    252 			/* New one */
    253 			w = safe_alloc(sizeof(WhoisConfig));
    254 			safe_strdup(w->name, cep->name);
    255 			AddListItem(w, whoisconfig);
    256 		}
    257 		for (cepp = cep->items; cepp; cepp = cepp->next)
    258 		{
    259 			WhoisConfigUser user = whois_config_user_strtovalue(cepp->name);
    260 			WhoisConfigDetails details = whois_config_details_strtovalue(cepp->value);
    261 			w->permissions[user] = details;
    262 		}
    263 	}
    264 	return 1;
    265 }
    266 
    267 /** Get set::whois-details policy for an item.
    268  * @param client		The client doing the /WHOIS
    269  * @param target		The client being whoised, so the one to show all details for
    270  * @param name			The name of the whois item (eg "modes")
    271  */
    272 WhoisConfigDetails _whois_get_policy(Client *client, Client *target, const char *name)
    273 {
    274 	WhoisConfig *w = find_whois_config(name);
    275 	if (!w)
    276 		return WHOIS_CONFIG_DETAILS_DEFAULT;
    277 	if ((client == target) && (w->permissions[WHOIS_CONFIG_USER_SELF] > 0))
    278 		return w->permissions[WHOIS_CONFIG_USER_SELF];
    279 	if (IsOper(client) && (w->permissions[WHOIS_CONFIG_USER_OPER] > 0))
    280 		return w->permissions[WHOIS_CONFIG_USER_OPER];
    281 	if (w->permissions[WHOIS_CONFIG_USER_EVERYONE] > 0)
    282 		return w->permissions[WHOIS_CONFIG_USER_EVERYONE];
    283 	return WHOIS_CONFIG_DETAILS_NONE;
    284 }
    285 
    286 /* WHOIS command.
    287  * parv[1] = list of nicks (comma separated)
    288  */
    289 CMD_FUNC(cmd_whois)
    290 {
    291 	Membership *lp;
    292 	Client *target;
    293 	Channel *channel;
    294 	char *nick, *tmp;
    295 	char *p = NULL;
    296 	int len, mlen;
    297 	char querybuf[BUFSIZE];
    298 	char buf[BUFSIZE];
    299 	int ntargets = 0;
    300 	int maxtargets = max_targets_for_command("WHOIS");
    301 
    302 	if (parc < 2)
    303 	{
    304 		sendnumeric(client, ERR_NONICKNAMEGIVEN);
    305 		return;
    306 	}
    307 
    308 	if (parc > 2)
    309 	{
    310 		if (hunt_server(client, recv_mtags, "WHOIS", 1, parc, parv) != HUNTED_ISME)
    311 			return;
    312 		parv[1] = parv[2];
    313 	}
    314 
    315 	strlcpy(querybuf, parv[1], sizeof(querybuf));
    316 
    317 	for (tmp = canonize(parv[1]); (nick = strtoken(&p, tmp, ",")); tmp = NULL)
    318 	{
    319 		unsigned char showchannel, wilds, hideoper; /* <- these are all boolean-alike */
    320 		NameValuePrioList *list = NULL, *e;
    321 		int policy; /* for temporary stuff */
    322 
    323 		if (MyUser(client) && (++ntargets > maxtargets))
    324 		{
    325 			sendnumeric(client, ERR_TOOMANYTARGETS, nick, maxtargets, "WHOIS");
    326 			break;
    327 		}
    328 
    329 		/* We do not support "WHOIS *" */
    330 		wilds = (strchr(nick, '?') || strchr(nick, '*'));
    331 		if (wilds)
    332 			continue;
    333 
    334 		target = find_user(nick, NULL);
    335 		if (!target)
    336 		{
    337 			sendnumeric(client, ERR_NOSUCHNICK, nick);
    338 			continue;
    339 		}
    340 
    341 		/* Ok, from this point we are going to proceed with the WHOIS.
    342 		 * The idea here is NOT to send any lines, so don't call sendto functions.
    343 		 * Instead, use add_nvplist_numeric() and add_nvplist_numeric_fmt()
    344 		 * to add items to the whois list.
    345 		 * Then at the end of this loop we call modules who can also add/remove
    346 		 * whois lines, and only after that we FINALLY send all the whois lines
    347 		 * in one go.
    348 		 */
    349 
    350 		hideoper = 0;
    351 		if (IsHideOper(target) && (target != client) && !IsOper(client))
    352 			hideoper = 1;
    353 
    354 		if (whois_get_policy(client, target, "basic") > WHOIS_CONFIG_DETAILS_NONE)
    355 		{
    356 			add_nvplist_numeric(&list, -1000000, "basic", client, RPL_WHOISUSER, target->name,
    357 				target->user->username,
    358 				IsHidden(target) ? target->user->virthost : target->user->realhost,
    359 				target->info);
    360 		}
    361 
    362 		if (whois_get_policy(client, target, "modes") > WHOIS_CONFIG_DETAILS_NONE)
    363 		{
    364 			add_nvplist_numeric(&list, -100000, "modes", client, RPL_WHOISMODES, target->name,
    365 				get_usermode_string(target), target->user->snomask ? target->user->snomask : "");
    366 		}
    367 		if (whois_get_policy(client, target, "realhost") > WHOIS_CONFIG_DETAILS_NONE)
    368 		{
    369 			add_nvplist_numeric(&list, -90000, "realhost", client, RPL_WHOISHOST, target->name,
    370 				(MyConnect(target) && strcmp(target->ident, "unknown")) ? target->ident : "*",
    371 				target->user->realhost, target->ip ? target->ip : "");
    372 		}
    373 
    374 		if (IsRegNick(target) && (whois_get_policy(client, target, "registered-nick") > WHOIS_CONFIG_DETAILS_NONE))
    375 		{
    376 			add_nvplist_numeric(&list, -80000, "registered-nick", client, RPL_WHOISREGNICK, target->name);
    377 		}
    378 
    379 		/* The following code deals with channels */
    380 		policy = whois_get_policy(client, target, "channels");
    381 		if (policy > WHOIS_CONFIG_DETAILS_NONE)
    382 		{
    383 			int channel_whois_lines = 0;
    384 			mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name);
    385 			for (len = 0, *buf = '\0', lp = target->user->channel; lp; lp = lp->next)
    386 			{
    387 				Hook *h;
    388 				int ret = EX_ALLOW;
    389 				int operoverride = 0;
    390 				
    391 				channel = lp->channel;
    392 				showchannel = 0;
    393 
    394 				if (ShowChannel(client, channel))
    395 					showchannel = 1;
    396 
    397 				for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next)
    398 				{
    399 					int n = (*(h->func.intfunc))(client, target, channel);
    400 					/* Hook return values:
    401 					 * EX_ALLOW means 'yes is ok, as far as modules are concerned'
    402 					 * EX_DENY means 'hide this channel, unless oper overriding'
    403 					 * EX_ALWAYS_DENY means 'hide this channel, always'
    404 					 * ... with the exception that we always show the channel if you /WHOIS yourself
    405 					 */
    406 					if (n == EX_DENY)
    407 					{
    408 						ret = EX_DENY;
    409 					}
    410 					else if (n == EX_ALWAYS_DENY)
    411 					{
    412 						ret = EX_ALWAYS_DENY;
    413 						break;
    414 					}
    415 				}
    416 				
    417 				if (ret == EX_DENY)
    418 					showchannel = 0;
    419 				
    420 				/* If the channel is normally hidden, but the user is an IRCOp,
    421 				 * and has the channel:see:whois privilege,
    422 				 * and set::whois-details for 'channels' has 'oper full',
    423 				 * then show it:
    424 				 */
    425 				if (!showchannel && (ValidatePermissionsForPath("channel:see:whois",client,NULL,channel,NULL)) && (policy == WHOIS_CONFIG_DETAILS_FULL))
    426 				{
    427 					showchannel = 1; /* OperOverride */
    428 					operoverride = 1;
    429 				}
    430 				
    431 				if ((ret == EX_ALWAYS_DENY) && (target != client))
    432 					continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */
    433 
    434 				/* This deals with target==client but also for unusual set::whois-details overrides
    435 				 * such as 'everyone full'
    436 				 */
    437 				if (policy == WHOIS_CONFIG_DETAILS_FULL)
    438 					showchannel = 1;
    439 
    440 				if (showchannel)
    441 				{
    442 					if (len + strlen(channel->name) > (size_t)BUFSIZE - 4 - mlen)
    443 					{
    444 						add_nvplist_numeric_fmt(&list, -70500-channel_whois_lines, "channels", client, RPL_WHOISCHANNELS,
    445 						                        "%s :%s", target->name, buf);
    446 						channel_whois_lines++;
    447 						*buf = '\0';
    448 						len = 0;
    449 					}
    450 
    451 					if (operoverride)
    452 					{
    453 						/* '?' and '!' both mean we can see the channel in /WHOIS and normally wouldn't,
    454 						 * but there's still a slight difference between the two...
    455 						 */
    456 						if (!PubChannel(channel))
    457 						{
    458 							/* '?' means it's a secret/private channel (too) */
    459 							*(buf + len++) = '?';
    460 						}
    461 						else
    462 						{
    463 							/* public channel but hidden in WHOIS (umode +p, service bot, etc) */
    464 							*(buf + len++) = '!';
    465 						}
    466 					}
    467 
    468 					if (!MyUser(client) || !HasCapability(client, "multi-prefix"))
    469 					{
    470 						/* Standard NAMES reply (single character) */
    471 						char c = mode_to_prefix(*lp->member_modes);
    472 						if (c)
    473 							*(buf + len++) = c;
    474 					}
    475 					else
    476 					{
    477 						/* NAMES reply with all rights included (multi-prefix / NAMESX) */
    478 						strcpy(buf + len, modes_to_prefix(lp->member_modes));
    479 						len += strlen(buf + len);
    480 					}
    481 					if (len)
    482 						*(buf + len) = '\0';
    483 					strcpy(buf + len, channel->name);
    484 					len += strlen(channel->name);
    485 					strcat(buf + len, " ");
    486 					len++;
    487 				}
    488 			}
    489 
    490 			if (buf[0] != '\0')
    491 			{
    492 				add_nvplist_numeric_fmt(&list, -70500-channel_whois_lines, "channels", client, RPL_WHOISCHANNELS,
    493 							"%s :%s", target->name, buf);
    494 				channel_whois_lines++;
    495 			}
    496 		}
    497 
    498 		if (!(IsULine(target) && !IsOper(client) && HIDE_ULINES) &&
    499 		    whois_get_policy(client, target, "server") > WHOIS_CONFIG_DETAILS_NONE)
    500 		{
    501 			add_nvplist_numeric(&list, -60000, "server", client, RPL_WHOISSERVER,
    502 			                    target->name, target->user->server, target->uplink->info);
    503 		}
    504 
    505 		if (target->user->away && (whois_get_policy(client, target, "away") > WHOIS_CONFIG_DETAILS_NONE))
    506 		{
    507 			add_nvplist_numeric(&list, -50000, "away", client, RPL_AWAY,
    508 			                    target->name, target->user->away);
    509 		}
    510 
    511 		if (IsOper(target) && !hideoper)
    512 		{
    513 			policy = whois_get_policy(client, target, "oper");
    514 			if (policy == WHOIS_CONFIG_DETAILS_FULL)
    515 			{
    516 				const char *operlogin = get_operlogin(target);
    517 				const char *operclass = get_operclass(target);
    518 
    519 				if (operlogin && operclass)
    520 				{
    521 					add_nvplist_numeric_fmt(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
    522 					                        "%s :is %s (%s) [%s]",
    523 					                        target->name, "an IRC Operator", operlogin, operclass);
    524 				} else
    525 				if (operlogin)
    526 				{
    527 					add_nvplist_numeric_fmt(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
    528 					                        "%s :is %s (%s)",
    529 					                        target->name, "an IRC Operator", operlogin);
    530 				} else
    531 				{
    532 					add_nvplist_numeric(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
    533 							    target->name, "an IRC Operator");
    534 				}
    535 			} else
    536 			if (policy == WHOIS_CONFIG_DETAILS_LIMITED)
    537 			{
    538 				add_nvplist_numeric(&list, -40000, "oper", client, RPL_WHOISOPERATOR,
    539 				                    target->name, "an IRC Operator");
    540 			}
    541 		}
    542 
    543 		if (target->umodes & UMODE_SECURE)
    544 		{
    545 			policy = whois_get_policy(client, target, "secure");
    546 			if (policy == WHOIS_CONFIG_DETAILS_LIMITED)
    547 			{
    548 				add_nvplist_numeric(&list, -30000, "secure", client, RPL_WHOISSECURE,
    549 				                    target->name, "is using a Secure Connection");
    550 			} else
    551 			if (policy == WHOIS_CONFIG_DETAILS_FULL)
    552 			{
    553 				const char *ciphers = tls_get_cipher(target);
    554 				if (ciphers)
    555 				{
    556 					add_nvplist_numeric_fmt(&list, -30000, "secure", client, RPL_WHOISSECURE,
    557 					                        "%s :is using a Secure Connection [%s]",
    558 					                        target->name, ciphers);
    559 				} else {
    560 					add_nvplist_numeric(&list, -30000, "secure", client, RPL_WHOISSECURE,
    561 							    target->name, "is using a Secure Connection");
    562 				}
    563 			}
    564 		}
    565 
    566 		/* The following code deals with security-groups */
    567 		policy = whois_get_policy(client, target, "security-groups");
    568 		if ((policy > WHOIS_CONFIG_DETAILS_NONE) && !IsULine(target))
    569 		{
    570 			SecurityGroup *s;
    571 			int security_groups_whois_lines = 0;
    572 
    573 			mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name) + strlen("is in security-groups: ");
    574 
    575 			if (user_allowed_by_security_group_name(target, "known-users"))
    576 				strlcpy(buf, "known-users,", sizeof(buf));
    577 			else
    578 				strlcpy(buf, "unknown-users,", sizeof(buf));
    579 			len = strlen(buf);
    580 
    581 			for (s = securitygroups; s; s = s->next)
    582 			{
    583 				if (len + strlen(s->name) > (size_t)BUFSIZE - 4 - mlen)
    584 				{
    585 					buf[len-1] = '\0';
    586 					add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups",
    587 					                        target, RPL_WHOISSPECIAL,
    588 								"%s :is in security-groups: %s", target->name, buf);
    589 					security_groups_whois_lines++;
    590 					*buf = '\0';
    591 					len = 0;
    592 				}
    593 				if (strcmp(s->name, "known-users") && user_allowed_by_security_group(target, s))
    594 				{
    595 					strcpy(buf + len, s->name);
    596 					len += strlen(buf+len);
    597 					strcpy(buf + len, ",");
    598 					len++;
    599 				}
    600 			}
    601 
    602 			if (*buf)
    603 			{
    604 				buf[len-1] = '\0';
    605 				add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups",
    606 				                        client, RPL_WHOISSPECIAL,
    607 							"%s :is in security-groups: %s", target->name, buf);
    608 				security_groups_whois_lines++;
    609 			}
    610 		}
    611 		if (MyUser(target) && IsShunned(target) && (whois_get_policy(client, target, "shunned") > WHOIS_CONFIG_DETAILS_NONE))
    612 		{
    613 			add_nvplist_numeric(&list, -20000, "shunned", client, RPL_WHOISSPECIAL,
    614 			                    target->name, "is shunned");
    615 		}
    616 
    617 		if (target->user->swhois && (whois_get_policy(client, target, "swhois") > WHOIS_CONFIG_DETAILS_NONE))
    618 		{
    619 			SWhois *s;
    620 			int swhois_lines = 0;
    621 
    622 			for (s = target->user->swhois; s; s = s->next)
    623 			{
    624 				if (hideoper && !IsOper(client) && s->setby && !strcmp(s->setby, "oper"))
    625 					continue; /* hide oper-based swhois entries */
    626 				add_nvplist_numeric(&list, 100000+swhois_lines, "swhois", client, RPL_WHOISSPECIAL,
    627 				                    target->name, s->line);
    628 				swhois_lines++;
    629 			}
    630 		}
    631 
    632 		/* TODO: hmm.. this should be a bit more towards the beginning of the whois, no ? */
    633 		if (IsLoggedIn(target) && (whois_get_policy(client, target, "account") > WHOIS_CONFIG_DETAILS_NONE))
    634 		{
    635 			add_nvplist_numeric(&list, 200000, "account", client, RPL_WHOISLOGGEDIN,
    636 			                    target->name, target->user->account);
    637 		}
    638 
    639 		if (MyConnect(target))
    640 		{
    641 			policy = whois_get_policy(client, target, "idle");
    642 			/* If the policy is 'full' then show the idle time.
    643 			 * If the policy is 'limited then show the idle time according to the +I rules
    644 			 */
    645 			if ((policy == WHOIS_CONFIG_DETAILS_FULL) ||
    646 			    ((policy == WHOIS_CONFIG_DETAILS_LIMITED) && !hide_idle_time(client, target)))
    647 			{
    648 				add_nvplist_numeric(&list, 500000, "idle", client, RPL_WHOISIDLE,
    649 				                    target->name,
    650 				                    (long long)(TStime() - target->local->idle_since),
    651 				                    (long long)target->local->creationtime);
    652 			}
    653 		}
    654 
    655 		RunHook(HOOKTYPE_WHOIS, client, target, &list);
    656 
    657 		for (e = list; e; e = e->next)
    658 			sendto_one(client, NULL, "%s", e->value);
    659 
    660 		free_nvplist(list);
    661 	}
    662 	sendnumeric(client, RPL_ENDOFWHOIS, querybuf);
    663 }