From 2e3e4f72ba7fe66669106b089cce51569ba6d1d3 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 7 Apr 2021 22:16:18 -0400 Subject: [PATCH 1/8] fix inverted error check in deleteCorrespondents --- irc/mysql/history.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irc/mysql/history.go b/irc/mysql/history.go index c744af1f..0e24cf36 100644 --- a/irc/mysql/history.go +++ b/irc/mysql/history.go @@ -395,7 +395,7 @@ func (mysql *MySQL) deleteCorrespondents(ctx context.Context, threshold int64) { mysql.logError("error deleting correspondents", err) } else { count, err := result.RowsAffected() - if err != nil { + if !mysql.logError("error deleting correspondents", err) { mysql.logger.Debug(fmt.Sprintf("deleted %d correspondents entries", count)) } } From f9c1a00b9116946484c29fadb6f2fa480b9f0a21 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 7 Apr 2021 22:35:54 -0400 Subject: [PATCH 2/8] populate (tls.Certificate).Leaf --- irc/config.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/irc/config.go b/irc/config.go index c0a831d1..ba3dcc76 100644 --- a/irc/config.go +++ b/irc/config.go @@ -8,6 +8,7 @@ package irc import ( "bytes" "crypto/tls" + "crypto/x509" "errors" "fmt" "io" @@ -846,7 +847,7 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error } func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Config, err error) { - cert, err := tls.LoadX509KeyPair(config.Cert, config.Key) + cert, err := loadCertWithLeaf(config.Cert, config.Key) if err != nil { return nil, &CertKeyError{Err: err} } @@ -865,6 +866,20 @@ func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Confi return &result, nil } +func loadCertWithLeaf(certFile, keyFile string) (cert tls.Certificate, err error) { + // LoadX509KeyPair: "On successful return, Certificate.Leaf will be nil because + // the parsed form of the certificate is not retained." tls.Config: + // "Note: if there are multiple Certificates, and they don't have the + // optional field Leaf set, certificate selection will incur a significant + // per-handshake performance cost." + cert, err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return + } + cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) + return +} + // prepareListeners populates Config.Server.trueListeners func (conf *Config) prepareListeners() (err error) { if len(conf.Server.Listeners) == 0 { From aecb28a616e1e7ca0606ff88afd3c8181348e0f4 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 7 Apr 2021 22:49:33 -0400 Subject: [PATCH 3/8] support SNI --- irc/config.go | 62 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/irc/config.go b/irc/config.go index ba3dcc76..35313f02 100644 --- a/irc/config.go +++ b/irc/config.go @@ -55,12 +55,15 @@ type TLSListenConfig struct { // This is the YAML-deserializable type of the value of the `Server.Listeners` map type listenerConfigBlock struct { - TLS TLSListenConfig - Proxy bool - Tor bool - STSOnly bool `yaml:"sts-only"` - WebSocket bool - HideSTS bool `yaml:"hide-sts"` + // normal TLS configuration, with a single certificate: + TLS TLSListenConfig + // SNI configuration, with multiple certificates: + TLSCertificates []TLSListenConfig `yaml:"tls-certificates"` + Proxy bool + Tor bool + STSOnly bool `yaml:"sts-only"` + WebSocket bool + HideSTS bool `yaml:"hide-sts"` } type HistoryCutoff uint @@ -537,11 +540,10 @@ type Config struct { passwordBytes []byte Name string nameCasefolded string - // Listeners is the new style for configuring listeners: - Listeners map[string]listenerConfigBlock - UnixBindMode os.FileMode `yaml:"unix-bind-mode"` - TorListeners TorListenersConfig `yaml:"tor-listeners"` - WebSockets struct { + Listeners map[string]listenerConfigBlock + UnixBindMode os.FileMode `yaml:"unix-bind-mode"` + TorListeners TorListenersConfig `yaml:"tor-listeners"` + WebSockets struct { AllowedOrigins []string `yaml:"allowed-origins"` allowedOriginRegexps []*regexp.Regexp } @@ -846,13 +848,30 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error return operators, nil } -func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Config, err error) { - cert, err := loadCertWithLeaf(config.Cert, config.Key) - if err != nil { - return nil, &CertKeyError{Err: err} +func loadTlsConfig(config listenerConfigBlock) (tlsConfig *tls.Config, err error) { + var certificates []tls.Certificate + if len(config.TLSCertificates) != 0 { + // SNI configuration with multiple certificates + for _, certPairConf := range config.TLSCertificates { + cert, err := loadCertWithLeaf(certPairConf.Cert, certPairConf.Key) + if err != nil { + return nil, err + } + certificates = append(certificates, cert) + } + } else if config.TLS.Cert != "" { + // normal configuration with one certificate + cert, err := loadCertWithLeaf(config.TLS.Cert, config.TLS.Key) + if err != nil { + return nil, err + } + certificates = append(certificates, cert) + } else { + // plaintext! + return nil, nil } clientAuth := tls.RequestClientCert - if webSocket { + if config.WebSocket { // if Chrome receives a server request for a client certificate // on a websocket connection, it will immediately disconnect: // https://bugs.chromium.org/p/chromium/issues/detail?id=329884 @@ -860,7 +879,7 @@ func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Confi clientAuth = tls.NoClientCert } result := tls.Config{ - Certificates: []tls.Certificate{cert}, + Certificates: certificates, ClientAuth: clientAuth, } return &result, nil @@ -895,12 +914,9 @@ func (conf *Config) prepareListeners() (err error) { if lconf.STSOnly && !conf.Server.STS.Enabled { return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr) } - if block.TLS.Cert != "" { - tlsConfig, err := loadTlsConfig(block.TLS, block.WebSocket) - if err != nil { - return err - } - lconf.TLSConfig = tlsConfig + lconf.TLSConfig, err = loadTlsConfig(block) + if err != nil { + return &CertKeyError{Err: err} } lconf.RequireProxy = block.TLS.Proxy || block.Proxy lconf.WebSocket = block.WebSocket From 1fc513cef006b33923a47bb6c2594e291fa4823c Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 7 Apr 2021 23:13:20 -0400 Subject: [PATCH 4/8] document SNI --- default.yaml | 2 ++ docs/MANUAL.md | 15 +++++++++++++++ traditional.yaml | 2 ++ 3 files changed, 19 insertions(+) diff --git a/default.yaml b/default.yaml index bb785fb8..87a91a57 100644 --- a/default.yaml +++ b/default.yaml @@ -49,6 +49,8 @@ server: # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces: ":6697": + # this is a standard TLS configuration with a single certificate; + # see the manual for instructions on how to configure SNI tls: cert: fullchain.pem key: privkey.pem diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 3a40d1cf..94bda532 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -49,6 +49,7 @@ _Copyright © Daniel Oaks , Shivaram Lingamneni Date: Wed, 7 Apr 2021 23:23:09 -0400 Subject: [PATCH 5/8] fix #765 CS INFO with no arguments should list your registered channels --- irc/chanserv.go | 7 ++++++- irc/nickserv.go | 13 ++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/irc/chanserv.go b/irc/chanserv.go index 13d9e9bf..b885a707 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -144,7 +144,6 @@ If no regex is provided, all registered channels are returned.`, INFO displays info about a registered channel.`, helpShort: `$bINFO$b displays info about a registered channel.`, enabled: chanregEnabled, - minParams: 1, }, "get": { handler: csGetHandler, @@ -743,6 +742,12 @@ func csListHandler(service *ircService, server *Server, client *Client, command } func csInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + if len(params) == 0 { + // #765 + listRegisteredChannels(service, client.Account(), rb) + return + } + chname, err := CasefoldChannel(params[0]) if err != nil { service.Notice(rb, client.t("Invalid channel name")) diff --git a/irc/nickserv.go b/irc/nickserv.go index 5bf45c4b..3d5c10de 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -817,14 +817,21 @@ func nsInfoHandler(service *ircService, server *Server, client *Client, command for _, nick := range account.AdditionalNicks { service.Notice(rb, fmt.Sprintf(client.t("Additional grouped nick: %s"), nick)) } - for _, channel := range server.accounts.ChannelsForAccount(accountName) { - service.Notice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel)) - } + listRegisteredChannels(service, accountName, rb) if account.Suspended != nil { service.Notice(rb, suspensionToString(client, *account.Suspended)) } } +func listRegisteredChannels(service *ircService, accountName string, rb *ResponseBuffer) { + client := rb.session.client + channels := client.server.accounts.ChannelsForAccount(accountName) + service.Notice(rb, fmt.Sprintf(client.t("You have %d registered channel(s)."), len(channels))) + for _, channel := range rb.session.client.server.accounts.ChannelsForAccount(accountName) { + service.Notice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel)) + } +} + func nsRegisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { details := client.Details() passphrase := params[0] From 745fd764dd955c0beb2bf741cae8ad0f798d5787 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 8 Apr 2021 00:55:30 -0400 Subject: [PATCH 6/8] fix #1524 Document permissions structure of CS AMODE --- irc/chanserv.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/irc/chanserv.go b/irc/chanserv.go index b885a707..62e6483c 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -79,7 +79,9 @@ AMODE lists or modifies persistent mode settings that affect channel members. For example, $bAMODE #channel +o dan$b grants the holder of the "dan" account the +o operator mode every time they join #channel. To list current accounts and modes, use $bAMODE #channel$b. Note that users are always -referenced by their registered account names, not their nicknames.`, +referenced by their registered account names, not their nicknames. +The permissions hierarchy for adding and removing modes is the same as in +the ordinary /MODE command.`, helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`, enabled: chanregEnabled, minParams: 1, From 681718622448c35e33ea60c50537498fa1e841a1 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 8 Apr 2021 05:10:17 -0400 Subject: [PATCH 7/8] fix #1518 UBAN ADD and DEL need to produce snomasks and loglines --- irc/uban.go | 71 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/irc/uban.go b/irc/uban.go index d7651baf..b28fa6f4 100644 --- a/irc/uban.go +++ b/irc/uban.go @@ -13,6 +13,7 @@ import ( "github.com/oragono/oragono/irc/custime" "github.com/oragono/oragono/irc/flatip" + "github.com/oragono/oragono/irc/sno" "github.com/oragono/oragono/irc/utils" ) @@ -160,18 +161,64 @@ func ubanAddHandler(client *Client, target ubanTarget, params []string, rb *Resp switch target.banType { case ubanCIDR: - ubanAddCIDR(client, target, duration, requireSASL, operReason, rb) + err = ubanAddCIDR(client, target, duration, requireSASL, operReason, rb) case ubanNickmask: - ubanAddNickmask(client, target, duration, operReason, rb) + err = ubanAddNickmask(client, target, duration, operReason, rb) case ubanNick: - ubanAddAccount(client, target, duration, operReason, rb) - + err = ubanAddAccount(client, target, duration, operReason, rb) + } + if err == nil { + announceUban(client, true, target, duration, requireSASL, operReason) } return false } -func ubanAddCIDR(client *Client, target ubanTarget, duration time.Duration, requireSASL bool, operReason string, rb *ResponseBuffer) { - err := client.server.dlines.AddNetwork(target.cidr, duration, requireSASL, "", operReason, client.Oper().Name) +func announceUban(client *Client, add bool, target ubanTarget, duration time.Duration, requireSASL bool, operReason string) { + oper := client.Oper() + if oper == nil { + return + } + operName := oper.Name + + var buf strings.Builder + fmt.Fprintf(&buf, "Operator %s", operName) + + if add { + buf.WriteString(" added") + } else { + buf.WriteString(" removed") + } + switch target.banType { + case ubanCIDR: + buf.WriteString(" an IP-based") + case ubanNickmask: + buf.WriteString(" a NUH-mask") + case ubanNick: + buf.WriteString(" an account suspension") + } + buf.WriteString(" UBAN against ") + switch target.banType { + case ubanCIDR: + buf.WriteString(target.cidr.String()) + case ubanNickmask, ubanNick: + buf.WriteString(target.nickOrMask) + } + if duration != 0 { + fmt.Fprintf(&buf, " [duration: %v]", duration) + } + if requireSASL { + buf.WriteString(" [require-SASL]") + } + if operReason != "" { + fmt.Fprintf(&buf, " [reason: %s]", operReason) + } + line := buf.String() + client.server.snomasks.Send(sno.LocalXline, line) + client.server.logger.Info("opers", line) +} + +func ubanAddCIDR(client *Client, target ubanTarget, duration time.Duration, requireSASL bool, operReason string, rb *ResponseBuffer) (err error) { + err = client.server.dlines.AddNetwork(target.cidr, duration, requireSASL, "", operReason, client.Oper().Name) if err == nil { rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.cidr.HumanReadableString())) } else { @@ -192,10 +239,11 @@ func ubanAddCIDR(client *Client, target ubanTarget, duration time.Duration, requ rb.Notice(line) } } + return } -func ubanAddNickmask(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) { - err := client.server.klines.AddMask(target.nickOrMask, duration, "", operReason, client.Oper().Name) +func ubanAddNickmask(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) (err error) { + err = client.server.klines.AddMask(target.nickOrMask, duration, "", operReason, client.Oper().Name) if err == nil { rb.Notice(fmt.Sprintf(client.t("Successfully added UBAN for %s"), target.nickOrMask)) } else { @@ -229,9 +277,10 @@ func ubanAddNickmask(client *Client, target ubanTarget, duration time.Duration, } rb.Notice(client.t("You can suspend their accounts instead; try /UBAN ADD ")) } + return } -func ubanAddAccount(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) { +func ubanAddAccount(client *Client, target ubanTarget, duration time.Duration, operReason string, rb *ResponseBuffer) (err error) { account := target.nickOrMask // TODO this doesn't enumerate all sessions if ForceNickEqualsAccount is disabled var sessionData []SessionData @@ -239,7 +288,7 @@ func ubanAddAccount(client *Client, target ubanTarget, duration time.Duration, o sessionData, _ = mcl.AllSessionData(nil, true) } - err := client.server.accounts.Suspend(account, duration, client.Oper().Name, operReason) + err = client.server.accounts.Suspend(account, duration, client.Oper().Name, operReason) switch err { case nil: rb.Notice(fmt.Sprintf(client.t("Successfully suspended account %s"), account)) @@ -254,6 +303,7 @@ func ubanAddAccount(client *Client, target ubanTarget, duration time.Duration, o default: rb.Notice(client.t("An error occurred")) } + return } func ubanDelHandler(client *Client, target ubanTarget, params []string, rb *ResponseBuffer) bool { @@ -276,6 +326,7 @@ func ubanDelHandler(client *Client, target ubanTarget, params []string, rb *Resp } if err == nil { rb.Notice(fmt.Sprintf(client.t("Successfully removed ban on %s"), targetString)) + announceUban(client, false, target, 0, false, "") } else { rb.Notice(fmt.Sprintf(client.t("Could not remove ban: %v"), err)) } From 5cd76f89d458db48f50b047310b04537d12c27b1 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 8 Apr 2021 05:16:23 -0400 Subject: [PATCH 8/8] fix #1545 Warn users that NS UNREGISTER doesn't give them a "do-over"; the account name will remain reserved. --- irc/nickserv.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/irc/nickserv.go b/irc/nickserv.go index 3d5c10de..86ecb65d 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -968,6 +968,8 @@ func nsUnregisterHandler(service *ircService, server *Server, client *Client, co service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b"))) } else { service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b"))) + service.Notice(rb, ircfmt.Unescape(client.t("$bNote that an unregistered account name remains reserved and cannot be re-registered.$b"))) + service.Notice(rb, ircfmt.Unescape(client.t("$bIf you are having problems with your account, contact an administrator.$b"))) } service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/NS %s %s %s", strings.ToUpper(command), accountName, expectedCode))) return