From 8f4b7f5c5c84c07b441106ff7e8ec31328ddaf18 Mon Sep 17 00:00:00 2001 From: miki Date: Tue, 19 Sep 2023 12:36:09 +0000 Subject: [PATCH] clients: add ignore list to idle user autounsub This list includes well-known bots, to prevent them from being erroneously removed by default. --- brclient/appstate.go | 5 +-- brclient/brclient.conf.go | 7 +++++ brclient/config.go | 18 +++++++++++ bruig/flutterui/bruig/lib/config.dart | 19 +++++++++++- bruig/flutterui/bruig/lib/main.dart | 3 +- bruig/flutterui/plugin/lib/definitions.dart | 5 ++- bruig/flutterui/plugin/lib/definitions.g.dart | 4 +++ bruig/golib/command_handlers.go | 5 +-- bruig/golib/definitions.go | 5 +-- client/client.go | 6 ++++ client/client_kx.go | 31 +++++++++++++++++-- 11 files changed, 96 insertions(+), 12 deletions(-) diff --git a/brclient/appstate.go b/brclient/appstate.go index e1fbd679..e10bb91a 100644 --- a/brclient/appstate.go +++ b/brclient/appstate.go @@ -3174,8 +3174,9 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer, ResourcesProvider: resRouter, NoLoadChatHistory: args.NoLoadChatHistory, - AutoHandshakeInterval: args.AutoHandshakeInterval, - AutoRemoveIdleUsersInterval: args.AutoRemoveIdleUsersInterval, + AutoHandshakeInterval: args.AutoHandshakeInterval, + AutoRemoveIdleUsersInterval: args.AutoRemoveIdleUsersInterval, + AutoRemoveIdleUsersIgnoreList: args.AutoRemoveIdleUsersIgnore, CertConfirmer: func(ctx context.Context, cs *tls.ConnectionState, svrID *zkidentity.PublicIdentity) error { diff --git a/brclient/brclient.conf.go b/brclient/brclient.conf.go index 60a96de3..3a24036e 100644 --- a/brclient/brclient.conf.go +++ b/brclient/brclient.conf.go @@ -66,6 +66,13 @@ root = {{ .Root }} # Set to zero to disable idle user removal. # autoremoveidleusersinterval = 60d +# Comma-separated list of users to NOT auto-unsubscribe or remove from GCs +# during the idle check. This may be either the nick or UID of the user. By +# default, some well-known bots are included in this list: +# 86abd31f2141b274196d481edd061a00ab7a56b61a31656775c8a590d612b966 - Oprah +# ad716557157c1f191d8b5f8c6757ea41af49de27dc619fc87f337ca85be325ee - GC bot +# autoremoveignorelist = + # logging and debug [log] diff --git a/brclient/config.go b/brclient/config.go index 2a1970ca..2aa16c36 100644 --- a/brclient/config.go +++ b/brclient/config.go @@ -30,6 +30,16 @@ const ( appName = "brclient" ) +var ( + // defaultAutoRemoveIgnoreList is the list of users that should not be + // removed during the auto unsubscribe idle check. By default, these are + // some well-known bots. + defaultAutoRemoveIgnoreList = strings.Join([]string{ + "86abd31f2141b274196d481edd061a00ab7a56b61a31656775c8a590d612b966", // Oprah + "ad716557157c1f191d8b5f8c6757ea41af49de27dc619fc87f337ca85be325ee", // GC bot + }, ",") +) + var ( // Error to signal loadConfig() completed everything the cmd had to do // and main() should exit. @@ -99,6 +109,7 @@ type config struct { AutoHandshakeInterval time.Duration AutoRemoveIdleUsersInterval time.Duration + AutoRemoveIdleUsersIgnore []string SyncFreeList bool @@ -274,6 +285,7 @@ func loadConfig() (*config, error) { flagAutoHandshake := fs.String("autohandshakeinterval", "21d", "") flagAutoRemove := fs.String("autoremoveidleusersinterval", "60d", "") + flagAutoRemoveIgnoreList := fs.String("autoremoveignorelist", defaultAutoRemoveIgnoreList, "") // log flagMsgRoot := fs.String("log.msglog", defaultMsgRoot, "Root for message log files") @@ -410,6 +422,11 @@ func loadConfig() (*config, error) { mimeMap[spl[0]] = spl[1] } + autoRemoveIgnoreList := strings.Split(*flagAutoRemoveIgnoreList, ",") + for i := range autoRemoveIgnoreList { + autoRemoveIgnoreList[i] = strings.TrimSpace(autoRemoveIgnoreList[i]) + } + var jrpcListen []string if *flagJSONRPCListen != "" { jrpcListen = strings.Split(*flagJSONRPCListen, ",") @@ -502,6 +519,7 @@ func loadConfig() (*config, error) { AutoHandshakeInterval: autoHandshakeInterval, AutoRemoveIdleUsersInterval: autoRemoveInterval, + AutoRemoveIdleUsersIgnore: autoRemoveIgnoreList, SyncFreeList: *flagSyncFreeList, ExtenalEditorForComments: *flagExternalEditorForComments, diff --git a/bruig/flutterui/bruig/lib/config.dart b/bruig/flutterui/bruig/lib/config.dart index a3638df3..efe7ab5a 100644 --- a/bruig/flutterui/bruig/lib/config.dart +++ b/bruig/flutterui/bruig/lib/config.dart @@ -7,6 +7,11 @@ import 'package:path/path.dart' as path; const APPNAME = "bruig"; +const defaultAutoRemoveIgnoreList = [ + "86abd31f2141b274196d481edd061a00ab7a56b61a31656775c8a590d612b966", // Oprah + "ad716557157c1f191d8b5f8c6757ea41af49de27dc619fc87f337ca85be325ee", // GC bot +]; + String homeDir() { var env = Platform.environment; if (Platform.isWindows) { @@ -83,6 +88,7 @@ class Config { late final bool syncFreeList; late final int autoHandshakeInterval; late final int autoRemoveIdleUsersInterval; + late final List autoRemoveIgnoreList; Config(); Config.filled( @@ -111,7 +117,8 @@ class Config { this.noLoadChatHistory: true, this.syncFreeList: true, this.autoHandshakeInterval: 21 * 24 * 60 * 60, - this.autoRemoveIdleUsersInterval: 60 * 24 * 60 * 60}); + this.autoRemoveIdleUsersInterval: 60 * 24 * 60 * 60, + this.autoRemoveIgnoreList: defaultAutoRemoveIgnoreList}); factory Config.newWithRPCHost( Config cfg, String rpcHost, String tlsCert, String macaroonPath) => Config.filled( @@ -141,6 +148,7 @@ class Config { syncFreeList: cfg.syncFreeList, autoHandshakeInterval: cfg.autoHandshakeInterval, autoRemoveIdleUsersInterval: cfg.autoRemoveIdleUsersInterval, + autoRemoveIgnoreList: cfg.autoRemoveIgnoreList, ); Future saveConfig(String filepath) async { @@ -200,6 +208,13 @@ Future loadConfig(String filepath) async { return v != null && v != "" ? int.tryParse(v) : null; }; + var getCommaList = (String section, String opt) { + var v = f.get(section, opt); + return v != null && v != "" + ? v.split(",").map((e) => e.trim()).toList() + : null; + }; + var iniLogFile = f.get("log", "logfile"); String logfile = path.join(appDataDir, "applogs", "${APPNAME}.log"); if (iniLogFile != null) { @@ -255,6 +270,8 @@ Future loadConfig(String filepath) async { parseDurationSeconds(f.get("default", "autohandshakeinterval") ?? "21d"); c.autoRemoveIdleUsersInterval = parseDurationSeconds( f.get("default", "autoremoveidleusersinterval") ?? "60d"); + c.autoRemoveIgnoreList = getCommaList("default", "autoremoveignorelist") ?? + defaultAutoRemoveIgnoreList; if (c.walletType != "disabled") { c.lnRPCHost = f.get("payment", "lnrpchost") ?? "localhost:10009"; diff --git a/bruig/flutterui/bruig/lib/main.dart b/bruig/flutterui/bruig/lib/main.dart index 9db1255a..cc6bac3c 100644 --- a/bruig/flutterui/bruig/lib/main.dart +++ b/bruig/flutterui/bruig/lib/main.dart @@ -167,7 +167,8 @@ class _AppState extends State with WindowListener { cfg.circuitLimit, cfg.noLoadChatHistory, cfg.autoHandshakeInterval, - cfg.autoRemoveIdleUsersInterval); + cfg.autoRemoveIdleUsersInterval, + cfg.autoRemoveIgnoreList); await Golib.initClient(initArgs); navkey.currentState!.pushReplacementNamed(OverviewScreen.routeName); diff --git a/bruig/flutterui/plugin/lib/definitions.dart b/bruig/flutterui/plugin/lib/definitions.dart index 30df808a..5bb2e7d4 100644 --- a/bruig/flutterui/plugin/lib/definitions.dart +++ b/bruig/flutterui/plugin/lib/definitions.dart @@ -59,6 +59,8 @@ class InitClient { final int autoHandshakeInterval; @JsonKey(name: 'auto_remove_idle_users_interval') final int autoRemoveIdleUsersInterval; + @JsonKey(name: 'auto_remove_idle_users_ignore') + final List autoRemoveIdleUsersIgnore; InitClient( this.dbRoot, @@ -82,7 +84,8 @@ class InitClient { this.circuitLimit, this.noLoadChatHistory, this.autoHandshakeInterval, - this.autoRemoveIdleUsersInterval); + this.autoRemoveIdleUsersInterval, + this.autoRemoveIdleUsersIgnore); Map toJson() => _$InitClientToJson(this); } diff --git a/bruig/flutterui/plugin/lib/definitions.g.dart b/bruig/flutterui/plugin/lib/definitions.g.dart index 406a8a73..35fcc716 100644 --- a/bruig/flutterui/plugin/lib/definitions.g.dart +++ b/bruig/flutterui/plugin/lib/definitions.g.dart @@ -29,6 +29,9 @@ InitClient _$InitClientFromJson(Map json) => InitClient( json['no_load_chat_history'] as bool, json['auto_handshake_interval'] as int, json['auto_remove_idle_users_interval'] as int, + (json['auto_remove_idle_users_ignore'] as List) + .map((e) => e as String) + .toList(), ); Map _$InitClientToJson(InitClient instance) => @@ -55,6 +58,7 @@ Map _$InitClientToJson(InitClient instance) => 'no_load_chat_history': instance.noLoadChatHistory, 'auto_handshake_interval': instance.autoHandshakeInterval, 'auto_remove_idle_users_interval': instance.autoRemoveIdleUsersInterval, + 'auto_remove_idle_users_ignore': instance.autoRemoveIdleUsersIgnore, }; IDInit _$IDInitFromJson(Map json) => IDInit( diff --git a/bruig/golib/command_handlers.go b/bruig/golib/command_handlers.go index c23f672c..712bf153 100644 --- a/bruig/golib/command_handlers.go +++ b/bruig/golib/command_handlers.go @@ -390,8 +390,9 @@ func handleInitClient(handle uint32, args initClient) error { ResourcesProvider: resRouter, NoLoadChatHistory: args.NoLoadChatHistory, - AutoHandshakeInterval: time.Duration(args.AutoHandshakeInterval) * time.Second, - AutoRemoveIdleUsersInterval: time.Duration(args.AutoRemoveIdleUsersInterval) * time.Second, + AutoHandshakeInterval: time.Duration(args.AutoHandshakeInterval) * time.Second, + AutoRemoveIdleUsersInterval: time.Duration(args.AutoRemoveIdleUsersInterval) * time.Second, + AutoRemoveIdleUsersIgnoreList: args.AutoRemoveIdleUsersIgnore, CertConfirmer: func(ctx context.Context, cs *tls.ConnectionState, svrID *zkidentity.PublicIdentity) error { diff --git a/bruig/golib/definitions.go b/bruig/golib/definitions.go index 2c472690..fec82497 100644 --- a/bruig/golib/definitions.go +++ b/bruig/golib/definitions.go @@ -38,8 +38,9 @@ type initClient struct { TorIsolation bool `json:"torisolation"` CircuitLimit uint32 `json:"circuit_limit"` - AutoHandshakeInterval int64 `json:"auto_handshake_interval"` - AutoRemoveIdleUsersInterval int64 `json:"auto_remove_idle_users_interval"` + AutoHandshakeInterval int64 `json:"auto_handshake_interval"` + AutoRemoveIdleUsersInterval int64 `json:"auto_remove_idle_users_interval"` + AutoRemoveIdleUsersIgnore []string `json:"auto_remove_idle_users_ignore"` } type iDInit struct { diff --git a/client/client.go b/client/client.go index 5db356cd..04a98b45 100644 --- a/client/client.go +++ b/client/client.go @@ -189,6 +189,12 @@ type Config struct { // automatically removed from GCs the local client admins and will be // automatically unsubscribed from posts. AutoRemoveIdleUsersInterval time.Duration + + // AutoRemoveIdleUsersIgnoreList is a list of users that should NOT be + // forcibly unsubscribed even if they are idle. The values may be an + // user's nick or the prefix of the string representatation of its nick + // (i.e. anything acceptable as returned by UserByNick()). + AutoRemoveIdleUsersIgnoreList []string } // logger creates a logger for the given subsystem in the configured backend. diff --git a/client/client_kx.go b/client/client_kx.go index 0d427e97..eec95c11 100644 --- a/client/client_kx.go +++ b/client/client_kx.go @@ -719,8 +719,31 @@ func (c *Client) unsubIdleUsers(limitInterval, lastHandshakeInterval time.Durati adminGCs = append(adminGCs, &gcs[i]) } + c.log.Debugf("Starting auto unsubscribe of idle users with limitDate %s "+ + "and limitHandshakeDate %s", limitDate.Format(time.RFC3339), + limitHandshakeDate.Format(time.RFC3339)) + + // Build ignore list. + ignoreList := make(map[clientintf.UserID]struct{}, len(c.cfg.AutoRemoveIdleUsersIgnoreList)) + for _, nick := range c.cfg.AutoRemoveIdleUsersIgnoreList { + uid, err := c.UIDByNick(nick) + if err == nil { + ignoreList[uid] = struct{}{} + } else { + c.log.Warnf("User %q in list to ignore from auto remove "+ + "not found", nick) + } + } + users := c.rul.userList() for _, uid := range users { + // Do not perform autounsub if this user is in the list to + // ignore unsubbing. + if _, ok := ignoreList[uid]; ok { + c.log.Tracef("Ignoring %s in auto unsubscribe action", uid) + continue + } + ru, err := c.rul.byID(uid) if err != nil { continue @@ -755,9 +778,11 @@ func (c *Client) unsubIdleUsers(limitInterval, lastHandshakeInterval time.Durati continue } - ru.log.Infof("User %s is idle (last received msg time is %s). "+ - "Removing from any active subscriptions and GCs.", - strescape.Nick(ru.Nick()), lastDecTime.Format(time.RFC3339)) + ru.log.Infof("User %s is idle (last received msg time is %s, "+ + "last handshake attempt time is %s). Removing from all "+ + "active subscriptions and GCs.", + strescape.Nick(ru.Nick()), lastDecTime.Format(time.RFC3339), + ab.LastHandshakeAttempt.Format(time.RFC3339)) c.ntfns.notifyUnsubscribingIdleRemote(ru, lastDecTime) // Forcibly make user unsub from posts.