From 0241e0c31d1674397d42be9fd757393f844cd13f Mon Sep 17 00:00:00 2001 From: Alex Jaspersen Date: Thu, 28 May 2020 15:53:14 +0000 Subject: [PATCH 1/5] Apply default user modes just before registration. Previously, we were applying defaults before the user had completed registration. This meant that the number of invisible users was incremented when the user connected, and then the total was incremented when registration was completed. Now both counters are updated at the same time. If a user disconnects prior to registration, +i has not yet been applied so it would not be decremented. --- irc/client.go | 1 - irc/config.go | 2 +- irc/modes.go | 31 +++++++++++++++++-------------- irc/modes_test.go | 14 +++++++------- irc/server.go | 9 ++++++++- irc/stats.go | 5 ++++- 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/irc/client.go b/irc/client.go index 4445ad5c..ab2b0176 100644 --- a/irc/client.go +++ b/irc/client.go @@ -337,7 +337,6 @@ func (server *Server) RunClient(conn IRCConn) { session.idletimer.Initialize(session) session.resetFakelag() - ApplyUserModeChanges(client, config.Accounts.defaultUserModes, false, nil) if wConn.Secure { client.SetMode(modes.TLS, true) } diff --git a/irc/config.go b/irc/config.go index 277413e1..d997f6d4 100644 --- a/irc/config.go +++ b/irc/config.go @@ -256,7 +256,7 @@ type AccountConfig struct { exemptedNets []net.IPNet } `yaml:"require-sasl"` DefaultUserModes *string `yaml:"default-user-modes"` - defaultUserModes modes.ModeChanges + defaultUserModes modes.Modes LDAP ldap.ServerConfig LoginThrottling ThrottleConfig `yaml:"login-throttling"` SkipServerPassword bool `yaml:"skip-server-password"` diff --git a/irc/modes.go b/irc/modes.go index 06dd8e51..acea6b9d 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -23,7 +23,7 @@ var ( // DefaultUserModes are set on all users when they login. // this can be overridden in the `accounts` config, with the `default-user-modes` key - DefaultUserModes = modes.ModeChanges{} + DefaultUserModes = modes.Modes{} ) // ApplyUserModeChanges applies the given changes, and returns the applied changes. @@ -110,32 +110,35 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool, return applied } +// parseDefaultModes uses the provided mode change parser to parse the rawModes. +func parseDefaultModes(rawModes string, parser func(params ...string) (modes.ModeChanges, map[rune]bool)) modes.Modes { + modeChangeStrings := strings.Fields(rawModes) + modeChanges, _ := parser(modeChangeStrings...) + defaultModes := make(modes.Modes, 0) + for _, modeChange := range modeChanges { + if modeChange.Op == modes.Add { + defaultModes = append(defaultModes, modeChange.Mode) + } + } + return defaultModes +} + // ParseDefaultChannelModes parses the `default-modes` line of the config func ParseDefaultChannelModes(rawModes *string) modes.Modes { if rawModes == nil { // not present in config, fall back to compile-time default return DefaultChannelModes } - modeChangeStrings := strings.Fields(*rawModes) - modeChanges, _ := modes.ParseChannelModeChanges(modeChangeStrings...) - defaultChannelModes := make(modes.Modes, 0) - for _, modeChange := range modeChanges { - if modeChange.Op == modes.Add { - defaultChannelModes = append(defaultChannelModes, modeChange.Mode) - } - } - return defaultChannelModes + return parseDefaultModes(*rawModes, modes.ParseChannelModeChanges) } // ParseDefaultUserModes parses the `default-user-modes` line of the config -func ParseDefaultUserModes(rawModes *string) modes.ModeChanges { +func ParseDefaultUserModes(rawModes *string) modes.Modes { if rawModes == nil { // not present in config, fall back to compile-time default return DefaultUserModes } - modeChangeStrings := strings.Fields(*rawModes) - modeChanges, _ := modes.ParseUserModeChanges(modeChangeStrings...) - return modeChanges + return parseDefaultModes(*rawModes, modes.ParseUserModeChanges) } // #1021: channel key must be valid as a non-final parameter diff --git a/irc/modes_test.go b/irc/modes_test.go index ca7aa6a8..005d0555 100644 --- a/irc/modes_test.go +++ b/irc/modes_test.go @@ -43,19 +43,19 @@ func TestParseDefaultUserModes(t *testing.T) { var parseTests = []struct { raw *string - expected modes.ModeChanges + expected modes.Modes }{ - {&iR, modes.ModeChanges{{Mode: modes.Invisible, Op: modes.Add}, {Mode: modes.RegisteredOnly, Op: modes.Add}}}, - {&i, modes.ModeChanges{{Mode: modes.Invisible, Op: modes.Add}}}, - {&empty, modes.ModeChanges{}}, - {&rminusi, modes.ModeChanges{{Mode: modes.RegisteredOnly, Op: modes.Add}}}, - {nil, modes.ModeChanges{}}, + {&iR, modes.Modes{modes.Invisible, modes.RegisteredOnly}}, + {&i, modes.Modes{modes.Invisible}}, + {&empty, modes.Modes{}}, + {&rminusi, modes.Modes{modes.RegisteredOnly}}, + {nil, modes.Modes{}}, } for _, testcase := range parseTests { result := ParseDefaultUserModes(testcase.raw) if !reflect.DeepEqual(result, testcase.expected) { - t.Errorf("expected modes %v, got %v", testcase.expected, result) + t.Errorf("expected modes %s, got %s", testcase.expected, result) } } } diff --git a/irc/server.go b/irc/server.go index bce3a101..dfeb118a 100644 --- a/irc/server.go +++ b/irc/server.go @@ -266,11 +266,18 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { return true } + // Apply default user modes (without updating the invisible counter) + // The number of invisible users will be updated by server.stats.Register + // if we're using default user mode +i. + for _, defaultMode := range server.Config().Accounts.defaultUserModes { + c.SetMode(defaultMode, true) + } + // registration has succeeded: c.SetRegistered() // count new user in statistics - server.stats.Register() + server.stats.Register(c.HasMode(modes.Invisible)) server.monitorManager.AlertAbout(c, true) server.playRegistrationBurst(session) diff --git a/irc/stats.go b/irc/stats.go index a54f7269..ec765177 100644 --- a/irc/stats.go +++ b/irc/stats.go @@ -41,9 +41,12 @@ func (s *Stats) AddRegistered(invisible, operator bool) { } // Transition a client from unregistered to registered -func (s *Stats) Register() { +func (s *Stats) Register(invisible bool) { s.mutex.Lock() s.Unknown -= 1 + if invisible { + s.Invisible += 1 + } s.Total += 1 s.setMax() s.mutex.Unlock() From 1477dab6042021de1374b831111d94f61299a364 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 28 May 2020 13:16:17 -0400 Subject: [PATCH 2/5] fix #1080 --- irc/handlers.go | 2 ++ irc/nickname.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/irc/handlers.go b/irc/handlers.go index 53fa2486..b9810b25 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -2724,6 +2724,8 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res } clientNick := client.Nick() rb.Add(nil, client.server.name, RPL_WHOISUSER, clientNick, service.Name, service.Name, "localhost", "*", fmt.Sprintf(client.t("Network service, for more info /msg %s HELP"), service.Name)) + // #1080: + rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, clientNick, service.Name, client.t("is a network service")) // hehe if client.HasMode(modes.TLS) { rb.Add(nil, client.server.name, RPL_WHOISSECURE, clientNick, service.Name, client.t("is using a secure connection")) diff --git a/irc/nickname.go b/irc/nickname.go index 9c54120f..718c948c 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -18,6 +18,9 @@ import ( var ( restrictedNicknames = []string{ "=scene=", // used for rp commands + "Global", // global announcements on some networks + // common services not implemented by us: + "MemoServ", "BotServ", "OperServ", } restrictedCasefoldedNicks = make(map[string]bool) From beea32adfea07736152b83c4dd7bd991a310a90d Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 28 May 2020 14:27:41 -0400 Subject: [PATCH 3/5] fix #1058 --- irc/modes/modes.go | 25 +++++++++++++++++++++++++ irc/server.go | 9 +++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/irc/modes/modes.go b/irc/modes/modes.go index 9e311298..ae2d9224 100644 --- a/irc/modes/modes.go +++ b/irc/modes/modes.go @@ -6,6 +6,7 @@ package modes import ( + "sort" "strings" "github.com/oragono/oragono/irc/utils" @@ -418,3 +419,27 @@ func (set *ModeSet) HighestChannelUserMode() (result Mode) { } return } + +type ByCodepoint Modes + +func (a ByCodepoint) Len() int { return len(a) } +func (a ByCodepoint) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByCodepoint) Less(i, j int) bool { return a[i] < a[j] } + +func RplMyInfo() (param1, param2, param3 string) { + userModes := make(Modes, len(SupportedUserModes)) + copy(userModes, SupportedUserModes) + sort.Sort(ByCodepoint(userModes)) + + channelModes := make(Modes, len(SupportedChannelModes)+len(ChannelUserModes)) + copy(channelModes, SupportedChannelModes) + copy(channelModes[len(SupportedChannelModes):], ChannelUserModes) + sort.Sort(ByCodepoint(channelModes)) + + // XXX enumerate these by hand, i can't see any way to DRY this + channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit} + channelParametrizedModes = append(channelParametrizedModes, ChannelUserModes...) + sort.Sort(ByCodepoint(channelParametrizedModes)) + + return userModes.String(), channelModes.String(), channelParametrizedModes.String() +} diff --git a/irc/server.go b/irc/server.go index bce3a101..484cee08 100644 --- a/irc/server.go +++ b/irc/server.go @@ -36,10 +36,8 @@ var ( // common error line to sub values into errorMsg = "ERROR :%s\r\n" - // supportedUserModesString acts as a cache for when we introduce users - supportedUserModesString = modes.SupportedUserModes.String() - // supportedChannelModesString acts as a cache for when we introduce users - supportedChannelModesString = modes.SupportedChannelModes.String() + // three final parameters of 004 RPL_MYINFO, enumerating our supported modes + rplMyInfo1, rplMyInfo2, rplMyInfo3 = modes.RplMyInfo() // whitelist of caps to serve on the STS-only listener. In particular, // never advertise SASL, to discourage people from sending their passwords: @@ -290,8 +288,7 @@ func (server *Server) playRegistrationBurst(session *Session) { session.Send(nil, server.name, RPL_WELCOME, d.nick, fmt.Sprintf(c.t("Welcome to the Internet Relay Network %s"), d.nick)) session.Send(nil, server.name, RPL_YOURHOST, d.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver)) session.Send(nil, server.name, RPL_CREATED, d.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123))) - //TODO(dan): Look at adding last optional [] parameter - session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString) + session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, rplMyInfo1, rplMyInfo2, rplMyInfo3) if c.isSTSOnly { for _, line := range server.Config().Server.STS.bannerLines { From 3491d43a2723c3a46a1d851ee2c8ce2dbb42be0a Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Fri, 29 May 2020 19:14:20 +1000 Subject: [PATCH 4/5] New translations irc.lang.json (Romanian) --- languages/ro-RO-irc.lang.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/languages/ro-RO-irc.lang.json b/languages/ro-RO-irc.lang.json index da024063..9b2a5c81 100644 --- a/languages/ro-RO-irc.lang.json +++ b/languages/ro-RO-irc.lang.json @@ -235,7 +235,7 @@ "That nickname is already reserved by someone else": "Pseudonimul este rezervat de altcineva", "That nickname is not registered": "Pseudonimul nu este înregistrat", "That vhost isn't being offered by the server": "Gazda virtuală nu este oferită de către server", - "The following vhosts are available and can be chosen with /HOSTSERV TAKE:": "", + "The following vhosts are available and can be chosen with /HOSTSERV TAKE:": "Următoarele gazde virtuale sunt disponibile și pot fi alese cu comanda /HOSTSERV TAKE:", "The server does not offer any vhosts": "Serverul nu oferă nici o gazdă virtuală", "The server does not offer any vhosts, but you can request one with /HOSTSERV REQUEST": "Serverul nu oferă nici o gazdă virtuală, actualmente. Poți cere una, cu comanda /HOSTSERV REQUEST", "The stored channel history setting is: %s": "Setarea pentru stocarea istoricului mesajelor canalului este: %s", @@ -317,7 +317,7 @@ "You're not logged into an account": "Nu te-ai autentificat la niciun cont", "You're not on that channel": "Nu te afli pe acel canal", "You're now logged in as %s": "Te-ai autentificat ca: %s", - "Your account credentials are managed externally and cannot be changed here": "", + "Your account credentials are managed externally and cannot be changed here": "Credențialele contului tău sunt administrate extern și nu pot fi modificate aici", "Your account is not configured to receive autoreplayed missed messages": "Contul tău nu este configurat pentru derularea automată a mesajelor pierdute", "Your client does not support BRB": "Clientul folosit de tine nu suportă BRB", "Your host is %[1]s, running version %[2]s": "Gazda ta este %[1]s, rulând versiunea %[2]s", From 2cb91bab0464000e83014c0067284556e8c5873a Mon Sep 17 00:00:00 2001 From: Alex Jaspersen Date: Sat, 30 May 2020 15:45:43 +0000 Subject: [PATCH 5/5] Fix issue with one-character args in ArgsToStrings. --- irc/utils/args.go | 2 +- irc/utils/args_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/irc/utils/args.go b/irc/utils/args.go index 64475da1..ea3f0c96 100644 --- a/irc/utils/args.go +++ b/irc/utils/args.go @@ -35,7 +35,7 @@ func ArgsToStrings(maxLength int, arguments []string, delim string) []string { continue } - if len(buffer) > 1 { + if len(buffer) > 0 { buffer += delim } buffer += arguments[0] diff --git a/irc/utils/args_test.go b/irc/utils/args_test.go index 5c84b26a..b846d122 100644 --- a/irc/utils/args_test.go +++ b/irc/utils/args_test.go @@ -5,6 +5,14 @@ package utils import "testing" +func TestArgsToStrings(t *testing.T) { + val := ArgsToStrings(512, []string{"a", "b", "c"}, ",") + assertEqual(val, []string{"a,b,c"}, t) + + val = ArgsToStrings(10, []string{"abcd", "efgh", "ijkl"}, ",") + assertEqual(val, []string{"abcd,efgh", "ijkl"}, t) +} + func TestStringToBool(t *testing.T) { val, err := StringToBool("on") assertEqual(val, true, t)