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:
commit
639817f014
@ -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
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
71
irc/uban.go
71
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 <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))
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user