3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-25 13:29:27 +01:00
This commit is contained in:
Shivaram Lingamneni 2019-08-27 00:51:09 -04:00
parent 014a5fe63c
commit 7ed27d4a42
9 changed files with 206 additions and 153 deletions

View File

@ -32,6 +32,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
- History - History
- IP cloaking - IP cloaking
- Frequently Asked Questions - Frequently Asked Questions
- IRC over TLS
- Modes - Modes
- User Modes - User Modes
- Channel Modes - Channel Modes
@ -342,9 +343,17 @@ If you're familiar with getting this output through your client (e.g. in weechat
Otherwise, in the Oragono config file, you'll want to enable raw line logging by removing `-userinput -useroutput` under the `logging` section. Once you start up your server, connect, fail to oper and get disconnected, you'll see a bunch of input/output lines in Ora's log file. Remove your password from those logs and pass them our way. Otherwise, in the Oragono config file, you'll want to enable raw line logging by removing `-userinput -useroutput` under the `logging` section. Once you start up your server, connect, fail to oper and get disconnected, you'll see a bunch of input/output lines in Ora's log file. Remove your password from those logs and pass them our way.
-------------------------------------------------------------------------------------------
# IRC over TLS
Traditionally, IRC used a plaintext protocol, typically on port 6667. Over time, a convention emerged to use this protocol inside SSL/TLS instead, typically on port 6697. As of now, we recommend that you make your server available *exclusively* via TLS, since allowing plaintext access can result in the disclosure of user data or passwords. While the default config file still exposes a public plaintext port for the benefit of legacy clients, it also contains instructions on how to disable it --- if at all possible, you should follow them!
## How do I use Let's Encrypt certificates? ## How do I use Let's Encrypt certificates?
Every deployment's gonna be different, but you can use certificates from [Let's Encrypt](https://letsencrypt.org) without too much trouble. Here's some steps that should help get you on the right track: [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. 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>/`). 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>/`).
@ -364,6 +373,34 @@ The main issues you'll run into are going to be permissions issues. This is beca
On other platforms or with alternative ACME tools, you may need to use other steps or the specific files may be named differently. On other platforms or with alternative ACME tools, you may need to use other steps or the specific files may be named differently.
## How can I "redirect" users from plaintext to TLS?
The [STS specification](https://ircv3.net/specs/extensions/sts) can be used to redirect clients from plaintext to TLS automatically. If you set `server.sts.enabled` to `true`, clients with specific support for STS that connect in plaintext will disconnect and reconnect over TLS. To use STS, you must be using certificates issued by a generally recognized certificate authority, such as Let's Encrypt.
Many clients do not have this support. However, you can designate port 6667 as an "STS-only" listener: any client that connects to such a listener will receive both the machine-readable STS policy and a human-readable message instructing them to reconnect over TLS, and will then be disconnected by the server before they can send or receive any chat data. Here is an example of how to configure this behavior:
```yaml
listeners:
":6667":
sts-only: true
# These are loopback-only plaintext listeners on port 6668:
"127.0.0.1:6668": # (loopback ipv4, localhost-only)
"[::1]:6668": # (loopback ipv6, localhost-only)
":6697":
tls:
key: tls.key
cert: tls.crt
sts:
enabled: true
# how long clients should be forced to use TLS for.
duration: 1mo2d5m
```
-------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------

View File

@ -4,8 +4,8 @@
package caps package caps
import ( import (
"bytes"
"sort" "sort"
"strings"
"github.com/oragono/oragono/irc/utils" "github.com/oragono/oragono/irc/utils"
) )
@ -13,6 +13,9 @@ import (
// Set holds a set of enabled capabilities. // Set holds a set of enabled capabilities.
type Set [bitsetLen]uint32 type Set [bitsetLen]uint32
// Values holds capability values.
type Values map[Capability]string
// NewSet returns a new Set, with the given capabilities enabled. // NewSet returns a new Set, with the given capabilities enabled.
func NewSet(capabs ...Capability) *Set { func NewSet(capabs ...Capability) *Set {
var newSet Set var newSet Set
@ -88,8 +91,10 @@ func (s *Set) Empty() bool {
return utils.BitsetEmpty(s[:]) return utils.BitsetEmpty(s[:])
} }
const maxPayloadLength = 440
// String returns all of our enabled capabilities as a string. // String returns all of our enabled capabilities as a string.
func (s *Set) String(version Version, values *Values) string { func (s *Set) String(version Version, values Values) (result []string) {
var strs sort.StringSlice var strs sort.StringSlice
var capab Capability var capab Capability
@ -101,7 +106,7 @@ func (s *Set) String(version Version, values *Values) string {
} }
capString := capab.Name() capString := capab.Name()
if version == Cap302 { if version == Cap302 {
val, exists := values.Get(capab) val, exists := values[capab]
if exists { if exists {
capString += "=" + val capString += "=" + val
} }
@ -109,8 +114,31 @@ func (s *Set) String(version Version, values *Values) string {
strs = append(strs, capString) strs = append(strs, capString)
} }
if len(strs) == 0 {
return []string{""}
}
// sort the cap string before we send it out // sort the cap string before we send it out
sort.Sort(strs) sort.Sort(strs)
return strings.Join(strs, " ") var buf bytes.Buffer
for _, str := range strs {
tokenLen := len(str)
if buf.Len() != 0 {
tokenLen += 1
}
if maxPayloadLength < buf.Len()+tokenLen {
result = append(result, buf.String())
buf.Reset()
}
if buf.Len() != 0 {
buf.WriteByte(' ')
}
buf.WriteString(str)
}
if buf.Len() != 0 {
result = append(result, buf.String())
}
return
} }

View File

@ -44,18 +44,18 @@ func TestSets(t *testing.T) {
} }
// test String() // test String()
values := NewValues() values := make(Values)
values.Set(InviteNotify, "invitemepls") values[InviteNotify] = "invitemepls"
actualCap301ValuesString := s1.String(Cap301, values) actualCap301ValuesString := s1.String(Cap301, values)
expectedCap301ValuesString := "invite-notify userhost-in-names" expectedCap301ValuesString := []string{"invite-notify userhost-in-names"}
if actualCap301ValuesString != expectedCap301ValuesString { if !reflect.DeepEqual(actualCap301ValuesString, expectedCap301ValuesString) {
t.Errorf("Generated Cap301 values string [%s] did not match expected values string [%s]", actualCap301ValuesString, expectedCap301ValuesString) t.Errorf("Generated Cap301 values string [%v] did not match expected values string [%v]", actualCap301ValuesString, expectedCap301ValuesString)
} }
actualCap302ValuesString := s1.String(Cap302, values) actualCap302ValuesString := s1.String(Cap302, values)
expectedCap302ValuesString := "invite-notify=invitemepls userhost-in-names" expectedCap302ValuesString := []string{"invite-notify=invitemepls userhost-in-names"}
if actualCap302ValuesString != expectedCap302ValuesString { if !reflect.DeepEqual(actualCap302ValuesString, expectedCap302ValuesString) {
t.Errorf("Generated Cap302 values string [%s] did not match expected values string [%s]", actualCap302ValuesString, expectedCap302ValuesString) t.Errorf("Generated Cap302 values string [%s] did not match expected values string [%s]", actualCap302ValuesString, expectedCap302ValuesString)
} }
} }

View File

@ -1,45 +0,0 @@
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package caps
import "sync"
// Values holds capability values.
type Values struct {
sync.RWMutex
// values holds our actual capability values.
values map[Capability]string
}
// NewValues returns a new Values.
func NewValues() *Values {
return &Values{
values: make(map[Capability]string),
}
}
// Set sets the value for the given capability.
func (v *Values) Set(capab Capability, value string) {
v.Lock()
defer v.Unlock()
v.values[capab] = value
}
// Unset removes the value for the given capability, if it exists.
func (v *Values) Unset(capab Capability) {
v.Lock()
defer v.Unlock()
delete(v.values, capab)
}
// Get returns the value of the given capability, and whether one exists.
func (v *Values) Get(capab Capability) (string, bool) {
v.RLock()
defer v.RUnlock()
value, exists := v.values[capab]
return value, exists
}

View File

@ -58,6 +58,7 @@ type Client struct {
flags modes.ModeSet flags modes.ModeSet
hostname string hostname string
invitedTo map[string]bool invitedTo map[string]bool
isSTSOnly bool
isTor bool isTor bool
languages []string languages []string
loginThrottle connection_limits.GenericThrottle loginThrottle connection_limits.GenericThrottle
@ -220,6 +221,7 @@ func (server *Server) RunClient(conn clientConn) {
atime: now, atime: now,
channels: make(ChannelSet), channels: make(ChannelSet),
ctime: now, ctime: now,
isSTSOnly: conn.Config.IsSTSOnly,
isTor: conn.Config.IsTor, isTor: conn.Config.IsTor,
languages: server.Languages().Default(), languages: server.Languages().Default(),
loginThrottle: connection_limits.GenericThrottle{ loginThrottle: connection_limits.GenericThrottle{

View File

@ -14,10 +14,12 @@ import (
"os" "os"
"regexp" "regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
"code.cloudfoundry.org/bytefmt" "code.cloudfoundry.org/bytefmt"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/cloaks" "github.com/oragono/oragono/irc/cloaks"
"github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/connection_limits"
"github.com/oragono/oragono/irc/custime" "github.com/oragono/oragono/irc/custime"
@ -45,6 +47,7 @@ type TLSListenConfig struct {
type listenerConfigBlock struct { type listenerConfigBlock struct {
TLS TLSListenConfig TLS TLSListenConfig
Tor bool Tor bool
STSOnly bool `yaml:"sts-only"`
} }
// listenerConfig is the config governing a particular listener (bound address), // listenerConfig is the config governing a particular listener (bound address),
@ -52,6 +55,7 @@ type listenerConfigBlock struct {
type listenerConfig struct { type listenerConfig struct {
TLSConfig *tls.Config TLSConfig *tls.Config
IsTor bool IsTor bool
IsSTSOnly bool
} }
type AccountConfig struct { type AccountConfig struct {
@ -235,6 +239,8 @@ type STSConfig struct {
DurationString string `yaml:"duration"` DurationString string `yaml:"duration"`
Port int Port int
Preload bool Preload bool
STSOnlyBanner string `yaml:"sts-only-banner"`
bannerLines []string
} }
// Value returns the STS value to advertise in CAP // Value returns the STS value to advertise in CAP
@ -306,6 +312,8 @@ type Config struct {
ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"`
ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"`
supportedCaps *caps.Set
capValues caps.Values
} }
Languages struct { Languages struct {
@ -511,6 +519,10 @@ func (conf *Config) prepareListeners() (err error) {
for addr, block := range conf.Server.Listeners { for addr, block := range conf.Server.Listeners {
var lconf listenerConfig var lconf listenerConfig
lconf.IsTor = block.Tor lconf.IsTor = block.Tor
lconf.IsSTSOnly = block.STSOnly
if lconf.IsSTSOnly && !conf.Server.STS.Enabled {
return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr)
}
if block.TLS.Cert != "" { if block.TLS.Cert != "" {
tlsConfig, err := loadTlsConfig(block.TLS) tlsConfig, err := loadTlsConfig(block.TLS)
if err != nil { if err != nil {
@ -592,6 +604,15 @@ func LoadConfig(filename string) (config *Config, err error) {
if config.Limits.RegistrationMessages == 0 { if config.Limits.RegistrationMessages == 0 {
config.Limits.RegistrationMessages = 1024 config.Limits.RegistrationMessages = 1024
} }
config.Server.supportedCaps = caps.NewCompleteSet()
config.Server.capValues = make(caps.Values)
err = config.prepareListeners()
if err != nil {
return nil, fmt.Errorf("failed to prepare listeners: %v", err)
}
if config.Server.STS.Enabled { if config.Server.STS.Enabled {
config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString) config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString)
if err != nil { if err != nil {
@ -600,7 +621,18 @@ func LoadConfig(filename string) (config *Config, err error) {
if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 { if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port) return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
} }
if config.Server.STS.STSOnlyBanner != "" {
config.Server.STS.bannerLines = utils.WordWrap(config.Server.STS.STSOnlyBanner, 400)
} else {
config.Server.STS.bannerLines = []string{fmt.Sprintf("This server is only accessible over TLS. Please reconnect using TLS on port %d.", config.Server.STS.Port)}
} }
} else {
config.Server.supportedCaps.Disable(caps.STS)
config.Server.STS.Duration = 0
}
// set this even if STS is disabled
config.Server.capValues[caps.STS] = config.Server.STS.Value()
if config.Server.ConnectionThrottler.Enabled { if config.Server.ConnectionThrottler.Enabled {
config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString) config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString)
if err != nil { if err != nil {
@ -626,10 +658,17 @@ func LoadConfig(filename string) (config *Config, err error) {
newWebIRC = append(newWebIRC, webirc) newWebIRC = append(newWebIRC, webirc)
} }
config.Server.WebIRC = newWebIRC config.Server.WebIRC = newWebIRC
// process limits // process limits
if config.Limits.LineLen.Rest < 512 { if config.Limits.LineLen.Rest < 512 {
config.Limits.LineLen.Rest = 512 config.Limits.LineLen.Rest = 512
} }
if config.Limits.LineLen.Rest == 512 {
config.Server.supportedCaps.Disable(caps.MaxLine)
} else {
config.Server.capValues[caps.MaxLine] = strconv.Itoa(config.Limits.LineLen.Rest)
}
var newLogConfigs []logger.LoggingConfig var newLogConfigs []logger.LoggingConfig
for _, logConfig := range config.Logging { for _, logConfig := range config.Logging {
// methods // methods
@ -713,6 +752,11 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
} }
config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
if !config.Accounts.AuthenticationEnabled {
config.Server.supportedCaps.Disable(caps.SASL)
}
maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString) maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error()) return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
@ -723,6 +767,7 @@ func LoadConfig(filename string) (config *Config, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not load languages: %s", err.Error()) return nil, fmt.Errorf("Could not load languages: %s", err.Error())
} }
config.Server.capValues[caps.Languages] = config.languageManager.CapValue()
// RecoverFromErrors defaults to true // RecoverFromErrors defaults to true
if config.Debug.RecoverFromErrors != nil { if config.Debug.RecoverFromErrors != nil {
@ -798,10 +843,5 @@ func LoadConfig(filename string) (config *Config, err error) {
} }
} }
err = config.prepareListeners()
if err != nil {
return nil, fmt.Errorf("failed to prepare listeners: %v", err)
}
return config, nil return config, nil
} }

View File

@ -301,6 +301,11 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
config := server.Config() config := server.Config()
details := client.Details() details := client.Details()
if client.isSTSOnly {
rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed"))
return false
}
if details.account != "" { if details.account != "" {
rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account")) rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
return false return false
@ -535,6 +540,12 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
toRemove := caps.NewSet() toRemove := caps.NewSet()
var capString string var capString string
config := server.Config()
supportedCaps := config.Server.supportedCaps
if client.isSTSOnly {
supportedCaps = stsOnlyCaps
}
badCaps := false badCaps := false
if len(msg.Params) > 1 { if len(msg.Params) > 1 {
capString = msg.Params[1] capString = msg.Params[1]
@ -546,7 +557,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
remove = true remove = true
} }
capab, err := caps.NameToCapability(str) capab, err := caps.NameToCapability(str)
if err != nil || (!remove && !SupportedCapabilities.Has(capab)) { if err != nil || (!remove && !supportedCaps.Has(capab)) {
badCaps = true badCaps = true
} else if !remove { } else if !remove {
toAdd.Enable(capab) toAdd.Enable(capab)
@ -556,6 +567,20 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
} }
} }
sendCapLines := func(cset *caps.Set, values caps.Values) {
version := rb.session.capVersion
capLines := cset.String(version, values)
// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
// the server.name source:
for i, capStr := range capLines {
if version == caps.Cap302 && i < len(capLines)-1 {
rb.Add(nil, server.name, "CAP", details.nick, subCommand, "*", capStr)
} else {
rb.Add(nil, server.name, "CAP", details.nick, subCommand, capStr)
}
}
}
switch subCommand { switch subCommand {
case "LS": case "LS":
if !client.registered { if !client.registered {
@ -568,14 +593,11 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
rb.session.capVersion = newVersion rb.session.capVersion = newVersion
} }
} }
// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains sendCapLines(supportedCaps, config.Server.capValues)
// the server.name source... otherwise it doesn't respond to the CAP message with
// anything and just hangs on connection.
//TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
rb.Add(nil, server.name, "CAP", details.nick, subCommand, SupportedCapabilities.String(rb.session.capVersion, CapValues))
case "LIST": case "LIST":
rb.Add(nil, server.name, "CAP", details.nick, subCommand, rb.session.capabilities.String(caps.Cap301, CapValues)) // values not sent on LIST so force 3.1 // values not sent on LIST
sendCapLines(&rb.session.capabilities, nil)
case "REQ": case "REQ":
if !client.registered { if !client.registered {

View File

@ -39,13 +39,9 @@ var (
// supportedChannelModesString acts as a cache for when we introduce users // supportedChannelModesString acts as a cache for when we introduce users
supportedChannelModesString = modes.SupportedChannelModes.String() supportedChannelModesString = modes.SupportedChannelModes.String()
// SupportedCapabilities are the caps we advertise. // whitelist of caps to serve on the STS-only listener. In particular,
// MaxLine, SASL and STS may be unset during server startup / rehash. // never advertise SASL, to discourage people from sending their passwords:
SupportedCapabilities = caps.NewCompleteSet() stsOnlyCaps = caps.NewSet(caps.STS, caps.MessageTags, caps.ServerTime, caps.LabeledResponse, caps.Nope)
// CapValues are the actual values we advertise to v3.2 clients.
// actual values are set during server startup.
CapValues = caps.NewValues()
) )
// ListenerWrapper wraps a listener so it can be safely reconfigured or stopped // ListenerWrapper wraps a listener so it can be safely reconfigured or stopped
@ -340,6 +336,11 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
return return
} }
if c.isSTSOnly {
server.playRegistrationBurst(session)
return true
}
// client MUST send PASS if necessary, or authenticate with SASL if necessary, // client MUST send PASS if necessary, or authenticate with SASL if necessary,
// before completing the other registration commands // before completing the other registration commands
authOutcome := c.isAuthorized(server.Config()) authOutcome := c.isAuthorized(server.Config())
@ -407,6 +408,13 @@ func (server *Server) playRegistrationBurst(session *Session) {
//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter //TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString) session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
if c.isSTSOnly {
for _, line := range server.Config().Server.STS.bannerLines {
c.Notice(line)
}
return
}
rb := NewResponseBuffer(session) rb := NewResponseBuffer(session)
server.RplISupport(c, rb) server.RplISupport(c, rb)
server.Lusers(c, rb) server.Lusers(c, rb)
@ -623,23 +631,17 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
server.logger.Debug("server", "Regenerating HELP indexes for new languages") server.logger.Debug("server", "Regenerating HELP indexes for new languages")
server.helpIndexManager.GenerateIndices(config.languageManager) server.helpIndexManager.GenerateIndices(config.languageManager)
currentLanguageValue, _ := CapValues.Get(caps.Languages) if oldConfig != nil && config.Server.capValues[caps.Languages] != oldConfig.Server.capValues[caps.Languages] {
newLanguageValue := config.languageManager.CapValue()
if currentLanguageValue != newLanguageValue {
updatedCaps.Add(caps.Languages) updatedCaps.Add(caps.Languages)
CapValues.Set(caps.Languages, newLanguageValue)
} }
// SASL // SASL
authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled
if config.Accounts.AuthenticationEnabled && (oldConfig == nil || !authPreviouslyEnabled) { if config.Accounts.AuthenticationEnabled && (oldConfig == nil || !authPreviouslyEnabled) {
// enabling SASL // enabling SASL
SupportedCapabilities.Enable(caps.SASL)
CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
addedCaps.Add(caps.SASL) addedCaps.Add(caps.SASL)
} else if !config.Accounts.AuthenticationEnabled && (oldConfig == nil || authPreviouslyEnabled) { } else if !config.Accounts.AuthenticationEnabled && (oldConfig == nil || authPreviouslyEnabled) {
// disabling SASL // disabling SASL
SupportedCapabilities.Disable(caps.SASL)
removedCaps.Add(caps.SASL) removedCaps.Add(caps.SASL)
} }
@ -661,39 +663,17 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
server.channels.loadRegisteredChannels() server.channels.loadRegisteredChannels()
} }
// MaxLine
if config.Limits.LineLen.Rest != 512 {
SupportedCapabilities.Enable(caps.MaxLine)
value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
CapValues.Set(caps.MaxLine, value)
} else {
SupportedCapabilities.Disable(caps.MaxLine)
}
// STS // STS
stsPreviouslyEnabled := oldConfig != nil && oldConfig.Server.STS.Enabled stsPreviouslyEnabled := oldConfig != nil && oldConfig.Server.STS.Enabled
stsValue := config.Server.STS.Value() stsValue := config.Server.capValues[caps.STS]
stsDisabledByRehash := false stsCurrentCapValue := ""
stsCurrentCapValue, _ := CapValues.Get(caps.STS) if oldConfig != nil {
stsCurrentCapValue = oldConfig.Server.capValues[caps.STS]
}
server.logger.Debug("server", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled)) server.logger.Debug("server", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled))
if config.Server.STS.Enabled { if (config.Server.STS.Enabled != stsPreviouslyEnabled) || (stsValue != stsCurrentCapValue) {
// enabling STS // XXX: STS is always removed by CAP NEW sts=duration=0, not CAP DEL
SupportedCapabilities.Enable(caps.STS)
if !stsPreviouslyEnabled {
addedCaps.Add(caps.STS) addedCaps.Add(caps.STS)
CapValues.Set(caps.STS, stsValue)
} else if stsValue != stsCurrentCapValue {
// STS policy updated
CapValues.Set(caps.STS, stsValue)
updatedCaps.Add(caps.STS)
}
} else {
// disabling STS
SupportedCapabilities.Disable(caps.STS)
if stsPreviouslyEnabled {
removedCaps.Add(caps.STS)
stsDisabledByRehash = true
}
} }
// resize history buffers as needed // resize history buffers as needed
@ -708,42 +688,35 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
// burst new and removed caps // burst new and removed caps
var capBurstSessions []*Session var capBurstSessions []*Session
added := make(map[caps.Version]string) added := make(map[caps.Version][]string)
var removed string var removed []string
// updated caps get DEL'd and then NEW'd // updated caps get DEL'd and then NEW'd
// so, we can just add updated ones to both removed and added lists here and they'll be correctly handled // so, we can just add updated ones to both removed and added lists here and they'll be correctly handled
server.logger.Debug("server", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues)) server.logger.Debug("server", "Updated Caps", strings.Join(updatedCaps.String(caps.Cap301, config.Server.capValues), " "))
addedCaps.Union(updatedCaps) addedCaps.Union(updatedCaps)
removedCaps.Union(updatedCaps) removedCaps.Union(updatedCaps)
if !addedCaps.Empty() || !removedCaps.Empty() { if !addedCaps.Empty() || !removedCaps.Empty() {
capBurstSessions = server.clients.AllWithCapsNotify() capBurstSessions = server.clients.AllWithCapsNotify()
added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues) added[caps.Cap301] = addedCaps.String(caps.Cap301, config.Server.capValues)
added[caps.Cap302] = addedCaps.String(caps.Cap302, CapValues) added[caps.Cap302] = addedCaps.String(caps.Cap302, config.Server.capValues)
// removed never has values, so we leave it as Cap301 // removed never has values, so we leave it as Cap301
removed = removedCaps.String(caps.Cap301, CapValues) removed = removedCaps.String(caps.Cap301, config.Server.capValues)
} }
for _, sSession := range capBurstSessions { for _, sSession := range capBurstSessions {
if stsDisabledByRehash {
// remove STS policy
//TODO(dan): this is an ugly hack. we can write this better.
stsPolicy := "sts=duration=0"
if !addedCaps.Empty() {
added[caps.Cap302] = added[caps.Cap302] + " " + stsPolicy
} else {
addedCaps.Enable(caps.STS)
added[caps.Cap302] = stsPolicy
}
}
// DEL caps and then send NEW ones so that updated caps get removed/added correctly // DEL caps and then send NEW ones so that updated caps get removed/added correctly
if !removedCaps.Empty() { if !removedCaps.Empty() {
sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "DEL", removed) for _, capStr := range removed {
sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "DEL", capStr)
}
} }
if !addedCaps.Empty() { if !addedCaps.Empty() {
sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "NEW", added[sSession.capVersion]) for _, capStr := range added[sSession.capVersion] {
sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "NEW", capStr)
}
} }
} }
@ -905,15 +878,11 @@ func (server *Server) setupListeners(config *Config) (err error) {
} }
} }
publicPlaintextListener := ""
// create new listeners that were not previously configured // create new listeners that were not previously configured
numTlsListeners := 0
hasStandardTlsListener := false
for newAddr, newConfig := range config.Server.trueListeners { for newAddr, newConfig := range config.Server.trueListeners {
if newConfig.TLSConfig != nil { if strings.HasPrefix(newAddr, ":") && !newConfig.IsTor && !newConfig.IsSTSOnly && newConfig.TLSConfig == nil {
numTlsListeners += 1 publicPlaintextListener = newAddr
if strings.HasSuffix(newAddr, ":6697") {
hasStandardTlsListener = true
}
} }
_, exists := server.listeners[newAddr] _, exists := server.listeners[newAddr]
if !exists { if !exists {
@ -929,12 +898,8 @@ func (server *Server) setupListeners(config *Config) (err error) {
} }
} }
if numTlsListeners == 0 { if publicPlaintextListener != "" {
server.logger.Warning("server", "You are not exposing an SSL/TLS listening port. You should expose at least one port (typically 6697) to accept TLS connections") server.logger.Warning("listeners", fmt.Sprintf("Your server is configured with public plaintext listener %s. Consider disabling it for improved security and privacy.", publicPlaintextListener))
}
if !hasStandardTlsListener {
server.logger.Warning("server", "Port 6697 is the standard TLS port for IRC. You should (also) expose port 6697 as a TLS port to ensure clients can connect securely")
} }
return return

View File

@ -15,18 +15,22 @@ server:
# The standard plaintext port for IRC is 6667. This will listen on all interfaces: # The standard plaintext port for IRC is 6667. This will listen on all interfaces:
":6667": ":6667":
# Allowing plaintext over the public Internet poses security and privacy issues,
# so if possible, we recommend that you comment out the above line and replace
# it with these two, which listen only on local interfaces:
# "127.0.0.1:6667": # (loopback ipv4, localhost-only)
# "[::1]:6667": # (loopback ipv6, localhost-only)
# Alternately, if you have a TLS certificate issued by a recognized CA,
# you can configure port 6667 as an STS-only listener that only serves
# "redirects" to the TLS port, but doesn't allow chat. See the manual
# for details.
# The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces: # The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
":6697": ":6697":
tls: tls:
key: tls.key key: tls.key
cert: tls.crt cert: tls.crt
# Since using plaintext over the public Internet poses security and privacy issues,
# you may wish to use plaintext only on local interfaces. To do so, comment out
# the `":6667":` line, then uncomment these two lines:
# "127.0.0.1:6667": # (loopback ipv4, localhost-only)
# "[::1]:6667": # (loopback ipv6, localhost-only)
# Example of a Unix domain socket for proxying: # Example of a Unix domain socket for proxying:
# "/tmp/oragono_sock": # "/tmp/oragono_sock":