From 01fb744c405792dd3f0859a53f74cd4cb5fd166f Mon Sep 17 00:00:00 2001 From: David Schultz Date: Tue, 30 Aug 2022 15:49:43 -0500 Subject: [PATCH] Add umode +I to allow users to hide their idle time (#220) --- doc/reference.conf | 4 ++ extensions/Makefile.am | 1 + extensions/umode_hide_idle_time.c | 80 +++++++++++++++++++++++++++++++ help/users/umode | 1 + include/hook.h | 8 ++++ modules/m_stats.c | 22 ++++++--- modules/m_trace.c | 15 +++++- modules/m_who.c | 22 ++++++++- modules/m_whois.c | 21 ++++++-- 9 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 extensions/umode_hide_idle_time.c diff --git a/doc/reference.conf b/doc/reference.conf index 35afb85a..4cd8c037 100644 --- a/doc/reference.conf +++ b/doc/reference.conf @@ -78,6 +78,7 @@ * Global nick-change notices -- sno_globalnickchange * Oper-override (modehacking only) -- override * Stop services kills -- no_kill_services + * Allows you to hide your idle time (umode +I) -- umode_hide_idle_time */ #loadmodule "extensions/chm_adminonly"; #loadmodule "extensions/chm_nonotice"; @@ -110,6 +111,7 @@ #loadmodule "extensions/sno_globaloper"; #loadmodule "extensions/override"; #loadmodule "extensions/no_kill_services"; +#loadmodule "extensions/umode_hide_idle_time"; /* serverinfo {}: Contains information about the server. (OLD M:) */ serverinfo { @@ -474,6 +476,8 @@ privset "local_op" { * oper:receive_immunity: * confers the benefits of chmode +M (operpeace) (from extensions/chm_operpeace) * usermode:helpops allows setting +h (from extensions/helpops) + * auspex:usertimes: + * allows viewing user idle/connect times even when +I is set (from extensions/umode_hide_idle_time) */ privs = oper:general, oper:privs, oper:testline, oper:kill, oper:operwall, oper:message, usermode:servnotice, auspex:oper, auspex:hostname, auspex:umodes, auspex:cmodes; diff --git a/extensions/Makefile.am b/extensions/Makefile.am index 5fe872dd..e404a9ee 100644 --- a/extensions/Makefile.am +++ b/extensions/Makefile.am @@ -66,6 +66,7 @@ extension_LTLIBRARIES = \ identify_msg.la \ cap_realhost.la \ invex_regonly.la \ + umode_hide_idle_time.la \ cap_oper.la \ example_module.la diff --git a/extensions/umode_hide_idle_time.c b/extensions/umode_hide_idle_time.c new file mode 100644 index 00000000..13077638 --- /dev/null +++ b/extensions/umode_hide_idle_time.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 David Schultz + * + * This program is free software; 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 2 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include "stdinc.h" +#include "modules.h" +#include "client.h" +#include "hook.h" +#include "ircd.h" +#include "logger.h" +#include "send.h" +#include "s_conf.h" +#include "s_user.h" +#include "s_newconf.h" + +static const char hide_desc[] = "Provides user mode +I to hide a user's idle time"; + +static void h_huc_doing_idle_time_hook(void *); + +mapi_hfn_list_av1 huc_hfnlist[] = { + { "doing_whois_show_idle", h_huc_doing_idle_time_hook }, + { "doing_trace_show_idle", h_huc_doing_idle_time_hook }, + { "doing_stats_show_idle", h_huc_doing_idle_time_hook }, + { "doing_who_show_idle", h_huc_doing_idle_time_hook }, + { NULL, NULL } +}; + +static void +h_huc_doing_idle_time_hook(void *data_) +{ + hook_data_client_approval *data = data_; + + if (data->approved == 0) + return; + + if (data->target->umodes & user_modes['I']) + { + if (HasPrivilege(data->client, "auspex:usertimes")) + data->approved = WHOIS_IDLE_AUSPEX; + else if (data->client != data->target) + data->approved = WHOIS_IDLE_HIDE; + } +} + +static int +_modinit(void) +{ + user_modes['I'] = find_umode_slot(); + construct_umodebuf(); + if (!user_modes['I']) + { + ierror("umode_hide_idle_time: unable to allocate usermode slot for +I, unloading extension"); + return -1; + } + return 0; +} + +static void +_moddeinit(void) +{ + user_modes['I'] = 0; + construct_umodebuf(); +} + +DECLARE_MODULE_AV2(hide_idle_time, _modinit, _moddeinit, NULL, NULL, huc_hfnlist, NULL, NULL, hide_desc); diff --git a/help/users/umode b/help/users/umode index 124896d8..94256812 100644 --- a/help/users/umode +++ b/help/users/umode @@ -19,6 +19,7 @@ User modes: (? designates that the umode is provided by an extension +G - Deny users not on your /ACCEPT list and not in a channel with you from messaging you and inviting you to channels. This is a softer form of +g. + ? +I - Hides your idle time. +Q - Prevents you from being affected by channel forwarding. +R - Prevents unidentified users that you have not accepted from messaging you. diff --git a/include/hook.h b/include/hook.h index c1971614..fc56b288 100644 --- a/include/hook.h +++ b/include/hook.h @@ -21,6 +21,14 @@ enum hook_priority HOOK_MONITOR = 100 }; +/* for idle time privacy features */ +enum whois_idle_approval +{ + WHOIS_IDLE_HIDE = 0, + WHOIS_IDLE_SHOW = 1, + WHOIS_IDLE_AUSPEX = 2 +}; + typedef void (*hookfn) (void *data); extern int h_iosend_id; diff --git a/modules/m_stats.c b/modules/m_stats.c index 31177b71..b67d8c30 100644 --- a/modules/m_stats.c +++ b/modules/m_stats.c @@ -60,11 +60,13 @@ struct Message stats_msgtab = { int doing_stats_hook; int doing_stats_p_hook; +int doing_stats_show_idle_hook; mapi_clist_av1 stats_clist[] = { &stats_msgtab, NULL }; mapi_hlist_av1 stats_hlist[] = { { "doing_stats", &doing_stats_hook }, { "doing_stats_p", &doing_stats_p_hook }, + { "doing_stats_show_idle", &doing_stats_show_idle_hook }, { NULL, NULL } }; @@ -1621,19 +1623,27 @@ stats_l_client(struct Client *source_p, struct Client *target_p, else { + /* fire the doing_stats_show_idle hook to allow modules to tell us whether to show the idle time */ + hook_data_client_approval hdata_showidle; + + hdata_showidle.client = source_p; + hdata_showidle.target = target_p; + hdata_showidle.approved = WHOIS_IDLE_SHOW; + + call_hook(doing_stats_show_idle_hook, &hdata_showidle); sendto_one_numeric(source_p, RPL_STATSLINKINFO, Lformat, show_ip(source_p, target_p) ? (IsUpper(statchar) ? get_client_name(target_p, SHOW_IP) : get_client_name(target_p, HIDE_IP)) : get_client_name(target_p, MASK_IP), - (int) rb_linebuf_len(&target_p->localClient->buf_sendq), - (int) target_p->localClient->sendM, - (int) target_p->localClient->sendK, - (int) target_p->localClient->receiveM, - (int) target_p->localClient->receiveK, + hdata_showidle.approved ? (int) rb_linebuf_len(&target_p->localClient->buf_sendq) : 0, + hdata_showidle.approved ? (int) target_p->localClient->sendM : 0, + hdata_showidle.approved ? (int) target_p->localClient->sendK : 0, + hdata_showidle.approved ? (int) target_p->localClient->receiveM : 0, + hdata_showidle.approved ? (int) target_p->localClient->receiveK : 0, rb_current_time() - target_p->localClient->firsttime, - (rb_current_time() > target_p->localClient->lasttime) ? + (rb_current_time() > target_p->localClient->lasttime) && hdata_showidle.approved ? (rb_current_time() - target_p->localClient->lasttime) : 0, "-"); } diff --git a/modules/m_trace.c b/modules/m_trace.c index a634635b..ae9a0057 100644 --- a/modules/m_trace.c +++ b/modules/m_trace.c @@ -52,10 +52,12 @@ struct Message trace_msgtab = { }; int doing_trace_hook; +int doing_trace_show_idle_hook; mapi_clist_av1 trace_clist[] = { &trace_msgtab, NULL }; mapi_hlist_av1 trace_hlist[] = { { "doing_trace", &doing_trace_hook }, + { "doing_trace_show_idle", &doing_trace_show_idle_hook }, { NULL, NULL } }; DECLARE_MODULE_AV2(trace, NULL, NULL, trace_clist, trace_hlist, NULL, NULL, NULL, trace_desc); @@ -381,13 +383,22 @@ report_this_status(struct Client *source_p, struct Client *target_p) case STAT_CLIENT: { + /* fire the doing_trace_show_idle hook to allow modules to tell us whether to show the idle time */ + hook_data_client_approval hdata_showidle; + + hdata_showidle.client = source_p; + hdata_showidle.target = target_p; + hdata_showidle.approved = WHOIS_IDLE_SHOW; + + call_hook(doing_trace_show_idle_hook, &hdata_showidle); + sendto_one_numeric(source_p, 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), - (unsigned long)(rb_current_time() - target_p->localClient->last)); + hdata_showidle.approved ? (unsigned long)(rb_current_time() - target_p->localClient->lasttime) : 0, + hdata_showidle.approved ? (unsigned long)(rb_current_time() - target_p->localClient->last) : 0); cnt++; } diff --git a/modules/m_who.c b/modules/m_who.c index 0c077328..d47d44bb 100644 --- a/modules/m_who.c +++ b/modules/m_who.c @@ -91,8 +91,14 @@ _moddeinit(void) delete_isupport("WHOX"); } +int doing_who_show_idle_hook; + mapi_clist_av1 who_clist[] = { &who_msgtab, NULL }; -DECLARE_MODULE_AV2(who, _modinit, _moddeinit, who_clist, NULL, NULL, NULL, NULL, who_desc); +mapi_hlist_av1 who_hlist[] = { + { "doing_who_show_idle", &doing_who_show_idle_hook }, + { NULL, NULL } +}; +DECLARE_MODULE_AV2(who, _modinit, _moddeinit, who_clist, who_hlist, NULL, NULL, NULL, who_desc); /* ** m_who @@ -531,7 +537,19 @@ do_who(struct Client *source_p, struct Client *target_p, struct membership *mspt if (fmt->fields & FIELD_HOP) append_format(str, sizeof str, &pos, " %d", ConfigServerHide.flatten_links && !IsOperGeneral(source_p) && !IsExemptShide(source_p) ? 0 : target_p->hopcount); if (fmt->fields & FIELD_IDLE) - append_format(str, sizeof str, &pos, " %d", (int)(MyClient(target_p) ? rb_current_time() - target_p->localClient->last : 0)); + { + /* fire the doing_who_show_idle hook to allow modules to tell us whether to show the idle time */ + hook_data_client_approval hdata_showidle; + + hdata_showidle.client = source_p; + hdata_showidle.target = target_p; + hdata_showidle.approved = WHOIS_IDLE_SHOW; + + call_hook(doing_who_show_idle_hook, &hdata_showidle); + + append_format(str, sizeof str, &pos, " %d", + hdata_showidle.approved ? (int)(MyClient(target_p) ? rb_current_time() - target_p->localClient->last : 0) : 0); + } if (fmt->fields & FIELD_ACCOUNT) { /* display as in whois */ diff --git a/modules/m_whois.c b/modules/m_whois.c index ecd86569..68bb17c9 100644 --- a/modules/m_whois.c +++ b/modules/m_whois.c @@ -60,12 +60,14 @@ struct Message whois_msgtab = { int doing_whois_hook; int doing_whois_global_hook; int doing_whois_channel_visibility_hook; +int doing_whois_show_idle_hook; mapi_clist_av1 whois_clist[] = { &whois_msgtab, NULL }; mapi_hlist_av1 whois_hlist[] = { { "doing_whois", &doing_whois_hook }, { "doing_whois_global", &doing_whois_global_hook }, { "doing_whois_channel_visibility", &doing_whois_channel_visibility_hook }, + { "doing_whois_show_idle", &doing_whois_show_idle_hook }, { NULL, NULL } }; @@ -373,10 +375,23 @@ single_whois(struct Client *source_p, struct Client *target_p, int operspy) target_p->name, buf); } + /* fire the doing_whois_show_idle hook to allow modules to tell us whether to show the idle time */ + hook_data_client_approval hdata_showidle; + + hdata_showidle.client = source_p; + hdata_showidle.target = target_p; + hdata_showidle.approved = WHOIS_IDLE_SHOW; + + call_hook(doing_whois_show_idle_hook, &hdata_showidle); + sendto_one_numeric(source_p, RPL_WHOISIDLE, form_str(RPL_WHOISIDLE), - target_p->name, - (long)(rb_current_time() - target_p->localClient->last), - (unsigned long)target_p->localClient->firsttime); + target_p->name, + hdata_showidle.approved ? (long)(rb_current_time() - target_p->localClient->last) : 0, + (unsigned long)target_p->localClient->firsttime); + + if (hdata_showidle.approved == WHOIS_IDLE_AUSPEX || hdata_showidle.approved == WHOIS_IDLE_HIDE) + /* if the target has hidden their idle time, notify the source */ + sendto_one_numeric(source_p, RPL_WHOISTEXT, form_str(RPL_WHOISTEXT), target_p->name, "is using a private idle time"); } else {