From 1123eefcb085802bbad7a04bf0d8b7f082d123d6 Mon Sep 17 00:00:00 2001
From: Ed Kellett <e@kellett.im>
Date: Sun, 7 Jul 2019 04:57:53 +0100
Subject: [PATCH] Rework oper hiding

As it stands, oper hiding is rather messy and inconsistent. Add
SeesOper(target, source), which is true iff target should appear as an
oper to source. If I haven't missed something, all commands that reveal
oper status now use the same logic.

general::hide_opers_in_whois is a special case, and affects /whois only.

general::hide_opers is introduced, and has the same effect as giving
everyone oper:hidden. All commands that reveal oper status respect both.
---
 include/s_conf.h     |  1 +
 include/s_newconf.h  |  2 ++
 ircd/newconf.c       |  1 +
 ircd/s_conf.c        |  1 +
 modules/m_etrace.c   | 10 +++++-----
 modules/m_stats.c    |  2 +-
 modules/m_trace.c    | 11 +++++++----
 modules/m_userhost.c |  5 +++--
 modules/m_who.c      | 10 +++++-----
 modules/m_whois.c    |  2 +-
 10 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/include/s_conf.h b/include/s_conf.h
index 9744eb92..f158b61e 100644
--- a/include/s_conf.h
+++ b/include/s_conf.h
@@ -239,6 +239,7 @@ struct config_file_entry
 	int certfp_method;
 
 	int hide_opers_in_whois;
+	int hide_opers;
 };
 
 struct config_channel_entry
diff --git a/include/s_newconf.h b/include/s_newconf.h
index 4792b005..41e74442 100644
--- a/include/s_newconf.h
+++ b/include/s_newconf.h
@@ -166,6 +166,8 @@ extern void cluster_generic(struct Client *, const char *, int cltype,
 #define IsOperRemoteBan(x)	(HasPrivilege((x), "oper:remoteban"))
 #define IsOperMassNotice(x)	(HasPrivilege((x), "oper:mass_notice"))
 
+#define SeesOper(target, source)	(IsOper((target)) && (!ConfigFileEntry.hide_opers && !HasPrivilege((target), "oper:hidden") || IsOper((source))))
+
 extern struct oper_conf *make_oper_conf(void);
 extern void free_oper_conf(struct oper_conf *);
 extern void clear_oper_conf(void);
diff --git a/ircd/newconf.c b/ircd/newconf.c
index 9830cda3..f63669ec 100644
--- a/ircd/newconf.c
+++ b/ircd/newconf.c
@@ -2797,6 +2797,7 @@ static struct ConfEntry conf_general_table[] =
 	{ "max_ratelimit_tokens",	CF_INT,   NULL, 0, &ConfigFileEntry.max_ratelimit_tokens	},
 	{ "away_interval",		CF_INT,   NULL, 0, &ConfigFileEntry.away_interval		},
 	{ "hide_opers_in_whois",	CF_YESNO, NULL, 0, &ConfigFileEntry.hide_opers_in_whois		},
+	{ "hide_opers",		CF_YESNO, NULL, 0, &ConfigFileEntry.hide_opers		},
 	{ "certfp_method",	CF_STRING, conf_set_general_certfp_method, 0, NULL },
 	{ "\0", 		0, 	  NULL, 0, NULL }
 };
diff --git a/ircd/s_conf.c b/ircd/s_conf.c
index a828c1aa..dbed64d0 100644
--- a/ircd/s_conf.c
+++ b/ircd/s_conf.c
@@ -809,6 +809,7 @@ set_default_conf(void)
 	ConfigFileEntry.nicklen = NICKLEN;
 	ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_CERT_SHA1;
 	ConfigFileEntry.hide_opers_in_whois = 0;
+	ConfigFileEntry.hide_opers = 0;
 
 	if (!alias_dict)
 		alias_dict = rb_dictionary_create("alias", rb_strcasecmp);
