3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-25 21:39:25 +01:00

Merge remote-tracking branch 'origin/master' into websockets_draft.2

This commit is contained in:
Shivaram Lingamneni 2020-04-30 22:16:07 -04:00
commit 25813f6d3a
20 changed files with 268 additions and 77 deletions

View File

@ -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 <username> <email> <password>`
1. Register your account with `/NS REGISTER <password>`
2. Join the channel with `/join #channel`
3. Register the channel with `/CS REGISTER #channel`

View File

@ -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

View File

@ -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

View File

@ -22,6 +22,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
- Installing
- Windows
- macOS / Linux / Raspberry Pi
- Productionizing
- Upgrading
- Features
- User Accounts
@ -120,16 +121,47 @@ If you're using Arch Linux, you can also install the [`oragono` package](https:/
1. Create a volume for persistent data: `docker volume create oragono-data`
1. Run the container, exposing the default ports: `docker run -d --name oragono -v oragono-data:/ircd-data -p 6667:6667 -p 6697:6697 oragono/oragono:latest`
For further information and a sample docker-compose file see the separate [Docker documentation](https://github.com/oragono/oragono/blog/master/distrib/docker/README.md).
For further information and a sample docker-compose file see the separate [Docker documentation](https://github.com/oragono/oragono/blob/master/distrib/docker/README.md).
## Running oragono as a service on Linux
## Productionizing
The recommended way to operate oragono as a service on Linux is via systemd. This provides a standard interface for starting, stopping, and rehashing (via `systemctl reload`) the service. It also captures oragono's loglines (sent to stderr in the default configuration) and writes them to the system journal.
If you're using Arch, the abovementioned AUR package bundles a systemd file for starting and stopping the server. If you're rolling your own deployment, here's an [example](https://github.com/darwin-network/slash/blob/master/etc/systemd/system/ircd.service) of a systemd unit file that can be used to run Oragono as an unprivileged role user.
The only major distribution that currently packages Oragono is Arch Linux; the aforementioned AUR package includes a systemd unit file. However, it should be fairly straightforward to set up a productionized Oragono on any Linux distribution. Here's a quickstart guide for Debian/Ubuntu:
On a non-systemd system, oragono can be configured to log to a file and used [logrotate(8)](https://linux.die.net/man/8/logrotate), since it will reopen its log files (as well as rehashing the config file) upon receiving a SIGHUP.
1. Create a dedicated, unprivileged role user who will own the oragono process and all its associated files: `adduser --system --group oragono`. This user now has a home directory at `/home/oragono`.
1. Copy the executable binary `oragono`, the config file `ircd.yaml`, the database `ircd.db`, and the self-signed TLS certificate (`tls.crt` and `tls.key`) to `/home/oragono`. Ensure that they are all owned by the new oragono role user: `sudo chown oragono:oragono /home/oragono/*`. Ensure that the configuration file logs to stderr.
1. Install our example [oragono.service](https://github.com/oragono/oragono/blob/master/distrib/systemd/oragono.service) file to `/etc/systemd/system/oragono.service`.
1. Enable and start the new service with the following commands:
1. `systemctl daemon-reload`
1. `systemctl enable oragono.service`
1. `systemctl start oragono.service`
1. Confirm that the service started correctly with `systemctl status oragono.service`
The other major hurdle for productionizing (but one well worth the effort) is obtaining valid TLS certificates for your domain, if you haven't already done so:
1. The simplest way to get valid TLS certificates is from [Let's Encrypt](https://letsencrypt.org/) with [Certbot](https://certbot.eff.org/). The correct procedure will depend on whether you are already running a web server on port 80. If you are, follow the guides on the Certbot website; if you aren't, you can use `certbot certonly --standalone --preferred-challenges http -d example.com` (replace `example.com` with your domain).
1. At this point, you should have certificates available at `/etc/letsencrypt/live/example.com` (replacing `example.com` with your domain). You should serve `fullchain.pem` as the certificate and `privkey.pem` as its private key. However, these files are owned by root and the private key is not readable by the oragono role user, so you won't be able to use them directly in their current locations. You can write a post-renewal hook for certbot to make copies of these certificates accessible to the oragono role user. For example, install the following script as `/etc/letsencrypt/renewal-hooks/post/install-oragono-certificates`, again replacing `example.com` with your domain name, and chmod it 0755:
````bash
#!/bin/bash
set -eu
umask 077
cp /etc/letsencrypt/live/example.com/fullchain.pem /home/oragono/tls.crt
cp /etc/letsencrypt/live/example.com/privkey.pem /home/oragono/tls.key
chown oragono:oragono /home/oragono/tls.*
# rehash oragono, which will reload the certificates:
systemctl reload oragono.service
````
Executing this script manually will install the certificates for the first time and perform a rehash, enabling them.
If you are using Certbot 0.29.0 or higher, you can also change the ownership of the files under `/etc/letsencrypt` so that the oragono user can read them, as described in the [UnrealIRCd documentation](https://www.unrealircd.org/docs/Setting_up_certbot_for_use_with_UnrealIRCd#Tweaking_permissions_on_the_key_file).
On a non-systemd system, oragono can be configured to log to a file and used [logrotate(8)](https://linux.die.net/man/8/logrotate), since it will reopen its log files (as well as rehashing the config file) upon receiving a SIGHUP. To rehash manually outside the context of log rotation, you can use `killall -HUP oragono` or `pkill -HUP oragono`.
## Upgrading to a new version of Oragono
@ -366,30 +398,9 @@ Similarly, for a public channel (one without `+i`), users can ban nick/account n
# IRC over TLS
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. While the default config file exposes a plaintext public port, it also contains instructions on how to disable it or replace it with a 'dummy' plaintext listener that simply directs users to reconnect using TLS.
## How do I use Let's Encrypt certificates?
[Let's Encrypt](https://letsencrypt.org) is a widely recognized certificate authority that provides free certificates. Here's a quick-start guide for using those certificates with Oragono:
1. Follow this [guidance](https://letsencrypt.org/getting-started/) from Let's Encrypt to create your certificates.
2. You should now have a set of `pem` files, Mainly, we're interested in your `live/` Let's Encrypt directory (e.g. `/etc/letsencrypt/live/<site>/`).
3. Here are how the config file keys map to LE files:
- `cert: tls.crt` is `live/<site>/fullchain.pem`
- ` key: tls.key` is `live/<site>/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?

View File

@ -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 {

View File

@ -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,6 +1284,8 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
}
}
// #959: don't save STATUSMSG
if minPrefixMode == modes.Mode(0) {
channel.AddHistoryItem(history.Item{
Type: histType,
Message: message,
@ -1259,6 +1294,7 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
Tags: clientOnlyTags,
})
}
}
func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChange, rb *ResponseBuffer) (applied bool, result modes.ModeChange) {
target := channel.server.clients.Get(change.Arg)

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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

View File

@ -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())

View File

@ -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")

View File

@ -1408,6 +1408,14 @@ func languageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *
// LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
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"))
// #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
}
}
if oper != nil {
applyOper(client, oper, rb)
}
return false
}

View File

@ -22,6 +22,7 @@ const (
Mode
Tagmsg
Nick
Topic
)
const (

View File

@ -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.

View File

@ -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")

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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",

View File

@ -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