diff --git a/README.md b/README.md index 124af7cb..8d2a5b35 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Oragono is a modern IRC server written in Go. Its core design principles are: * Being simple to set up and use * Combining the features of an ircd, a services framework, and a bouncer (integrated account management, history storage, and bouncer functionality) -* Bleeding-edge [IRCv3 support](http://ircv3.net/software/servers.html), suitable for use as an IRCv3 reference implementation +* Bleeding-edge [IRCv3 support](https://ircv3.net/software/servers.html), suitable for use as an IRCv3 reference implementation * Highly customizable via a rehashable (i.e., reloadable at runtime) YAML config Oragono is a fork of the [Ergonomadic](https://github.com/jlatt/ergonomadic) IRC daemon <3 @@ -39,7 +39,7 @@ If you want to take a look at a running Oragono instance or test some client cod * automated client connection limits * passwords stored with [bcrypt](https://godoc.org/golang.org/x/crypto) * banning ips/nets and masks with `KLINE` and `DLINE` -* [IRCv3 support](http://ircv3.net/software/servers.html) +* [IRCv3 support](https://ircv3.net/software/servers.html) * a heavy focus on developing with [specifications](https://oragono.io/specs.html) ## Installation @@ -112,7 +112,7 @@ oragono run ### How to register a channel -1. Register your account with `/NS REGISTER ` +1. Register your account with `/NS REGISTER ` 2. Join the channel with `/join #channel` 3. Register the channel with `/CS REGISTER #channel` diff --git a/conventional.yaml b/conventional.yaml index 77c4e000..0e4b5def 100644 --- a/conventional.yaml +++ b/conventional.yaml @@ -434,6 +434,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/ @@ -495,6 +501,10 @@ channels: # how many channels can each account register? max-channels-per-account: 15 + # as a crude countermeasure against spambots, anonymous connections younger + # than this value will get an empty response to /LIST (a time period of 0 disables) + list-delay: 0s + # operator classes oper-classes: # local operator diff --git a/distrib/systemd/oragono.service b/distrib/systemd/oragono.service new file mode 100644 index 00000000..6af6ad6f --- /dev/null +++ b/distrib/systemd/oragono.service @@ -0,0 +1,19 @@ +[Unit] +Description=oragono +After=network.target +# If you are using MySQL for history storage, comment out the above line +# and uncomment these two instead: +# Requires=mysql.service +# After=network.target mysql.service + +[Service] +Type=simple +User=oragono +WorkingDirectory=/home/oragono +ExecStart=/home/oragono/oragono run --conf /home/oragono/ircd.yaml +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +LimitNOFILE=1048576 + +[Install] +WantedBy=multi-user.target diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 8f8fd901..733441d8 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -22,6 +22,7 @@ _Copyright © Daniel Oaks , Shivaram Lingamneni /`). -3. Here are how the config file keys map to LE files: - - `cert: tls.crt` is `live//fullchain.pem` - - ` key: tls.key` is `live//privkey.pem` -4. You may need to copy the `pem` files to another directory so Oragono can read them, or similarly use a script like [this one](https://github.com/darwin-network/slash/blob/master/etc/bin/install-lecerts) to automagically do something similar. -5. By default, `certbot` will automatically renew your certificates. Oragono will only reread certificates when it is restarted, or during a rehash (e.g., on receiving the `/rehash` command or the `SIGHUP` signal). You can add an executable script to `/etc/letsencrypt/renewal-hooks/post` that can perform the rehash. Here's one example of such a script: - -```bash -#!/bin/bash -pkill -HUP oragono -``` - -The main issues you'll run into are going to be permissions issues. This is because by default, certbot will generate certificates that non-root users can't (and probably shouldn't) read. If you run into trouble, look over the script in step **4** and/or make sure you're copying the files to somewhere else, as well as giving them correct permissions with `chown`, `chgrp` and `chmod`. - -On other platforms or with alternative ACME tools, you may need to use other steps or the specific files may be named differently. +IRC has traditionally been available over both plaintext (on port 6667) and SSL/TLS (on port 6697). We recommend that you make your server available exclusively via TLS, since exposing plaintext access allows for unauthorized interception or modification of user data or passwords. The default config file no longer exposes a plaintext port, so if you haven't modified your `listeners` section, you're good to go. +For a quickstart guide to obtaining valid TLS certificates from Let's Encrypt, see the "productionizing" section of the manual above. ## How can I "redirect" users from plaintext to TLS? diff --git a/irc/accounts.go b/irc/accounts.go index 9926c900..68a8b7a6 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -1174,7 +1174,12 @@ func (am *AccountManager) Unregister(account string, erase bool) error { var channelsStr string keepProtections := false am.server.store.Update(func(tx *buntdb.Tx) error { + // get the unfolded account name; for an active account, this is + // stored under accountNameKey, for an unregistered account under unregisteredKey accountName, _ = tx.Get(accountNameKey) + if accountName == "" { + accountName, _ = tx.Get(unregisteredKey) + } if erase { tx.Delete(unregisteredKey) } else { diff --git a/irc/channel.go b/irc/channel.go index f0ff6cef..c18eddc7 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -541,10 +541,16 @@ func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) strin func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool { channel.stateMutex.RLock() + founder := channel.registeredFounder clientModes := channel.members[client] targetModes := channel.members[target] channel.stateMutex.RUnlock() + if founder != "" && founder == client.Account() { + // #950: founder can kick or whatever without actually having the +q mode + return true + } + return channelUserModeHasPrivsOver(clientModes.HighestChannelUserMode(), targetModes.HighestChannelUserMode()) } @@ -1064,6 +1070,25 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0]) rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) } + case history.Topic: + if eventPlayback { + rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "TOPIC", chname, item.Message.Message) + } else { + message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + } + case history.Mode: + params := make([]string, len(item.Message.Split)+1) + params[0] = chname + for i, pair := range item.Message.Split { + params[i+1] = pair.Message + } + if eventPlayback { + rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "MODE", params...) + } else { + message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " ")) + rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message) + } } } } @@ -1113,22 +1138,30 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe } channel.stateMutex.Lock() + chname := channel.name channel.topic = topic channel.topicSetBy = client.nickMaskString channel.topicSetTime = time.Now().UTC() channel.stateMutex.Unlock() - prefix := client.NickMaskString() + details := client.Details() + message := utils.MakeMessage(topic) + rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic) for _, member := range channel.Members() { for _, session := range member.Sessions() { - if session == rb.session { - rb.Add(nil, prefix, "TOPIC", channel.name, topic) - } else { - session.Send(nil, prefix, "TOPIC", channel.name, topic) + if session != rb.session { + session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic) } } } + channel.AddHistoryItem(history.Item{ + Type: history.Topic, + Nick: details.nickMask, + AccountName: details.accountName, + Message: message, + }) + channel.MarkDirty(IncludeTopic) } @@ -1251,13 +1284,16 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod } } - channel.AddHistoryItem(history.Item{ - Type: histType, - Message: message, - Nick: nickmask, - AccountName: account, - Tags: clientOnlyTags, - }) + // #959: don't save STATUSMSG + if minPrefixMode == modes.Mode(0) { + channel.AddHistoryItem(history.Item{ + Type: histType, + Message: message, + Nick: nickmask, + AccountName: account, + Tags: clientOnlyTags, + }) + } } func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChange, rb *ResponseBuffer) (applied bool, result modes.ModeChange) { diff --git a/irc/chanserv.go b/irc/chanserv.go index 1aa53132..2a08d313 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -244,7 +244,7 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str if member.Account() == change.Arg { applied, change := channel.applyModeToMember(client, change, rb) if applied { - announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, rb) + announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, "*", rb) } } } @@ -291,7 +291,7 @@ func csOpHandler(server *Server, client *Client, command string, params []string }, rb) if applied { - announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, rb) + announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", rb) } csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName)) @@ -343,7 +343,7 @@ func csRegisterHandler(server *Server, client *Client, command string, params [] }, rb) if applied { - announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, rb) + announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", rb) } } 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/client_lookup_set.go b/irc/client_lookup_set.go index 048b0d3b..6d9834f9 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -205,9 +205,18 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick // the client may just be changing case if currentClient != nil && currentClient != client && session != nil { // these conditions forbid reattaching to an existing session: - if registered || !bouncerAllowed || account == "" || account != currentClient.Account() || client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) { + if registered || !bouncerAllowed || account == "" || account != currentClient.Account() { return "", errNicknameInUse } + // check TLS modes + if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) { + if useAccountName { + // #955: this is fatal because they can't fix it by trying a different nick + return "", errInsecureReattach + } else { + return "", errNicknameInUse + } + } reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session) if !reattachSuccessful { return "", errNicknameInUse diff --git a/irc/config.go b/irc/config.go index c5d652eb..9b4849da 100644 --- a/irc/config.go +++ b/irc/config.go @@ -263,6 +263,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"` @@ -552,6 +554,7 @@ type Config struct { OperatorOnly bool `yaml:"operator-only"` MaxChannelsPerAccount int `yaml:"max-channels-per-account"` } + ListDelay time.Duration `yaml:"list-delay"` } OperClasses map[string]*OperClassConfig `yaml:"oper-classes"` @@ -984,6 +987,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/errors.go b/irc/errors.go index d860e9e2..fc11e320 100644 --- a/irc/errors.go +++ b/irc/errors.go @@ -42,6 +42,7 @@ var ( errNickMissing = errors.New("nick missing") errNicknameInvalid = errors.New("invalid nickname") errNicknameInUse = errors.New("nickname in use") + errInsecureReattach = errors.New("insecure reattach") errNicknameReserved = errors.New("nickname is reserved") errNickAccountMismatch = errors.New(`Your nickname must match your account name; try logging out and logging back in with SASL`) errNoExistingBan = errors.New("Ban does not exist") diff --git a/irc/handlers.go b/irc/handlers.go index 31d4eae6..dbcb6793 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1408,6 +1408,14 @@ func languageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb * // LIST [{,}] [{,}] func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { + config := server.Config() + if time.Since(client.ctime) < config.Channels.ListDelay && client.Account() == "" && !client.HasMode(modes.Operator) { + remaining := time.Until(client.ctime.Add(config.Channels.ListDelay)) + csNotice(rb, fmt.Sprintf(client.t("This server requires that you wait %v after connecting before you can use /LIST. You have %v left."), config.Channels.ListDelay, remaining)) + rb.Add(nil, server.name, RPL_LISTEND, client.Nick(), client.t("End of LIST")) + return false + } + // get channels var channels []string for _, param := range msg.Params { @@ -1520,24 +1528,35 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res } // process mode changes, include list operations (an empty set of changes does a list) applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb) - announceCmodeChanges(channel, applied, client.NickMaskString(), rb) + details := client.Details() + announceCmodeChanges(channel, applied, details.nickMask, details.accountName, rb) return false } -func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source string, rb *ResponseBuffer) { +func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName string, rb *ResponseBuffer) { // send out changes if len(applied) > 0 { - //TODO(dan): we should change the name of String and make it return a slice here - args := append([]string{channel.name}, applied.Strings()...) - rb.Add(nil, source, "MODE", args...) + message := utils.MakeMessage("") + changeStrings := applied.Strings() + for _, changeString := range changeStrings { + message.Split = append(message.Split, utils.MessagePair{Message: changeString}) + } + args := append([]string{channel.name}, changeStrings...) + rb.AddFromClient(message.Time, message.Msgid, source, accountName, nil, "MODE", args...) for _, member := range channel.Members() { for _, session := range member.Sessions() { if session != rb.session { - session.Send(nil, source, "MODE", args...) + session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, nil, "MODE", args...) } } } + channel.AddHistoryItem(history.Item{ + Type: history.Mode, + Nick: source, + AccountName: accountName, + Message: message, + }) } } @@ -2054,7 +2073,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp } // must pass at least one check, and all enabled checks - var checkPassed, checkFailed bool + var checkPassed, checkFailed, passwordFailed bool oper := server.GetOperator(msg.Params[0]) if oper != nil { if oper.Fingerprint != "" { @@ -2065,8 +2084,11 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp } } if !checkFailed && oper.Pass != nil { - if len(msg.Params) == 1 || bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil { + if len(msg.Params) == 1 { checkFailed = true + } else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil { + checkFailed = true + passwordFailed = true } else { checkPassed = true } @@ -2075,11 +2097,18 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp if !checkPassed || checkFailed { rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect")) - client.Quit(client.t("Password incorrect"), rb.session) - return true + // #951: only disconnect them if we actually tried to check a password for them + if passwordFailed { + client.Quit(client.t("Password incorrect"), rb.session) + return true + } else { + return false + } } - applyOper(client, oper, rb) + if oper != nil { + applyOper(client, oper, rb) + } return false } diff --git a/irc/history/history.go b/irc/history/history.go index b7317943..5a1ece64 100644 --- a/irc/history/history.go +++ b/irc/history/history.go @@ -22,6 +22,7 @@ const ( Mode Tagmsg Nick + Topic ) const ( 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/irc/nickname.go b/irc/nickname.go index a49a2137..59d7beb2 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -25,12 +25,11 @@ var ( restrictedSkeletons = make(map[string]bool) ) -// returns whether the change succeeded or failed -func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) bool { +func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) error { currentNick := client.Nick() details := target.Details() if details.nick == nickname { - return true + return nil } hadNick := details.nick != "*" origNickMask := details.nickMask @@ -52,7 +51,7 @@ func performNickChange(server *Server, client *Client, target *Client, session * rb.Add(nil, server.name, ERR_UNKNOWNERROR, currentNick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error())) } if err != nil { - return false + return err } message := utils.MakeMessage("") @@ -88,7 +87,7 @@ func performNickChange(server *Server, client *Client, target *Client, session * client.server.monitorManager.AlertAbout(target, true) target.nickTimer.Touch(rb) } // else: these will be deferred to the end of registration (see #572) - return true + return nil } func (server *Server) RandomlyRename(client *Client) { @@ -124,7 +123,7 @@ func fixupNickEqualsAccount(client *Client, rb *ResponseBuffer, config *Config) if !client.registered { return true } - if !performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) { + if performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) != nil { client.server.accounts.Logout(client) nsNotice(rb, client.t("A client is already using that account; try logging out and logging back in with SASL")) return false diff --git a/irc/nickserv.go b/irc/nickserv.go index 4ea051c0..1f52a5df 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -841,6 +841,8 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params if erase { // account may not be in a loadable state, e.g., if it was unregistered accountName = username + // make the confirmation code nondeterministic for ERASE + registeredAt = server.ctime } else { account, err := server.accounts.LoadAccount(username) if err == errAccountDoesNotExist { diff --git a/irc/server.go b/irc/server.go index d0e032dd..717fd3e8 100644 --- a/irc/server.go +++ b/irc/server.go @@ -438,11 +438,14 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { } rb := NewResponseBuffer(session) - nickAssigned := performNickChange(server, c, c, session, c.preregNick, rb) + nickError := performNickChange(server, c, c, session, c.preregNick, rb) rb.Send(true) - if !nickAssigned { + if nickError == errInsecureReattach { + c.Quit(c.t("You can't mix secure and insecure connections to this account"), nil) + return true + } else if nickError != nil { c.preregNick = "" - return + return false } if session.client != c { @@ -450,7 +453,7 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { // we'll play the reg burst later, on the new goroutine associated with // (thisSession, otherClient). This is to avoid having to transfer state // like nickname, hostname, etc. to show the correct values in the reg burst. - return + return false } // check KLINEs diff --git a/languages/ro-RO-irc.lang.json b/languages/ro-RO-irc.lang.json index 27d465ae..da024063 100644 --- a/languages/ro-RO-irc.lang.json +++ b/languages/ro-RO-irc.lang.json @@ -113,7 +113,7 @@ "Former Core Developers:": "Foști dezvoltatori:", "Founder: %s": "Fondator: %s", "GHOSTed by %s": "%s a folosit GHOST", - "Given current server settings, the channel history setting is: %s": "", + "Given current server settings, the channel history setting is: %s": "Conform setărilor actuale ale serverului, setarea pentru istoricul de canal este: %s", "Given current server settings, your client is always-on": "Conform setărilor actuale ale serverului, clientul tău are activă opțiunea de conectare permanentă", "Given current server settings, your client is not always-on": "Conform setărilor actuale ale serverului, clientul tău nu are activă opțiunea de conectare permanentă", "Given current server settings, your direct message history setting is: %s": "Conform setărilor actuale ale serverului, setarea aferentă istoricului de mesaje este: %s", @@ -228,7 +228,7 @@ "Successfully ungrouped nick %s with your account": "Pseudonimul %s a fost degrupat de la contul tău, cu succes", "Successfully unpurged channel %s from the server": "Canalul %s nu mai este purjat din server", "Successfully unregistered account %s": "Contul %s a fost șters cu succes", - "That account is set to always-on; try logging out and logging back in with SASL": "", + "That account is set to always-on; try logging out and logging back in with SASL": "Acel cont are activă setarea conectare-permanentă; încearcă să te deconectezi și să te reconectezi cu SASL", "That certificate fingerprint is already associated with another account": "Amprenta de certificat este deja asociată unui alt cont", "That certificate fingerprint was already authorized": "Amprenta certificatului a fost autorizată deja", "That channel is not registered": "Acel canal nu este înregistrat", diff --git a/oragono.yaml b/oragono.yaml index 1799b022..1deb50b2 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -455,6 +455,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/ @@ -516,6 +522,10 @@ channels: # how many channels can each account register? max-channels-per-account: 15 + # as a crude countermeasure against spambots, anonymous connections younger + # than this value will get an empty response to /LIST (a time period of 0 disables) + list-delay: 0s + # operator classes oper-classes: # local operator