diff --git a/modules/m_etrace.c b/modules/m_etrace.c
index fded420b..193d842b 100644
--- a/modules/m_etrace.c
+++ b/modules/m_etrace.c
@@ -167,7 +167,7 @@ do_etrace(struct Client *source_p, int ipv4, int ipv6)
 
 		sendto_one(source_p, form_str(RPL_ETRACE),
 			   me.name, source_p->name,
-			   IsOper(target_p) ? "Oper" : "User",
+			   SeesOper(target_p, source_p) ? "Oper" : "User",
 			   get_client_class(target_p),
 			   target_p->name, target_p->username, target_p->host,
 			   show_ip(source_p, target_p) ? target_p->sockhost : "255.255.255.255",
@@ -206,14 +206,14 @@ do_single_etrace(struct Client *source_p, struct Client *target_p)
 	if(!show_ip(source_p, target_p))
 		sendto_one(source_p, form_str(RPL_ETRACEFULL),
 				me.name, source_p->name,
-				IsOper(target_p) ? "Oper" : "User",
+				SeesOper(target_p, source_p) ? "Oper" : "User",
 				get_client_class(target_p),
 				target_p->name, target_p->username, target_p->host,
 				"255.255.255.255", "<hidden> <hidden>", target_p->info);
 	else
 		sendto_one(source_p, form_str(RPL_ETRACEFULL),
 				me.name, source_p->name,
-				IsOper(target_p) ? "Oper" : "User",
+				SeesOper(target_p, source_p) ? "Oper" : "User",
 				get_client_class(target_p),
 				target_p->name, target_p->username,
 				target_p->host, target_p->sockhost,
@@ -278,7 +278,7 @@ m_chantrace(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou
 
 		sendto_one(source_p, form_str(RPL_ETRACE),
 				me.name, source_p->name,
-				IsOper(target_p) ? "Oper" : "User",
+				SeesOper(target_p, source_p) ? "Oper" : "User",
 				/* class field -- pretend its server.. */
 				target_p->servptr->name,
 				target_p->name, target_p->username, target_p->host,
@@ -323,7 +323,7 @@ match_masktrace(struct Client *source_p, rb_dlink_list *list,
 
 			sendto_one(source_p, form_str(RPL_ETRACE),
 				me.name, source_p->name,
-				IsOper(target_p) ? "Oper" : "User",
+				SeesOper(target_p, source_p) ? "Oper" : "User",
 				/* class field -- pretend its server.. */
 				target_p->servptr->name,
 				target_p->name, target_p->username, target_p->host,
diff --git a/modules/m_stats.c b/modules/m_stats.c
index 673d2212..af433146 100644
--- a/modules/m_stats.c
+++ b/modules/m_stats.c
@@ -830,7 +830,7 @@ stats_operedup (struct Client *source_p)
 	{
 		target_p = oper_ptr->data;
 
-		if(IsOperInvis(target_p) && !IsOper(source_p))
+		if(!SeesOper(target_p, source_p))
 			continue;
 
 		if(target_p->user->away)
diff --git a/modules/m_trace.c b/modules/m_trace.c
index 38550667..1baf2bd7 100644
--- a/modules/m_trace.c
+++ b/modules/m_trace.c
@@ -204,6 +204,9 @@ m_trace(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_
 			if(!doall && wilds && (match(tname, target_p->name) == 0))
 				continue;
 
+			if(!SeesOper(target_p, source_p))
+				continue;
+
 			report_this_status(source_p, target_p);
 		}
 
@@ -233,14 +236,14 @@ m_trace(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_
 		target_p = ptr->data;
 
 		/* dont show invisible users to remote opers */
-		if(IsInvisible(target_p) && dow && !MyConnect(source_p) && !IsOper(target_p))
+		if(IsInvisible(target_p) && dow && !MyConnect(source_p) && !SeesOper(target_p, source_p))
 			continue;
 
 		if(!doall && wilds && !match(tname, target_p->name))
 			continue;
 
 		/* remote opers may not see invisible normal users */
-		if(dow && !MyConnect(source_p) && !IsOper(target_p) &&
+		if(dow && !MyConnect(source_p) && !SeesOper(target_p, source_p) &&
 				IsInvisible(target_p))
 			continue;
 
@@ -379,8 +382,8 @@ report_this_status(struct Client *source_p, struct Client *target_p)
 	case STAT_CLIENT:
 		{
 			sendto_one_numeric(source_p,
-					IsOper(target_p) ? RPL_TRACEOPERATOR : RPL_TRACEUSER,
-					IsOper(target_p) ? form_str(RPL_TRACEOPERATOR) : form_str(RPL_TRACEUSER),
+					SeesOper(target_p, source_p) ? RPL_TRACEOPERATOR : RPL_TRACEUSER,
+					SeesOper(target_p, source_p) ? form_str(RPL_TRACEOPERATOR) : form_str(RPL_TRACEUSER),
 					class_name, name,
 					show_ip(source_p, target_p) ? ip : empty_sockhost,
 					(unsigned long)(rb_current_time() - target_p->localClient->lasttime),
diff --git a/modules/m_userhost.c b/modules/m_userhost.c
index 76e6cd17..6b0a9ac1 100644
--- a/modules/m_userhost.c
+++ b/modules/m_userhost.c
@@ -33,6 +33,7 @@
 #include "parse.h"
 #include "modules.h"
 #include "s_conf.h"
+#include "s_newconf.h"
 
 static const char userhost_desc[] =
 	"Provides the USERHOST command to show a user's host";
@@ -85,7 +86,7 @@ m_userhost(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sour
 			{
 				rl = sprintf(response, "%s%s=%c%s@%s ",
 						target_p->name,
-						IsOper(target_p) ? "*" : "",
+						SeesOper(target_p, source_p) ? "*" : "",
 						(target_p->user->away) ? '-' : '+',
 						target_p->username,
 						target_p->sockhost);
@@ -94,7 +95,7 @@ m_userhost(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sour
 			{
 				rl = sprintf(response, "%s%s=%c%s@%s ",
 						target_p->name,
-						IsOper(target_p) ? "*" : "",
+						SeesOper(target_p, source_p) ? "*" : "",
 						(target_p->user->away) ? '-' : '+',
 						target_p->username, target_p->host);
 			}
diff --git a/modules/m_who.c b/modules/m_who.c
index aa674582..b7198a65 100644
--- a/modules/m_who.c
+++ b/modules/m_who.c
@@ -218,7 +218,7 @@ m_who(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p,
 	/* '/who nick' */
 
 	if(((target_p = find_named_person(mask)) != NULL) &&
-	   (!server_oper || IsOper(target_p)))
+	   (!server_oper || SeesOper(target_p, source_p)))
 	{
 		int isinvis = 0;
 
@@ -314,7 +314,7 @@ who_common_channel(struct Client *source_p, struct Channel *chptr,
 		if(!IsInvisible(target_p) || IsMarked(target_p))
 			continue;
 
-		if(server_oper && !IsOper(target_p))
+		if(server_oper && !SeesOper(target_p, source_p))
 			continue;
 
 		SetMark(target_p);
@@ -387,7 +387,7 @@ who_global(struct Client *source_p, const char *mask, int server_oper, int opers
 			continue;
 		}
 
-		if(server_oper && !IsOper(target_p))
+		if(server_oper && !SeesOper(target_p, source_p))
 			continue;
 
 		if(maxmatches > 0)
@@ -435,7 +435,7 @@ do_who_on_channel(struct Client *source_p, struct Channel *chptr,
 		msptr = ptr->data;
 		target_p = msptr->client_p;
 
-		if(server_oper && !IsOper(target_p))
+		if(server_oper && !SeesOper(target_p, source_p))
 			continue;
 
 		if(member || !IsInvisible(target_p))
@@ -488,7 +488,7 @@ do_who(struct Client *source_p, struct Client *target_p, struct membership *mspt
 	const char *q;
 
 	sprintf(status, "%c%s%s",
-		   target_p->user->away ? 'G' : 'H', IsOper(target_p) ? "*" : "", msptr ? find_channel_status(msptr, fmt->fields || IsCapable(source_p, CLICAP_MULTI_PREFIX)) : "");
+		   target_p->user->away ? 'G' : 'H', SeesOper(target_p, source_p) ? "*" : "", msptr ? find_channel_status(msptr, fmt->fields || IsCapable(source_p, CLICAP_MULTI_PREFIX)) : "");
 
 	if (fmt->fields == 0)
 		sendto_one(source_p, form_str(RPL_WHOREPLY), me.name,
diff --git a/modules/m_whois.c b/modules/m_whois.c
index 594656ee..d3384ed3 100644
--- a/modules/m_whois.c
+++ b/modules/m_whois.c
@@ -309,7 +309,7 @@ single_whois(struct Client *source_p, struct Client *target_p, int operspy)
 		sendto_one_numeric(source_p, RPL_AWAY, form_str(RPL_AWAY),
 				   target_p->name, target_p->user->away);
 
-	if(IsOper(target_p) && (!ConfigFileEntry.hide_opers_in_whois || IsOper(source_p)))
+	if((!ConfigFileEntry.hide_opers_in_whois || IsOper(source_p)) && SeesOper(target_p, source_p))
 	{
 		sendto_one_numeric(source_p, RPL_WHOISOPERATOR, form_str(RPL_WHOISOPERATOR),
 				   target_p->name,