3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-22 10:42:52 +01:00

Merge pull request #1608 from slingamn/sni.1

close remaining 2.6 issues
This commit is contained in:
Shivaram Lingamneni 2021-04-08 05:31:45 -04:00 committed by GitHub
commit 639817f014
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 39 deletions

View File

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

View File

@ -49,6 +49,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
- [Redirect from plaintext to TLS](#how-can-i-redirect-users-from-plaintext-to-tls)
- [Reverse proxies](#reverse-proxies)
- [Client certificates](#client-certificates)
- [SNI](#sni)
- [Modes](#modes)
- [User Modes](#user-modes)
- [Channel Modes](#channel-modes)
@ -606,6 +607,20 @@ Oragono supports authenticating to user accounts via TLS client certificates. Th
Client certificates are not supported over websockets due to a [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=329884).
## SNI
Oragono supports [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication); this is useful if you have multiple domain names for your server, with different certificates covering different domain names. Configure your TLS listener like this:
```yaml
":6697":
tls-certificates:
-
cert: cert1.pem
key: key1.pem
-
cert: cert2.pem
key: key2.pem
```
--------------------------------------------------------------------------------------------

View File

@ -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,
@ -144,7 +146,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 +744,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"))

View File

@ -8,6 +8,7 @@ package irc
import (
"bytes"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
@ -54,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
@ -536,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
}
@ -845,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 := tls.LoadX509KeyPair(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
@ -859,12 +879,26 @@ 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
}
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 {
@ -880,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

View File

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

View File

@ -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]
@ -961,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

View File

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

View File

@ -23,6 +23,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