diff --git a/CHANGELOG.md b/CHANGELOG.md index a583636d..2a29e524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,17 +15,19 @@ New release of Oragono! ### Added * Added `USERHOST` command (thanks @vegax87). +* Added draft IRCv3 capability [draft/sts](http://ircv3.net/specs/core/sts-3.3.html). ### Changed * Logging is now much better and useful. * Can now specify years, months and days (e.g. `1y12m30d`) with DLINE and KLINE. - + ### Removed ### Fixed * Fixed an account issue where clients could login to multiple accounts at once. - - +* Fixed issues that prevented rehashing after the first rehash had gone through successfully. + + ## [0.6.0] - 2017-01-19 We've added a ton of new features in this release! Automated connection throttling, the ability to `KLINE`, updated casemapping and line-length specifications. diff --git a/irc/capability.go b/irc/capability.go index 49c16cfe..40f67143 100644 --- a/irc/capability.go +++ b/irc/capability.go @@ -28,6 +28,7 @@ const ( MultiPrefix Capability = "multi-prefix" SASL Capability = "sasl" ServerTime Capability = "server-time" + STS Capability = "draft/sts" UserhostInNames Capability = "userhost-in-names" ) @@ -46,7 +47,8 @@ var ( MessageTags: true, MultiPrefix: true, // SASL is set during server startup - ServerTime: true, + ServerTime: true, + // STS is set during server startup UserhostInNames: true, } CapValues = map[Capability]string{ diff --git a/irc/config.go b/irc/config.go index 03777ba5..35464d79 100644 --- a/irc/config.go +++ b/irc/config.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/DanielOaks/oragono/irc/custime" "gopkg.in/yaml.v2" ) @@ -132,6 +133,26 @@ type LineLenConfig struct { Rest int } +type STSConfig struct { + Enabled bool + Duration time.Duration `yaml:"duration-real"` + DurationString string `yaml:"duration"` + Port int + Preload bool +} + +// Value returns the STS value to advertise in CAP +func (sts *STSConfig) Value() string { + 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 +} + type Config struct { Network struct { Name string @@ -144,8 +165,9 @@ type Config struct { Listen []string Wslisten string `yaml:"ws-listen"` TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"` - RestAPI RestAPIConfig `yaml:"rest-api"` - CheckIdent bool `yaml:"check-ident"` + STS STSConfig + RestAPI RestAPIConfig `yaml:"rest-api"` + CheckIdent bool `yaml:"check-ident"` MOTD string ConnectionLimits ConnectionLimitsConfig `yaml:"connection-limits"` ConnectionThrottle ConnectionThrottleConfig `yaml:"connection-throttling"` @@ -342,6 +364,15 @@ func LoadConfig(filename string) (config *Config, err error) { if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 { return nil, errors.New("Limits aren't setup properly, check them and make them sane") } + 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) + } + } if config.Server.ConnectionThrottle.Enabled { config.Server.ConnectionThrottle.Duration, err = time.ParseDuration(config.Server.ConnectionThrottle.DurationString) if err != nil { diff --git a/irc/server.go b/irc/server.go index 6bea3209..16f47248 100644 --- a/irc/server.go +++ b/irc/server.go @@ -118,6 +118,7 @@ type Server struct { restAPI *RestAPIConfig signals chan os.Signal store *buntdb.DB + stsEnabled bool whoWas *WhoWasList } @@ -154,6 +155,11 @@ func NewServer(configFilename string, config *Config, logger *Logger) (*Server, SupportedCapabilities[SASL] = true } + if config.Server.STS.Enabled { + SupportedCapabilities[STS] = true + CapValues[STS] = config.Server.STS.Value() + } + if config.Limits.LineLen.Tags > 512 || config.Limits.LineLen.Rest > 512 { SupportedCapabilities[MaxLine] = true CapValues[MaxLine] = fmt.Sprintf("%d,%d", config.Limits.LineLen.Tags, config.Limits.LineLen.Rest) @@ -212,6 +218,7 @@ func NewServer(configFilename string, config *Config, logger *Logger) (*Server, operclasses: *operClasses, operators: opers, signals: make(chan os.Signal, len(ServerExitSignals)), + stsEnabled: config.Server.STS.Enabled, rehashSignal: make(chan os.Signal, 1), restAPI: &config.Server.RestAPI, whoWas: NewWhoWasList(config.Limits.WhowasEntries), @@ -1234,8 +1241,13 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // rehash reloads the config and applies the changes from the config file. func (server *Server) rehash() error { + server.logger.Log(LogDebug, "rehash", "Starting rehash") + // only let one REHASH go on at a time server.rehashMutex.Lock() + defer server.rehashMutex.Unlock() + + server.logger.Log(LogDebug, "rehash", "Got rehash lock") config, err := LoadConfig(server.configFilename) @@ -1290,11 +1302,13 @@ func (server *Server) rehash() error { } } server.clients.ByNickMutex.RUnlock() + server.connectionThrottleMutex.Unlock() server.connectionLimitsMutex.Unlock() // setup new and removed caps addedCaps := make(CapabilitySet) removedCaps := make(CapabilitySet) + updatedCaps := make(CapabilitySet) // SASL if config.Accounts.AuthenticationEnabled && !server.accountAuthenticationEnabled { @@ -1309,11 +1323,42 @@ func (server *Server) rehash() error { } server.accountAuthenticationEnabled = config.Accounts.AuthenticationEnabled + // STS + stsValue := config.Server.STS.Value() + var stsDisabled bool + server.logger.Log(LogDebug, "rehash", "STS Vals", CapValues[STS], stsValue, fmt.Sprintf("server[%v] config[%v]", server.stsEnabled, config.Server.STS.Enabled)) + if config.Server.STS.Enabled && !server.stsEnabled { + // enabling STS + SupportedCapabilities[STS] = true + addedCaps[STS] = true + CapValues[STS] = stsValue + } else if !config.Server.STS.Enabled && server.stsEnabled { + // disabling STS + SupportedCapabilities[STS] = false + removedCaps[STS] = true + stsDisabled = true + } else if config.Server.STS.Enabled && server.stsEnabled && stsValue != CapValues[STS] { + // STS policy updated + CapValues[STS] = stsValue + updatedCaps[STS] = true + } + server.stsEnabled = config.Server.STS.Enabled + // burst new and removed caps var capBurstClients ClientSet added := make(map[CapVersion]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.Log(LogDebug, "rehash", "Updated Caps", updatedCaps.String(Cap301), strconv.Itoa(len(updatedCaps))) + if len(updatedCaps) > 0 { + for capab := range updatedCaps { + addedCaps[capab] = true + removedCaps[capab] = true + } + } + if len(addedCaps) > 0 || len(removedCaps) > 0 { capBurstClients = server.clients.AllWithCaps(CapNotify) @@ -1324,15 +1369,30 @@ func (server *Server) rehash() error { } for sClient := range capBurstClients { - if len(addedCaps) > 0 { - sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion]) + if stsDisabled { + // remove STS policy + //TODO(dan): this is an ugly hack. we can write this better. + stsPolicy := "sts=duration=0" + if len(addedCaps) > 0 { + added[Cap302] = added[Cap302] + " " + stsPolicy + } else { + addedCaps[STS] = true + added[Cap302] = stsPolicy + } } if len(removedCaps) > 0 { sClient.Send(nil, server.name, "CAP", sClient.nick, "DEL", removed) } + if len(addedCaps) > 0 { + sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion]) + } } // set server options + lineLenConfig := LineLenLimits{ + Tags: config.Limits.LineLen.Tags, + Rest: config.Limits.LineLen.Rest, + } server.limits = Limits{ AwayLen: int(config.Limits.AwayLen), ChannelLen: int(config.Limits.ChannelLen), @@ -1341,6 +1401,7 @@ func (server *Server) rehash() error { NickLen: int(config.Limits.NickLen), TopicLen: int(config.Limits.TopicLen), ChanListModes: int(config.Limits.ChanListModes), + LineLen: lineLenConfig, } server.operclasses = *operclasses server.operators = opers @@ -1403,7 +1464,6 @@ func (server *Server) rehash() error { } } - server.rehashMutex.Unlock() return nil } diff --git a/oragono.yaml b/oragono.yaml index 6f4bd6e6..9eed4c7c 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -26,6 +26,24 @@ server: ":6697": key: tls.key cert: tls.crt + + # strict transport security, to get clients to automagically use TLS + sts: + # whether to advertise STS + # + # to stop advertising STS, leave this enabled and set 'duration' below to "0". this will + # advertise to connecting users that the STS policy they have saved is no longer valid + enabled: true + + # how long clients should be forced to use TLS for. + # setting this to a too-long time will mean bad things if you later remove your TLS + duration: 0 + + # tls port - you should be listening on this port above + port: 6697 + + # should clients include this STS policy when they ship their inbuilt preload lists? + preload: false # rest management API, for use with web interface rest-api: