diff --git a/conventional.yaml b/conventional.yaml index e3929a83..0c9df059 100644 --- a/conventional.yaml +++ b/conventional.yaml @@ -430,6 +430,12 @@ accounts: offer-list: #- "oragono.test" + # modes that are set by default when a user connects + # if unset, no user modes will be set by default + # +i is invisible (a user's channels are hidden from whois replies) + # see /QUOTE HELP umodes for more user modes + # default-user-modes: +i + # support for deferring password checking to an external LDAP server # you should probably ignore this section! consult the grafana docs for details: # https://grafana.com/docs/grafana/latest/auth/ldap/ diff --git a/irc/client.go b/irc/client.go index 1443d6f7..b636678a 100644 --- a/irc/client.go +++ b/irc/client.go @@ -318,6 +318,10 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) { session.idletimer.Initialize(session) session.resetFakelag() + for _, defaultMode := range config.Accounts.defaultUserModes { + client.SetMode(defaultMode, true) + } + if conn.Config.TLSConfig != nil { client.SetMode(modes.TLS, true) // error is not useful to us here anyways so we can ignore it @@ -371,6 +375,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, alwaysOn: true, } + for _, defaultMode := range config.Accounts.defaultUserModes { + client.SetMode(defaultMode, true) + } + client.SetMode(modes.TLS, true) client.writerSemaphore.Initialize(1) client.history.Initialize(0, 0) diff --git a/irc/config.go b/irc/config.go index 42d58512..e40f04cc 100644 --- a/irc/config.go +++ b/irc/config.go @@ -261,6 +261,8 @@ type AccountConfig struct { Exempted []string exemptedNets []net.IPNet } `yaml:"require-sasl"` + DefaultUserModes *string `yaml:"default-user-modes"` + defaultUserModes modes.Modes LDAP ldap.ServerConfig LoginThrottling ThrottleConfig `yaml:"login-throttling"` SkipServerPassword bool `yaml:"skip-server-password"` @@ -982,6 +984,8 @@ func LoadConfig(filename string) (config *Config, err error) { } } + config.Accounts.defaultUserModes = ParseDefaultUserModes(config.Accounts.DefaultUserModes) + config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted) if err != nil { return nil, fmt.Errorf("Could not parse require-sasl exempted nets: %v", err.Error()) diff --git a/irc/modes.go b/irc/modes.go index e6f2c04e..86912474 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -20,6 +20,10 @@ var ( DefaultChannelModes = modes.Modes{ modes.NoOutside, modes.OpOnlyTopic, } + + // 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.Modes{} ) // ApplyUserModeChanges applies the given changes, and returns the applied changes. @@ -102,21 +106,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 parseDefaultModes(*rawModes, modes.ParseChannelModeChanges) +} + +// ParseDefaultUserModes parses the `default-user-modes` line of the config +func ParseDefaultUserModes(rawModes *string) modes.Modes { + if rawModes == nil { + // not present in config, fall back to compile-time default + return DefaultUserModes } - return defaultChannelModes + return parseDefaultModes(*rawModes, modes.ParseUserModeChanges) } // ApplyChannelModeChanges applies a given set of mode changes. diff --git a/irc/modes_test.go b/irc/modes_test.go index ece33313..005d0555 100644 --- a/irc/modes_test.go +++ b/irc/modes_test.go @@ -35,6 +35,31 @@ func TestParseDefaultChannelModes(t *testing.T) { } } +func TestParseDefaultUserModes(t *testing.T) { + iR := "+iR" + i := "+i" + empty := "" + rminusi := "+R -i" + + var parseTests = []struct { + raw *string + expected modes.Modes + }{ + {&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 %s, got %s", testcase.expected, result) + } + } +} + func TestUmodeGreaterThan(t *testing.T) { if !umodeGreaterThan(modes.Halfop, modes.Voice) { t.Errorf("expected Halfop > Voice") diff --git a/oragono.yaml b/oragono.yaml index 9ae504ab..9bf309e5 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -451,6 +451,12 @@ accounts: offer-list: #- "oragono.test" + # modes that are set by default when a user connects + # if unset, no user modes will be set by default + # +i is invisible (a user's channels are hidden from whois replies) + # see /QUOTE HELP umodes for more user modes + # default-user-modes: +i + # support for deferring password checking to an external LDAP server # you should probably ignore this section! consult the grafana docs for details: # https://grafana.com/docs/grafana/latest/auth/ldap/