Merge pull request #619 from slingamn/issue448.3

Fix #448, #594
This commit is contained in:
Shivaram Lingamneni 2019-09-08 03:36:24 -07:00 committed by GitHub
commit 542177213e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 231 additions and 167 deletions

@ -32,6 +32,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
- History
- IP cloaking
- Frequently Asked Questions
- IRC over TLS
- Modes
- User 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.
-------------------------------------------------------------------------------------------
# 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?
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.
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.
## 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
```
--------------------------------------------------------------------------------------------

@ -4,8 +4,8 @@
package caps
import (
"bytes"
"sort"
"strings"
"github.com/oragono/oragono/irc/utils"
)
@ -13,6 +13,9 @@ import (
// Set holds a set of enabled capabilities.
type Set [bitsetLen]uint32
// Values holds capability values.
type Values map[Capability]string
// NewSet returns a new Set, with the given capabilities enabled.
func NewSet(capabs ...Capability) *Set {
var newSet Set
@ -88,8 +91,10 @@ func (s *Set) Empty() bool {
return utils.BitsetEmpty(s[:])
}
// String returns all of our enabled capabilities as a string.
func (s *Set) String(version Version, values *Values) string {
const maxPayloadLength = 440
// Strings returns all of our enabled capabilities as a slice of strings.
func (s *Set) Strings(version Version, values Values) (result []string) {
var strs sort.StringSlice
var capab Capability
@ -100,8 +105,8 @@ func (s *Set) String(version Version, values *Values) string {
continue
}
capString := capab.Name()
if version == Cap302 {
val, exists := values.Get(capab)
if version >= Cap302 {
val, exists := values[capab]
if exists {
capString += "=" + val
}
@ -109,8 +114,31 @@ func (s *Set) String(version Version, values *Values) string {
strs = append(strs, capString)
}
if len(strs) == 0 {
return []string{""}
}
// sort the cap string before we send it out
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
}

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

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

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

@ -14,10 +14,12 @@ import (
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
"code.cloudfoundry.org/bytefmt"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/cloaks"
"github.com/oragono/oragono/irc/connection_limits"
"github.com/oragono/oragono/irc/custime"
@ -43,8 +45,9 @@ type TLSListenConfig struct {
// This is the YAML-deserializable type of the value of the `Server.Listeners` map
type listenerConfigBlock struct {
TLS TLSListenConfig
Tor bool
TLS TLSListenConfig
Tor bool
STSOnly bool `yaml:"sts-only"`
}
// listenerConfig is the config governing a particular listener (bound address),
@ -52,6 +55,7 @@ type listenerConfigBlock struct {
type listenerConfig struct {
TLSConfig *tls.Config
IsTor bool
IsSTSOnly bool
}
type AccountConfig struct {
@ -235,6 +239,8 @@ type STSConfig struct {
DurationString string `yaml:"duration"`
Port int
Preload bool
STSOnlyBanner string `yaml:"sts-only-banner"`
bannerLines []string
}
// Value returns the STS value to advertise in CAP
@ -306,6 +312,8 @@ type Config struct {
ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"`
ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"`
Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"`
supportedCaps *caps.Set
capValues caps.Values
}
Languages struct {
@ -511,6 +519,10 @@ func (conf *Config) prepareListeners() (err error) {
for addr, block := range conf.Server.Listeners {
var lconf listenerConfig
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 != "" {
tlsConfig, err := loadTlsConfig(block.TLS)
if err != nil {
@ -592,6 +604,15 @@ func LoadConfig(filename string) (config *Config, err error) {
if config.Limits.RegistrationMessages == 0 {
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 {
config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString)
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 {
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 {
config.Server.ConnectionThrottler.Duration, err = time.ParseDuration(config.Server.ConnectionThrottler.DurationString)
if err != nil {
@ -626,10 +658,21 @@ func LoadConfig(filename string) (config *Config, err error) {
newWebIRC = append(newWebIRC, webirc)
}
config.Server.WebIRC = newWebIRC
// process limits
if 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)
}
if !config.Accounts.Bouncer.Enabled {
config.Server.supportedCaps.Disable(caps.Bouncer)
}
var newLogConfigs []logger.LoggingConfig
for _, logConfig := range config.Logging {
// methods
@ -713,6 +756,11 @@ func LoadConfig(filename string) (config *Config, err error) {
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)
if err != nil {
return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
@ -723,6 +771,7 @@ func LoadConfig(filename string) (config *Config, err error) {
if err != nil {
return nil, fmt.Errorf("Could not load languages: %s", err.Error())
}
config.Server.capValues[caps.Languages] = config.languageManager.CapValue()
// RecoverFromErrors defaults to true
if config.Debug.RecoverFromErrors != nil {
@ -798,10 +847,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
}

@ -58,13 +58,8 @@ func (cl *Limiter) AddClient(addr net.IP, force bool) error {
cl.Lock()
defer cl.Unlock()
if !cl.enabled {
return nil
}
// check exempted lists
// we don't track populations for exempted addresses or nets - this is by design
if utils.IPInNets(addr, cl.exemptedNets) {
if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) {
return nil
}
@ -85,7 +80,7 @@ func (cl *Limiter) RemoveClient(addr net.IP) {
cl.Lock()
defer cl.Unlock()
if !cl.enabled {
if !cl.enabled || utils.IPInNets(addr, cl.exemptedNets) {
return
}

@ -63,6 +63,9 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo
if isBanned {
return errBanned, banMsg
}
// successfully added a limiter entry for the proxied IP;
// remove the entry for the real IP if applicable (#197)
client.server.connectionLimiter.RemoveClient(session.realIP)
// given IP is sane! override the client's current IP
ipstring := parsedProxiedIP.String()

@ -301,6 +301,11 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
config := server.Config()
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 != "" {
rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account"))
return false
@ -535,6 +540,12 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
toRemove := caps.NewSet()
var capString string
config := server.Config()
supportedCaps := config.Server.supportedCaps
if client.isSTSOnly {
supportedCaps = stsOnlyCaps
}
badCaps := false
if len(msg.Params) > 1 {
capString = msg.Params[1]
@ -546,7 +557,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
remove = true
}
capab, err := caps.NameToCapability(str)
if err != nil || (!remove && !SupportedCapabilities.Has(capab)) {
if err != nil || (!remove && !supportedCaps.Has(capab)) {
badCaps = true
} else if !remove {
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.Strings(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 {
case "LS":
if !client.registered {
@ -568,14 +593,11 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
rb.session.capVersion = newVersion
}
}
// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
// 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))
sendCapLines(supportedCaps, config.Server.capValues)
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":
if !client.registered {

@ -39,13 +39,9 @@ var (
// supportedChannelModesString acts as a cache for when we introduce users
supportedChannelModesString = modes.SupportedChannelModes.String()
// SupportedCapabilities are the caps we advertise.
// MaxLine, SASL and STS may be unset during server startup / rehash.
SupportedCapabilities = caps.NewCompleteSet()
// CapValues are the actual values we advertise to v3.2 clients.
// actual values are set during server startup.
CapValues = caps.NewValues()
// whitelist of caps to serve on the STS-only listener. In particular,
// never advertise SASL, to discourage people from sending their passwords:
stsOnlyCaps = caps.NewSet(caps.STS, caps.MessageTags, caps.ServerTime, caps.LabeledResponse, caps.Nope)
)
// 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
}
if c.isSTSOnly {
server.playRegistrationBurst(session)
return true
}
// client MUST send PASS if necessary, or authenticate with SASL if necessary,
// before completing the other registration commands
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
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)
server.RplISupport(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.helpIndexManager.GenerateIndices(config.languageManager)
currentLanguageValue, _ := CapValues.Get(caps.Languages)
newLanguageValue := config.languageManager.CapValue()
if currentLanguageValue != newLanguageValue {
if oldConfig != nil && config.Server.capValues[caps.Languages] != oldConfig.Server.capValues[caps.Languages] {
updatedCaps.Add(caps.Languages)
CapValues.Set(caps.Languages, newLanguageValue)
}
// SASL
authPreviouslyEnabled := oldConfig != nil && oldConfig.Accounts.AuthenticationEnabled
if config.Accounts.AuthenticationEnabled && (oldConfig == nil || !authPreviouslyEnabled) {
// enabling SASL
SupportedCapabilities.Enable(caps.SASL)
CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
addedCaps.Add(caps.SASL)
} else if !config.Accounts.AuthenticationEnabled && (oldConfig == nil || authPreviouslyEnabled) {
// disabling SASL
SupportedCapabilities.Disable(caps.SASL)
removedCaps.Add(caps.SASL)
}
@ -661,38 +663,25 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
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
stsPreviouslyEnabled := oldConfig != nil && oldConfig.Server.STS.Enabled
stsValue := config.Server.STS.Value()
stsDisabledByRehash := false
stsCurrentCapValue, _ := CapValues.Get(caps.STS)
stsValue := config.Server.capValues[caps.STS]
stsCurrentCapValue := ""
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))
if config.Server.STS.Enabled {
// enabling STS
SupportedCapabilities.Enable(caps.STS)
if !stsPreviouslyEnabled {
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
if (config.Server.STS.Enabled != stsPreviouslyEnabled) || (stsValue != stsCurrentCapValue) {
// XXX: STS is always removed by CAP NEW sts=duration=0, not CAP DEL
// so the appropriate notify is always a CAP NEW; put it in addedCaps for any change
addedCaps.Add(caps.STS)
}
if oldConfig != nil && config.Accounts.Bouncer.Enabled != oldConfig.Accounts.Bouncer.Enabled {
if config.Accounts.Bouncer.Enabled {
addedCaps.Add(caps.Bouncer)
} else {
removedCaps.Add(caps.Bouncer)
}
}
@ -706,50 +695,43 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
}
}
// activate the new config
server.SetConfig(config)
// burst new and removed caps
var capBurstSessions []*Session
added := make(map[caps.Version]string)
var removed string
added := make(map[caps.Version][]string)
var removed []string
// 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
server.logger.Debug("server", "Updated Caps", updatedCaps.String(caps.Cap301, CapValues))
server.logger.Debug("server", "Updated Caps", strings.Join(updatedCaps.Strings(caps.Cap301, config.Server.capValues), " "))
addedCaps.Union(updatedCaps)
removedCaps.Union(updatedCaps)
if !addedCaps.Empty() || !removedCaps.Empty() {
capBurstSessions = server.clients.AllWithCapsNotify()
added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
added[caps.Cap302] = addedCaps.String(caps.Cap302, CapValues)
added[caps.Cap301] = addedCaps.Strings(caps.Cap301, config.Server.capValues)
added[caps.Cap302] = addedCaps.Strings(caps.Cap302, config.Server.capValues)
// removed never has values, so we leave it as Cap301
removed = removedCaps.String(caps.Cap301, CapValues)
removed = removedCaps.Strings(caps.Cap301, config.Server.capValues)
}
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
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() {
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)
}
}
}
// save a pointer to the new config
server.SetConfig(config)
server.logger.Info("server", "Using datastore", config.Datastore.Path)
if initial {
if err := server.loadDatastore(config); err != nil {
@ -905,15 +887,11 @@ func (server *Server) setupListeners(config *Config) (err error) {
}
}
publicPlaintextListener := ""
// create new listeners that were not previously configured
numTlsListeners := 0
hasStandardTlsListener := false
for newAddr, newConfig := range config.Server.trueListeners {
if newConfig.TLSConfig != nil {
numTlsListeners += 1
if strings.HasSuffix(newAddr, ":6697") {
hasStandardTlsListener = true
}
if strings.HasPrefix(newAddr, ":") && !newConfig.IsTor && !newConfig.IsSTSOnly && newConfig.TLSConfig == nil {
publicPlaintextListener = newAddr
}
_, exists := server.listeners[newAddr]
if !exists {
@ -929,12 +907,8 @@ func (server *Server) setupListeners(config *Config) (err error) {
}
}
if numTlsListeners == 0 {
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")
}
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")
if publicPlaintextListener != "" {
server.logger.Warning("listeners", fmt.Sprintf("Your server is configured with public plaintext listener %s. Consider disabling it for improved security and privacy.", publicPlaintextListener))
}
return

@ -15,18 +15,22 @@ server:
# The standard plaintext port for IRC is 6667. This will listen on all interfaces:
":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:
":6697":
tls:
key: tls.key
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:
# "/tmp/oragono_sock":