3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 07:59:31 +01:00
ergo/irc/config.go

990 lines
30 KiB
Go
Raw Normal View History

// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
2017-03-27 14:15:02 +02:00
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
2014-02-09 16:53:42 +01:00
package irc
import (
"crypto/tls"
"fmt"
"io/ioutil"
2014-02-24 07:21:39 +01:00
"log"
2019-02-05 06:19:03 +01:00
"net"
"os"
2018-04-19 08:48:19 +02:00
"regexp"
2019-04-08 03:36:48 +02:00
"sort"
2019-08-27 06:51:09 +02:00
"strconv"
"strings"
"time"
2017-10-05 16:03:53 +02:00
"code.cloudfoundry.org/bytefmt"
2019-08-27 06:51:09 +02:00
"github.com/oragono/oragono/irc/caps"
2019-05-12 10:01:47 +02:00
"github.com/oragono/oragono/irc/cloaks"
"github.com/oragono/oragono/irc/connection_limits"
2017-06-14 20:00:53 +02:00
"github.com/oragono/oragono/irc/custime"
"github.com/oragono/oragono/irc/isupport"
2018-02-03 10:46:14 +01:00
"github.com/oragono/oragono/irc/languages"
2017-06-14 20:00:53 +02:00
"github.com/oragono/oragono/irc/logger"
2018-04-23 00:47:10 +02:00
"github.com/oragono/oragono/irc/modes"
2017-10-05 16:03:53 +02:00
"github.com/oragono/oragono/irc/passwd"
"github.com/oragono/oragono/irc/utils"
"gopkg.in/yaml.v2"
2014-02-09 16:53:42 +01:00
)
// here's how this works: exported (capitalized) members of the config structs
// are defined in the YAML file and deserialized directly from there. They may
// be postprocessed and overwritten by LoadConfig. Unexported (lowercase) members
// are derived from the exported members in LoadConfig.
2017-04-16 03:31:33 +02:00
// TLSListenConfig defines configuration options for listening on TLS.
type TLSListenConfig struct {
2019-11-20 23:14:42 +01:00
Cert string
Key string
Proxy bool
}
2019-06-28 16:45:34 +02:00
// This is the YAML-deserializable type of the value of the `Server.Listeners` map
type listenerConfigBlock struct {
2019-08-27 06:51:09 +02:00
TLS TLSListenConfig
Tor bool
STSOnly bool `yaml:"sts-only"`
2019-06-28 16:45:34 +02:00
}
2019-06-18 04:21:37 +02:00
// listenerConfig is the config governing a particular listener (bound address),
// in particular whether it has TLS or Tor (or both) enabled.
type listenerConfig struct {
2019-11-20 23:43:40 +01:00
TLSConfig *tls.Config
Tor bool
STSOnly bool
ProxyBeforeTLS bool
}
type AccountConfig struct {
Registration AccountRegistrationConfig
2019-01-01 22:45:37 +01:00
AuthenticationEnabled bool `yaml:"authentication-enabled"`
2019-02-05 06:19:03 +01:00
RequireSasl struct {
Enabled bool
Exempted []string
exemptedNets []net.IPNet
} `yaml:"require-sasl"`
LoginThrottling struct {
2019-01-01 22:45:37 +01:00
Enabled bool
Duration time.Duration
MaxAttempts int `yaml:"max-attempts"`
} `yaml:"login-throttling"`
SkipServerPassword bool `yaml:"skip-server-password"`
NickReservation NickReservationConfig `yaml:"nick-reservation"`
Bouncer struct {
Enabled bool
AllowedByDefault bool `yaml:"allowed-by-default"`
}
VHosts VHostConfig
}
2017-04-16 03:31:33 +02:00
// AccountRegistrationConfig controls account registration.
type AccountRegistrationConfig struct {
Enabled bool
EnabledCallbacks []string `yaml:"enabled-callbacks"`
EnabledCredentialTypes []string `yaml:"-"`
VerifyTimeout time.Duration `yaml:"verify-timeout"`
Callbacks struct {
Mailto struct {
Server string
Port int
TLS struct {
Enabled bool
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
ServerName string `yaml:"servername"`
}
Username string
Password string
Sender string
VerifyMessageSubject string `yaml:"verify-message-subject"`
VerifyMessage string `yaml:"verify-message"`
}
}
BcryptCost uint `yaml:"bcrypt-cost"`
}
2018-04-23 08:38:35 +02:00
type VHostConfig struct {
Enabled bool
MaxLength int `yaml:"max-length"`
ValidRegexpRaw string `yaml:"valid-regexp"`
ValidRegexp *regexp.Regexp
UserRequests struct {
Enabled bool
Channel string
Cooldown time.Duration
} `yaml:"user-requests"`
2020-01-12 04:43:40 +01:00
OfferList []string `yaml:"offer-list"`
2018-04-19 08:48:19 +02:00
}
2019-05-19 10:27:44 +02:00
type NickEnforcementMethod int
const (
2019-05-19 10:27:44 +02:00
// NickEnforcementOptional is the zero value; it serializes to
// "optional" in the yaml config, and "default" as an arg to `NS ENFORCE`.
// in both cases, it means "defer to the other source of truth", i.e.,
// in the config, defer to the user's custom setting, and as a custom setting,
2019-05-19 10:27:44 +02:00
// defer to the default in the config. if both are NickEnforcementOptional then
// there is no enforcement.
2019-05-19 10:27:44 +02:00
// XXX: these are serialized as numbers in the database, so beware of collisions
// when refactoring (any numbers currently in use must keep their meanings, or
// else be fixed up by a schema change)
NickEnforcementOptional NickEnforcementMethod = iota
NickEnforcementNone
NickEnforcementWithTimeout
NickEnforcementStrict
)
2019-05-19 10:27:44 +02:00
func nickReservationToString(method NickEnforcementMethod) string {
switch method {
2019-05-19 10:27:44 +02:00
case NickEnforcementOptional:
return "default"
2019-05-19 10:27:44 +02:00
case NickEnforcementNone:
return "none"
2019-05-19 10:27:44 +02:00
case NickEnforcementWithTimeout:
return "timeout"
2019-05-19 10:27:44 +02:00
case NickEnforcementStrict:
return "strict"
default:
return ""
}
}
2019-05-19 10:27:44 +02:00
func nickReservationFromString(method string) (NickEnforcementMethod, error) {
switch strings.ToLower(method) {
case "default":
2019-05-19 10:27:44 +02:00
return NickEnforcementOptional, nil
case "optional":
2019-05-19 10:27:44 +02:00
return NickEnforcementOptional, nil
case "none":
2019-05-19 10:27:44 +02:00
return NickEnforcementNone, nil
case "timeout":
2019-05-19 10:27:44 +02:00
return NickEnforcementWithTimeout, nil
case "strict":
2019-05-19 10:27:44 +02:00
return NickEnforcementStrict, nil
default:
2019-05-19 10:27:44 +02:00
return NickEnforcementOptional, fmt.Errorf("invalid nick-reservation.method value: %s", method)
}
}
2019-05-19 10:27:44 +02:00
func (nr *NickEnforcementMethod) UnmarshalYAML(unmarshal func(interface{}) error) error {
var orig string
var err error
if err = unmarshal(&orig); err != nil {
return err
}
2019-05-19 10:27:44 +02:00
method, err := nickReservationFromString(orig)
if err == nil {
*nr = method
}
return err
}
2019-12-18 13:06:04 +01:00
func (cm *Casemapping) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
var orig string
if err = unmarshal(&orig); err != nil {
return err
}
var result Casemapping
switch strings.ToLower(orig) {
case "ascii":
result = CasemappingASCII
case "precis", "rfc7613", "rfc8265":
result = CasemappingPRECIS
case "permissive", "fun":
result = CasemappingPermissive
default:
return fmt.Errorf("invalid casemapping value: %s", orig)
}
*cm = result
return nil
}
type NickReservationConfig struct {
Enabled bool
AdditionalNickLimit int `yaml:"additional-nick-limit"`
2019-05-19 10:27:44 +02:00
Method NickEnforcementMethod
AllowCustomEnforcement bool `yaml:"allow-custom-enforcement"`
RenameTimeout time.Duration `yaml:"rename-timeout"`
RenamePrefix string `yaml:"rename-prefix"`
}
2017-04-16 03:31:33 +02:00
// ChannelRegistrationConfig controls channel registration.
type ChannelRegistrationConfig struct {
Enabled bool
MaxChannelsPerAccount int `yaml:"max-channels-per-account"`
}
2017-04-16 03:31:33 +02:00
// OperClassConfig defines a specific operator class.
type OperClassConfig struct {
Title string
WhoisLine string
Extends string
Capabilities []string
}
2017-04-16 03:31:33 +02:00
// OperConfig defines a specific operator's configuration.
type OperConfig struct {
2019-12-19 02:33:50 +01:00
Class string
Vhost string
WhoisLine string `yaml:"whois-line"`
Password string
Fingerprint string
Auto bool
2019-12-19 02:33:50 +01:00
Modes string
}
// Various server-enforced limits on data size.
type Limits struct {
2020-01-19 05:47:05 +01:00
AwayLen int `yaml:"awaylen"`
ChanListModes int `yaml:"chan-list-modes"`
ChannelLen int `yaml:"channellen"`
IdentLen int `yaml:"identlen"`
KickLen int `yaml:"kicklen"`
MonitorEntries int `yaml:"monitor-entries"`
NickLen int `yaml:"nicklen"`
TopicLen int `yaml:"topiclen"`
WhowasEntries int `yaml:"whowas-entries"`
RegistrationMessages int `yaml:"registration-messages"`
2019-12-23 21:26:37 +01:00
Multiline struct {
MaxBytes int `yaml:"max-bytes"`
MaxLines int `yaml:"max-lines"`
}
}
2017-04-16 03:31:33 +02:00
// STSConfig controls the STS configuration/
type STSConfig struct {
Enabled bool
Duration time.Duration `yaml:"duration-real"`
DurationString string `yaml:"duration"`
Port int
Preload bool
2019-08-27 06:51:09 +02:00
STSOnlyBanner string `yaml:"sts-only-banner"`
bannerLines []string
}
// Value returns the STS value to advertise in CAP
func (sts *STSConfig) Value() string {
2018-01-28 01:52:07 +01:00
val := fmt.Sprintf("duration=%d", int(sts.Duration.Seconds()))
if sts.Enabled && sts.Port > 0 {
val += fmt.Sprintf(",port=%d", sts.Port)
}
if sts.Enabled && sts.Preload {
val += ",preload"
}
return val
}
2018-03-22 16:04:21 +01:00
type FakelagConfig struct {
Enabled bool
Window time.Duration
BurstLimit uint `yaml:"burst-limit"`
MessagesPerWindow uint `yaml:"messages-per-window"`
Cooldown time.Duration
2018-03-22 16:04:21 +01:00
}
2019-02-26 03:50:43 +01:00
type TorListenersConfig struct {
2019-06-28 16:45:34 +02:00
Listeners []string // legacy only
RequireSasl bool `yaml:"require-sasl"`
2019-02-26 03:50:43 +01:00
Vhost string
MaxConnections int `yaml:"max-connections"`
ThrottleDuration time.Duration `yaml:"throttle-duration"`
MaxConnectionsPerDuration int `yaml:"max-connections-per-duration"`
}
2017-04-16 03:31:33 +02:00
// Config defines the overall configuration.
2014-02-09 16:53:42 +01:00
type Config struct {
2016-04-12 07:44:00 +02:00
Network struct {
Name string
}
Server struct {
2019-06-28 16:45:34 +02:00
Password string
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"`
// Listen and TLSListeners are the legacy style:
Listen []string
TLSListeners map[string]TLSListenConfig `yaml:"tls-listeners"`
// either way, the result is this:
2019-12-17 21:10:23 +01:00
trueListeners map[string]listenerConfig
STS STSConfig
LookupHostnames *bool `yaml:"lookup-hostnames"`
lookupHostnames bool
ForwardConfirmHostnames bool `yaml:"forward-confirm-hostnames"`
CheckIdent bool `yaml:"check-ident"`
MOTD string
motdLines []string
MOTDFormatting bool `yaml:"motd-formatting"`
ProxyAllowedFrom []string `yaml:"proxy-allowed-from"`
proxyAllowedFromNets []net.IPNet
WebIRC []webircConfig `yaml:"webirc"`
MaxSendQString string `yaml:"max-sendq"`
MaxSendQBytes int
AllowPlaintextResume bool `yaml:"allow-plaintext-resume"`
Compatibility struct {
2019-05-09 20:18:30 +02:00
ForceTrailing *bool `yaml:"force-trailing"`
forceTrailing bool
SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"`
}
isupport isupport.List
IPLimits connection_limits.LimiterConfig `yaml:"ip-limits"`
Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"`
supportedCaps *caps.Set
capValues caps.Values
2019-12-18 13:06:04 +01:00
Casemapping Casemapping
}
Languages struct {
Enabled bool
Path string
2018-01-22 08:30:31 +01:00
Default string
}
2019-02-19 08:54:57 +01:00
languageManager *languages.Manager
Datastore struct {
Path string
2018-04-20 09:57:48 +02:00
AutoUpgrade bool
}
Accounts AccountConfig
Channels struct {
DefaultModes *string `yaml:"default-modes"`
defaultModes modes.Modes
2019-05-30 11:33:59 +02:00
MaxChannelsPerClient int `yaml:"max-channels-per-client"`
OpOnlyCreation bool `yaml:"operator-only-creation"`
Registration ChannelRegistrationConfig
}
OperClasses map[string]*OperClassConfig `yaml:"oper-classes"`
Opers map[string]*OperConfig
2014-03-13 09:55:46 +01:00
// parsed operator definitions, unexported so they can't be defined
// directly in YAML:
operators map[string]*Oper
2017-10-02 05:31:40 +02:00
Logging []logger.LoggingConfig
2017-03-06 04:31:10 +01:00
2017-04-30 04:35:07 +02:00
Debug struct {
2019-05-23 01:07:12 +02:00
RecoverFromErrors *bool `yaml:"recover-from-errors"`
recoverFromErrors bool
PprofListener *string `yaml:"pprof-listener"`
2017-04-30 04:35:07 +02:00
}
Limits Limits
2017-09-28 07:30:53 +02:00
2018-03-22 16:04:21 +01:00
Fakelag FakelagConfig
History struct {
2018-12-28 19:45:55 +01:00
Enabled bool
2019-05-19 22:34:52 +02:00
ChannelLength int `yaml:"channel-length"`
ClientLength int `yaml:"client-length"`
AutoresizeWindow time.Duration `yaml:"autoresize-window"`
AutoreplayOnJoin int `yaml:"autoreplay-on-join"`
ChathistoryMax int `yaml:"chathistory-maxmessages"`
}
2017-09-28 07:30:53 +02:00
Filename string
2014-02-24 07:21:39 +01:00
}
2017-04-16 03:31:33 +02:00
// OperClass defines an assembled operator class.
type OperClass struct {
Title string
WhoisLine string `yaml:"whois-line"`
Capabilities map[string]bool // map to make lookups much easier
}
2017-04-16 03:31:33 +02:00
// OperatorClasses returns a map of assembled operator classes from the given config.
2018-04-19 08:48:19 +02:00
func (conf *Config) OperatorClasses() (map[string]*OperClass, error) {
ocs := make(map[string]*OperClass)
// loop from no extends to most extended, breaking if we can't add any more
lenOfLastOcs := -1
for {
if lenOfLastOcs == len(ocs) {
2018-02-03 13:03:36 +01:00
return nil, ErrOperClassDependencies
}
lenOfLastOcs = len(ocs)
var anyMissing bool
for name, info := range conf.OperClasses {
_, exists := ocs[name]
_, extendsExists := ocs[info.Extends]
if exists {
// class already exists
continue
} else if len(info.Extends) > 0 && !extendsExists {
// class we extend on doesn't exist
_, exists := conf.OperClasses[info.Extends]
if !exists {
return nil, fmt.Errorf("Operclass [%s] extends [%s], which doesn't exist", name, info.Extends)
}
anyMissing = true
continue
}
// create new operclass
var oc OperClass
oc.Capabilities = make(map[string]bool)
// get inhereted info from other operclasses
if len(info.Extends) > 0 {
einfo := ocs[info.Extends]
for capab := range einfo.Capabilities {
oc.Capabilities[capab] = true
}
}
// add our own info
oc.Title = info.Title
for _, capab := range info.Capabilities {
oc.Capabilities[capab] = true
}
if len(info.WhoisLine) > 0 {
oc.WhoisLine = info.WhoisLine
} else {
oc.WhoisLine = "is a"
if strings.Contains(strings.ToLower(string(oc.Title[0])), "aeiou") {
oc.WhoisLine += "n"
}
oc.WhoisLine += " "
oc.WhoisLine += oc.Title
}
2018-04-19 08:48:19 +02:00
ocs[name] = &oc
}
if !anyMissing {
// we've got every operclass!
break
}
}
2018-04-19 08:48:19 +02:00
return ocs, nil
}
2017-04-16 03:31:33 +02:00
// Oper represents a single assembled operator's config.
type Oper struct {
2019-12-19 02:33:50 +01:00
Name string
Class *OperClass
WhoisLine string
Vhost string
Pass []byte
Fingerprint string
Auto bool
2019-12-19 02:33:50 +01:00
Modes []modes.ModeChange
}
2017-04-16 03:31:33 +02:00
// Operators returns a map of operator configs from the given OperClass and config.
2018-04-19 08:48:19 +02:00
func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error) {
operators := make(map[string]*Oper)
for name, opConf := range conf.Opers {
var oper Oper
// oper name
name, err := CasefoldName(name)
if err != nil {
return nil, fmt.Errorf("Could not casefold oper name: %s", err.Error())
}
2018-04-19 08:48:19 +02:00
oper.Name = name
if opConf.Password != "" {
oper.Pass, err = decodeLegacyPasswordHash(opConf.Password)
if err != nil {
2019-12-19 15:30:49 +01:00
return nil, fmt.Errorf("Oper %s has an invalid password hash: %s", oper.Name, err.Error())
}
}
2019-12-29 17:59:49 +01:00
if opConf.Fingerprint != "" {
oper.Fingerprint, err = utils.NormalizeCertfp(opConf.Fingerprint)
if err != nil {
return nil, fmt.Errorf("Oper %s has an invalid fingerprint: %s", oper.Name, err.Error())
}
}
oper.Auto = opConf.Auto
if oper.Pass == nil && oper.Fingerprint == "" {
return nil, fmt.Errorf("Oper %s has neither a password nor a fingerprint", name)
}
oper.Vhost = opConf.Vhost
2018-04-19 08:48:19 +02:00
class, exists := oc[opConf.Class]
if !exists {
return nil, fmt.Errorf("Could not load operator [%s] - they use operclass [%s] which does not exist", name, opConf.Class)
}
2018-04-19 08:48:19 +02:00
oper.Class = class
if len(opConf.WhoisLine) > 0 {
oper.WhoisLine = opConf.WhoisLine
} else {
oper.WhoisLine = class.WhoisLine
}
2018-04-23 00:47:10 +02:00
modeStr := strings.TrimSpace(opConf.Modes)
modeChanges, unknownChanges := modes.ParseUserModeChanges(strings.Split(modeStr, " ")...)
if len(unknownChanges) > 0 {
return nil, fmt.Errorf("Could not load operator [%s] due to unknown modes %v", name, unknownChanges)
}
oper.Modes = modeChanges
// successful, attach to list of opers
2018-04-19 08:48:19 +02:00
operators[name] = &oper
}
return operators, nil
}
2019-06-28 16:45:34 +02:00
func loadTlsConfig(config TLSListenConfig) (tlsConfig *tls.Config, err error) {
cert, err := tls.LoadX509KeyPair(config.Cert, config.Key)
if err != nil {
return nil, ErrInvalidCertKeyPair
2019-06-18 04:21:37 +02:00
}
2019-06-28 16:45:34 +02:00
result := tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert,
}
return &result, nil
}
2019-06-18 04:21:37 +02:00
2019-06-28 16:45:34 +02:00
// prepareListeners populates Config.Server.trueListeners
func (conf *Config) prepareListeners() (err error) {
listeners := make(map[string]listenerConfig)
if 0 < len(conf.Server.Listeners) {
for addr, block := range conf.Server.Listeners {
var lconf listenerConfig
2019-11-20 23:43:40 +01:00
lconf.Tor = block.Tor
lconf.STSOnly = block.STSOnly
if lconf.STSOnly && !conf.Server.STS.Enabled {
2019-08-27 06:51:09 +02:00
return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr)
}
2019-06-28 16:45:34 +02:00
if block.TLS.Cert != "" {
tlsConfig, err := loadTlsConfig(block.TLS)
if err != nil {
return err
}
lconf.TLSConfig = tlsConfig
2019-11-20 23:43:40 +01:00
lconf.ProxyBeforeTLS = block.TLS.Proxy
2019-06-18 04:21:37 +02:00
}
2019-06-28 16:45:34 +02:00
listeners[addr] = lconf
}
} else if 0 < len(conf.Server.Listen) {
log.Printf("WARNING: configuring listeners via the legacy `server.listen` config option")
log.Printf("This will be removed in a later release: you should update to use `server.listeners`")
torListeners := make(map[string]bool, len(conf.Server.TorListeners.Listeners))
for _, addr := range conf.Server.TorListeners.Listeners {
torListeners[addr] = true
}
for _, addr := range conf.Server.Listen {
var lconf listenerConfig
2019-11-20 23:43:40 +01:00
lconf.Tor = torListeners[addr]
2019-06-28 16:45:34 +02:00
tlsListenConf, ok := conf.Server.TLSListeners[addr]
if ok {
tlsConfig, err := loadTlsConfig(tlsListenConf)
if err != nil {
return err
}
lconf.TLSConfig = tlsConfig
2019-06-18 04:21:37 +02:00
}
2019-06-28 16:45:34 +02:00
listeners[addr] = lconf
}
2019-06-28 16:45:34 +02:00
} else {
return fmt.Errorf("No listeners were configured")
}
2019-06-28 16:45:34 +02:00
conf.Server.trueListeners = listeners
2019-06-18 04:21:37 +02:00
return nil
}
2019-06-28 16:45:34 +02:00
// LoadRawConfig loads the config without doing any consistency checks or postprocessing
func LoadRawConfig(filename string) (config *Config, err error) {
data, err := ioutil.ReadFile(filename)
2014-02-09 16:53:42 +01:00
if err != nil {
return nil, err
2014-02-09 16:53:42 +01:00
}
err = yaml.Unmarshal(data, &config)
if err != nil {
return nil, err
}
2019-06-28 16:45:34 +02:00
return
}
// LoadConfig loads the given YAML configuration file.
func LoadConfig(filename string) (config *Config, err error) {
config, err = LoadRawConfig(filename)
if err != nil {
return nil, err
}
2017-09-28 07:30:53 +02:00
config.Filename = filename
2016-04-12 07:44:00 +02:00
if config.Network.Name == "" {
2018-02-03 13:03:36 +01:00
return nil, ErrNetworkNameMissing
2016-04-12 07:44:00 +02:00
}
if config.Server.Name == "" {
2018-02-03 13:03:36 +01:00
return nil, ErrServerNameMissing
}
if !utils.IsServerName(config.Server.Name) {
2018-02-03 13:03:36 +01:00
return nil, ErrServerNameNotHostname
}
2019-12-20 00:41:46 +01:00
config.Server.nameCasefolded = strings.ToLower(config.Server.Name)
2016-08-19 15:21:52 +02:00
if config.Datastore.Path == "" {
2018-02-03 13:03:36 +01:00
return nil, ErrDatastorePathMissing
2016-08-19 15:21:52 +02:00
}
//dan: automagically fix identlen until a few releases in the future (from now, 0.12.0), being a newly-introduced limit
if config.Limits.IdentLen < 1 {
2019-02-03 20:01:46 +01:00
config.Limits.IdentLen = 20
}
2016-10-16 12:14:56 +02:00
if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 {
2018-02-03 13:03:36 +01:00
return nil, ErrLimitsAreInsane
}
if config.Limits.RegistrationMessages == 0 {
config.Limits.RegistrationMessages = 1024
}
2019-08-27 06:51:09 +02:00
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 {
return nil, fmt.Errorf("Could not parse STS duration: %s", 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)
}
2019-08-27 06:51:09 +02:00
if config.Server.STS.STSOnlyBanner != "" {
2020-01-19 05:47:05 +01:00
for _, line := range strings.Split(config.Server.STS.STSOnlyBanner, "\n") {
config.Server.STS.bannerLines = append(config.Server.STS.bannerLines, strings.TrimSpace(line))
}
2019-08-27 06:51:09 +02:00
} 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
}
2019-08-27 06:51:09 +02:00
// set this even if STS is disabled
config.Server.capValues[caps.STS] = config.Server.STS.Value()
2019-12-17 21:10:23 +01:00
// lookup-hostnames defaults to true if unset
if config.Server.LookupHostnames != nil {
config.Server.lookupHostnames = *config.Server.LookupHostnames
} else {
config.Server.lookupHostnames = true
}
2017-10-15 08:18:14 +02:00
// process webirc blocks
var newWebIRC []webircConfig
for _, webirc := range config.Server.WebIRC {
// skip webirc blocks with no hosts (such as the example one)
if len(webirc.Hosts) == 0 {
2017-10-15 08:18:14 +02:00
continue
}
err = webirc.Populate()
2017-10-15 08:18:14 +02:00
if err != nil {
return nil, fmt.Errorf("Could not parse WebIRC config: %s", err.Error())
}
newWebIRC = append(newWebIRC, webirc)
}
config.Server.WebIRC = newWebIRC
2019-08-27 06:51:09 +02:00
2019-12-23 21:26:37 +01:00
if config.Limits.Multiline.MaxBytes <= 0 {
config.Server.supportedCaps.Disable(caps.Multiline)
} else {
var multilineCapValue string
if config.Limits.Multiline.MaxLines == 0 {
multilineCapValue = fmt.Sprintf("max-bytes=%d", config.Limits.Multiline.MaxBytes)
} else {
multilineCapValue = fmt.Sprintf("max-bytes=%d,max-lines=%d", config.Limits.Multiline.MaxBytes, config.Limits.Multiline.MaxLines)
}
config.Server.capValues[caps.Multiline] = multilineCapValue
}
2019-09-01 03:29:23 +02:00
if !config.Accounts.Bouncer.Enabled {
config.Server.supportedCaps.Disable(caps.Bouncer)
}
2017-10-02 05:31:40 +02:00
var newLogConfigs []logger.LoggingConfig
2017-03-06 04:31:10 +01:00
for _, logConfig := range config.Logging {
// methods
methods := make(map[string]bool)
2017-03-06 04:31:10 +01:00
for _, method := range strings.Split(logConfig.Method, " ") {
if len(method) > 0 {
methods[strings.ToLower(method)] = true
2017-03-06 04:31:10 +01:00
}
}
if methods["file"] && logConfig.Filename == "" {
2018-02-03 13:03:36 +01:00
return nil, ErrLoggerFilenameMissing
2017-03-06 04:31:10 +01:00
}
logConfig.MethodFile = methods["file"]
2017-05-01 10:51:37 +02:00
logConfig.MethodStdout = methods["stdout"]
logConfig.MethodStderr = methods["stderr"]
2017-03-06 04:31:10 +01:00
// levels
level, exists := logger.LogLevelNames[strings.ToLower(logConfig.LevelString)]
2017-03-06 04:31:10 +01:00
if !exists {
return nil, fmt.Errorf("Could not translate log leve [%s]", logConfig.LevelString)
}
logConfig.Level = level
// types
for _, typeStr := range strings.Split(logConfig.TypeString, " ") {
if len(typeStr) == 0 {
continue
}
if typeStr == "-" {
2018-02-03 13:03:36 +01:00
return nil, ErrLoggerExcludeEmpty
2017-03-06 04:31:10 +01:00
}
if typeStr[0] == '-' {
typeStr = typeStr[1:]
logConfig.ExcludedTypes = append(logConfig.ExcludedTypes, typeStr)
2017-03-06 04:31:10 +01:00
} else {
logConfig.Types = append(logConfig.Types, typeStr)
2017-03-06 04:31:10 +01:00
}
}
if len(logConfig.Types) < 1 {
2018-02-03 13:03:36 +01:00
return nil, ErrLoggerHasNoTypes
2017-03-06 04:31:10 +01:00
}
2017-03-06 06:50:23 +01:00
newLogConfigs = append(newLogConfigs, logConfig)
2017-03-06 04:31:10 +01:00
}
2017-03-06 06:50:23 +01:00
config.Logging = newLogConfigs
// hardcode this for now
config.Accounts.Registration.EnabledCredentialTypes = []string{"passphrase", "certfp"}
for i, name := range config.Accounts.Registration.EnabledCallbacks {
if name == "none" {
// we store "none" as "*" internally
config.Accounts.Registration.EnabledCallbacks[i] = "*"
}
}
2019-04-08 03:36:48 +02:00
sort.Strings(config.Accounts.Registration.EnabledCallbacks)
2019-02-05 06:19:03 +01:00
config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted)
if err != nil {
return nil, fmt.Errorf("Could not parse require-sasl exempted nets: %v", err.Error())
}
config.Server.proxyAllowedFromNets, err = utils.ParseNetList(config.Server.ProxyAllowedFrom)
if err != nil {
return nil, fmt.Errorf("Could not parse proxy-allowed-from nets: %v", err.Error())
}
2018-04-23 08:38:35 +02:00
rawRegexp := config.Accounts.VHosts.ValidRegexpRaw
2018-04-19 08:48:19 +02:00
if rawRegexp != "" {
regexp, err := regexp.Compile(rawRegexp)
if err == nil {
2018-04-23 08:38:35 +02:00
config.Accounts.VHosts.ValidRegexp = regexp
2018-04-19 08:48:19 +02:00
} else {
log.Printf("invalid vhost regexp: %s\n", err.Error())
}
}
2018-04-23 08:38:35 +02:00
if config.Accounts.VHosts.ValidRegexp == nil {
config.Accounts.VHosts.ValidRegexp = defaultValidVhostRegex
2018-04-19 08:48:19 +02:00
}
2020-01-12 04:43:40 +01:00
for _, vhost := range config.Accounts.VHosts.OfferList {
if !config.Accounts.VHosts.ValidRegexp.MatchString(vhost) {
return nil, fmt.Errorf("invalid offered vhost: %s", vhost)
}
}
2019-01-01 22:45:37 +01:00
if !config.Accounts.LoginThrottling.Enabled {
config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
}
2019-08-27 06:51:09 +02:00
config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
if !config.Accounts.AuthenticationEnabled {
config.Server.supportedCaps.Disable(caps.SASL)
}
2018-03-18 02:32:12 +01:00
maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
2017-03-13 23:12:39 +01:00
if err != nil {
return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
}
2018-03-18 02:32:12 +01:00
config.Server.MaxSendQBytes = int(maxSendQBytes)
2017-03-13 23:12:39 +01:00
2019-02-19 08:54:57 +01:00
config.languageManager, err = languages.NewManager(config.Languages.Enabled, config.Languages.Path, config.Languages.Default)
if err != nil {
return nil, fmt.Errorf("Could not load languages: %s", err.Error())
2018-01-22 08:30:31 +01:00
}
2019-08-27 06:51:09 +02:00
config.Server.capValues[caps.Languages] = config.languageManager.CapValue()
2018-01-22 08:30:31 +01:00
// RecoverFromErrors defaults to true
2019-05-23 01:07:12 +02:00
if config.Debug.RecoverFromErrors != nil {
config.Debug.recoverFromErrors = *config.Debug.RecoverFromErrors
} else {
config.Debug.recoverFromErrors = true
}
// process operator definitions, store them to config.operators
operclasses, err := config.OperatorClasses()
if err != nil {
return nil, err
}
opers, err := config.Operators(operclasses)
if err != nil {
return nil, err
}
config.operators = opers
// parse default channel modes
config.Channels.defaultModes = ParseDefaultChannelModes(config.Channels.DefaultModes)
if config.Server.Password != "" {
config.Server.passwordBytes, err = decodeLegacyPasswordHash(config.Server.Password)
if err != nil {
return nil, err
}
}
if config.Accounts.Registration.BcryptCost == 0 {
config.Accounts.Registration.BcryptCost = passwd.DefaultCost
}
if config.Channels.MaxChannelsPerClient == 0 {
config.Channels.MaxChannelsPerClient = 100
}
if config.Channels.Registration.MaxChannelsPerAccount == 0 {
2019-02-06 21:47:20 +01:00
config.Channels.Registration.MaxChannelsPerAccount = 15
}
2019-05-09 20:18:30 +02:00
forceTrailingPtr := config.Server.Compatibility.ForceTrailing
if forceTrailingPtr != nil {
config.Server.Compatibility.forceTrailing = *forceTrailingPtr
} else {
config.Server.Compatibility.forceTrailing = true
}
config.loadMOTD()
// in the current implementation, we disable history by creating a history buffer
// with zero capacity. but the `enabled` config option MUST be respected regardless
// of this detail
if !config.History.Enabled {
config.History.ChannelLength = 0
config.History.ClientLength = 0
}
2019-05-12 10:01:47 +02:00
config.Server.Cloaks.Initialize()
2019-05-12 22:26:23 +02:00
if config.Server.Cloaks.Enabled {
if config.Server.Cloaks.Secret == "" || config.Server.Cloaks.Secret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
2019-05-13 01:19:36 +02:00
return nil, fmt.Errorf("You must generate a new value of server.ip-cloaking.secret to enable cloaking")
2019-05-12 22:26:23 +02:00
}
2019-12-24 22:20:18 +01:00
if !utils.IsHostname(config.Server.Cloaks.Netname) {
return nil, fmt.Errorf("Invalid netname for cloaked hostnames: %s", config.Server.Cloaks.Netname)
}
2019-05-12 22:26:23 +02:00
}
2019-05-12 08:17:57 +02:00
// now that all postprocessing is complete, regenerate ISUPPORT:
err = config.generateISupport()
if err != nil {
return nil, err
}
2019-11-20 23:14:42 +01:00
err = config.prepareListeners()
if err != nil {
return nil, fmt.Errorf("failed to prepare listeners: %v", err)
}
return config, nil
2014-02-09 16:53:42 +01:00
}
2019-12-23 21:26:37 +01:00
2020-01-14 07:21:47 +01:00
// setISupport sets up our RPL_ISUPPORT reply.
func (config *Config) generateISupport() (err error) {
maxTargetsString := strconv.Itoa(maxTargets)
// add RPL_ISUPPORT tokens
isupport := &config.Server.isupport
isupport.Initialize()
isupport.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen))
isupport.Add("CASEMAPPING", "ascii")
isupport.Add("CHANLIMIT", fmt.Sprintf("%s:%d", chanTypes, config.Channels.MaxChannelsPerClient))
isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), "", modes.Modes{modes.UserLimit, modes.Key}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret}.String()}, ","))
if config.History.Enabled && config.History.ChathistoryMax > 0 {
isupport.Add("draft/CHATHISTORY", strconv.Itoa(config.History.ChathistoryMax))
}
isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
isupport.Add("CHANTYPES", chanTypes)
isupport.Add("ELIST", "U")
isupport.Add("EXCEPTS", "")
isupport.Add("INVEX", "")
isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes)))
isupport.Add("MAXTARGETS", maxTargetsString)
isupport.Add("MODES", "")
isupport.Add("MONITOR", strconv.Itoa(config.Limits.MonitorEntries))
isupport.Add("NETWORK", config.Network.Name)
isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
isupport.Add("PREFIX", "(qaohv)~&@%+")
isupport.Add("RPCHAN", "E")
isupport.Add("RPUSER", "E")
isupport.Add("STATUSMSG", "~&@%+")
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:", maxTargetsString, maxTargetsString, maxTargetsString))
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))
if config.Server.Casemapping == CasemappingPRECIS {
isupport.Add("UTF8MAPPING", precisUTF8MappingToken)
}
err = isupport.RegenerateCachedReply()
return
}
2019-12-23 21:26:37 +01:00
// Diff returns changes in supported caps across a rehash.
func (config *Config) Diff(oldConfig *Config) (addedCaps, removedCaps *caps.Set) {
addedCaps = caps.NewSet()
removedCaps = caps.NewSet()
if oldConfig == nil {
return
}
if oldConfig.Server.capValues[caps.Languages] != config.Server.capValues[caps.Languages] {
// XXX updated caps get a DEL line and then a NEW line with the new value
addedCaps.Add(caps.Languages)
removedCaps.Add(caps.Languages)
}
if !oldConfig.Accounts.AuthenticationEnabled && config.Accounts.AuthenticationEnabled {
addedCaps.Add(caps.SASL)
} else if oldConfig.Accounts.AuthenticationEnabled && !config.Accounts.AuthenticationEnabled {
removedCaps.Add(caps.SASL)
}
if !oldConfig.Accounts.Bouncer.Enabled && config.Accounts.Bouncer.Enabled {
addedCaps.Add(caps.Bouncer)
} else if oldConfig.Accounts.Bouncer.Enabled && !config.Accounts.Bouncer.Enabled {
removedCaps.Add(caps.Bouncer)
}
if oldConfig.Limits.Multiline.MaxBytes != 0 && config.Limits.Multiline.MaxBytes == 0 {
removedCaps.Add(caps.Multiline)
} else if oldConfig.Limits.Multiline.MaxBytes == 0 && config.Limits.Multiline.MaxBytes != 0 {
addedCaps.Add(caps.Multiline)
} else if oldConfig.Limits.Multiline != config.Limits.Multiline {
removedCaps.Add(caps.Multiline)
addedCaps.Add(caps.Multiline)
}
if oldConfig.Server.STS.Enabled != config.Server.STS.Enabled || oldConfig.Server.capValues[caps.STS] != config.Server.capValues[caps.STS] {
// 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)
}
return
}