diff --git a/irc/config.go b/irc/config.go index 4cc0157b..7e10da15 100644 --- a/irc/config.go +++ b/irc/config.go @@ -41,6 +41,12 @@ type TLSListenConfig struct { Key string } +// This is the YAML-deserializable type of the value of the `Server.Listeners` map +type listenerConfigBlock struct { + TLS TLSListenConfig + Tor bool +} + // listenerConfig is the config governing a particular listener (bound address), // in particular whether it has TLS or Tor (or both) enabled. type listenerConfig struct { @@ -252,8 +258,8 @@ type FakelagConfig struct { } type TorListenersConfig struct { - Listeners []string - RequireSasl bool `yaml:"require-sasl"` + Listeners []string // legacy only + RequireSasl bool `yaml:"require-sasl"` Vhost string MaxConnections int `yaml:"max-connections"` ThrottleDuration time.Duration `yaml:"throttle-duration"` @@ -267,15 +273,19 @@ type Config struct { } Server struct { - Password string - passwordBytes []byte - Name string - nameCasefolded string - Listen []string - UnixBindMode os.FileMode `yaml:"unix-bind-mode"` - TLSListeners map[string]TLSListenConfig `yaml:"tls-listeners"` - TorListeners TorListenersConfig `yaml:"tor-listeners"` - listeners map[string]listenerConfig + 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: + trueListeners map[string]listenerConfig STS STSConfig CheckIdent bool `yaml:"check-ident"` MOTD string @@ -481,37 +491,63 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error return operators, nil } -// prepareListeners populates Config.Server.listeners +func loadTlsConfig(config TLSListenConfig) (tlsConfig *tls.Config, err error) { + cert, err := tls.LoadX509KeyPair(config.Cert, config.Key) + if err != nil { + return nil, ErrInvalidCertKeyPair + } + result := tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientAuth: tls.RequestClientCert, + } + return &result, nil +} + +// prepareListeners populates Config.Server.trueListeners func (conf *Config) prepareListeners() (err error) { - torListeners := make(map[string]bool, len(conf.Server.TorListeners.Listeners)) - for _, addr := range conf.Server.TorListeners.Listeners { - torListeners[addr] = true - } - - conf.Server.listeners = make(map[string]listenerConfig, len(conf.Server.Listen)) - - for _, addr := range conf.Server.Listen { - var lconf listenerConfig - lconf.IsTor = torListeners[addr] - tlsListenConf, ok := conf.Server.TLSListeners[addr] - if ok { - cert, err := tls.LoadX509KeyPair(tlsListenConf.Cert, tlsListenConf.Key) - if err != nil { - return ErrInvalidCertKeyPair + listeners := make(map[string]listenerConfig) + if 0 < len(conf.Server.Listeners) { + for addr, block := range conf.Server.Listeners { + var lconf listenerConfig + lconf.IsTor = block.Tor + if block.TLS.Cert != "" { + tlsConfig, err := loadTlsConfig(block.TLS) + if err != nil { + return err + } + lconf.TLSConfig = tlsConfig } - tlsConfig := tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientAuth: tls.RequestClientCert, - } - lconf.TLSConfig = &tlsConfig + listeners[addr] = lconf } - conf.Server.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 + lconf.IsTor = torListeners[addr] + tlsListenConf, ok := conf.Server.TLSListeners[addr] + if ok { + tlsConfig, err := loadTlsConfig(tlsListenConf) + if err != nil { + return err + } + lconf.TLSConfig = tlsConfig + } + listeners[addr] = lconf + } + } else { + return fmt.Errorf("No listeners were configured") } + conf.Server.trueListeners = listeners return nil } -// LoadConfig loads the given YAML configuration file. -func LoadConfig(filename string) (config *Config, err error) { +// LoadRawConfig loads the config without doing any consistency checks or postprocessing +func LoadRawConfig(filename string) (config *Config, err error) { data, err := ioutil.ReadFile(filename) if err != nil { return nil, err @@ -521,6 +557,15 @@ func LoadConfig(filename string) (config *Config, err error) { if err != nil { return nil, err } + 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 + } config.Filename = filename @@ -536,9 +581,6 @@ func LoadConfig(filename string) (config *Config, err error) { if config.Datastore.Path == "" { return nil, ErrDatastorePathMissing } - if len(config.Server.Listen) == 0 { - return nil, ErrNoListenersDefined - } //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 { config.Limits.IdentLen = 20 diff --git a/irc/server.go b/irc/server.go index 611278ff..a4bb1889 100644 --- a/irc/server.go +++ b/irc/server.go @@ -865,7 +865,7 @@ func (server *Server) setupListeners(config *Config) (err error) { // update or destroy all existing listeners for addr := range server.listeners { currentListener := server.listeners[addr] - newConfig, stillConfigured := config.Server.listeners[addr] + newConfig, stillConfigured := config.Server.trueListeners[addr] currentListener.Lock() currentListener.shouldStop = !stillConfigured @@ -883,7 +883,11 @@ func (server *Server) setupListeners(config *Config) (err error) { } // create new listeners that were not previously configured - for newAddr, newConfig := range config.Server.listeners { + numTlsListeners := 0 + for newAddr, newConfig := range config.Server.trueListeners { + if newConfig.TLSConfig != nil { + numTlsListeners += 1 + } _, exists := server.listeners[newAddr] if !exists { // make new listener @@ -898,11 +902,11 @@ func (server *Server) setupListeners(config *Config) (err error) { } } - if len(config.Server.TLSListeners) == 0 { + 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 config.Server.listeners[":6697"].TLSConfig == nil { + if config.Server.trueListeners[":6697"].TLSConfig == nil { 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") } diff --git a/oragono.go b/oragono.go index fe2f9069..b0e434ef 100644 --- a/oragono.go +++ b/oragono.go @@ -39,6 +39,46 @@ func getPassword() string { return strings.TrimSpace(text) } +// implements the `oragono mkcerts` command +func doMkcerts(configFile string, quiet bool) { + config, err := irc.LoadRawConfig(configFile) + if err != nil { + log.Fatal(err) + } + if !quiet { + log.Println("making self-signed certificates") + } + + certToKey := make(map[string]string) + for name, conf := range config.Server.Listeners { + if conf.TLS.Cert == "" { + continue + } + existingKey, ok := certToKey[conf.TLS.Cert] + if ok { + if existingKey == conf.TLS.Key { + continue + } else { + log.Fatal("Conflicting TLS key files for", conf.TLS.Cert) + } + } + if !quiet { + log.Printf(" making cert for %s listener\n", name) + } + host := config.Server.Name + cert, key := conf.TLS.Cert, conf.TLS.Key + err := mkcerts.CreateCert("Oragono", host, cert, key) + if err == nil { + if !quiet { + log.Printf(" Certificate created at %s : %s\n", cert, key) + } + certToKey[cert] = key + } else { + log.Fatal(" Could not create certificate:", err.Error()) + } + } +} + func main() { version := irc.SemVer usage := `oragono. @@ -88,11 +128,14 @@ Options: } else if arguments["mksecret"].(bool) { fmt.Println(utils.GenerateSecretKey()) return + } else if arguments["mkcerts"].(bool) { + doMkcerts(arguments["--conf"].(string), arguments["--quiet"].(bool)) + return } configfile := arguments["--conf"].(string) config, err := irc.LoadConfig(configfile) - if err != nil { + if err != nil && !(err == irc.ErrInvalidCertKeyPair && arguments["mkcerts"].(bool)) { log.Fatal("Config file did not load successfully: ", err.Error()) } @@ -114,25 +157,6 @@ Options: if !arguments["--quiet"].(bool) { log.Println("database upgraded: ", config.Datastore.Path) } - } else if arguments["mkcerts"].(bool) { - if !arguments["--quiet"].(bool) { - log.Println("making self-signed certificates") - } - - for name, conf := range config.Server.TLSListeners { - if !arguments["--quiet"].(bool) { - log.Printf(" making cert for %s listener\n", name) - } - host := config.Server.Name - err := mkcerts.CreateCert("Oragono", host, conf.Cert, conf.Key) - if err == nil { - if !arguments["--quiet"].(bool) { - log.Printf(" Certificate created at %s : %s\n", conf.Cert, conf.Key) - } - } else { - log.Fatal(" Could not create certificate:", err.Error()) - } - } } else if arguments["run"].(bool) { if !arguments["--quiet"].(bool) { logman.Info("server", fmt.Sprintf("Oragono v%s starting", irc.SemVer)) diff --git a/oragono.yaml b/oragono.yaml index 6ca5f1c7..234e6f12 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -11,14 +11,30 @@ server: name: oragono.test # addresses to listen on - listen: - - ":6697" # SSL/TLS port - - ":6667" # plaintext port - # To disable plaintext over the Internet, comment out :6667 and replace with: - # - "127.0.0.1:6667" # (loopback ipv4, localhost-only) - # - "[::1]:6667" # (loopback ipv6, localhost-only) - # Unix domain socket for proxying: - # - "/tmp/oragono_sock" + listeners: + # This is the standard SSL/TLS port for IRC: + ":6697": + tls: + key: tls.key + cert: tls.crt + + # The standard plaintext port for IRC is 6667. Since using plaintext over + # the public Internet poses security and privacy issues, we recommend using + # plaintext only on local interfaces: + "127.0.0.1:6667": # (loopback ipv4, localhost-only) + "[::1]:6667": # (loopback ipv6, localhost-only) + # If you need to use plaintext on non-local interfaces, comment out the above + # two lines, then uncomment the following line: + # ":6667": + + # Example of a Unix domain socket for proxying: + # "/tmp/oragono_sock": + + # Example of a Tor listener: any connection that comes in on this listener will + # be considered a Tor connection. It is strongly recommended that this listener + # *not* be on a public interface --- it should be on 127.0.0.0/8 or unix domain: + # "/tmp/oragono_tor_sock": + # tor: true # sets the permissions for Unix listen sockets. on a typical Linux system, # the default is 0775 or 0755, which prevents other users/groups from connecting @@ -26,23 +42,8 @@ server: # where anyone can connect. unix-bind-mode: 0777 - # tls listeners - tls-listeners: - # listener on ":6697" - ":6697": - key: tls.key - cert: tls.crt - - # tor listeners: designate listeners for use by a tor hidden service / .onion address - # WARNING: if you are running oragono as a pure hidden service, see the - # anonymization / hardening recommendations in docs/MANUAL.md + # configure the behavior of Tor listeners (ignored if you didn't enable any): tor-listeners: - # any connections that come in on these listeners will be considered - # Tor connections. it is strongly recommended that these listeners *not* - # be on public interfaces: they should be on 127.0.0.0/8 or unix domain - listeners: - # - "/tmp/oragono_tor_sock" - # if this is true, connections from Tor must authenticate with SASL require-sasl: false