diff --git a/broker.go b/broker.go index 0995c88..a6d06a5 100644 --- a/broker.go +++ b/broker.go @@ -2,7 +2,10 @@ package gott import ( gob "bytes" + "crypto/tls" "encoding/binary" + "errors" + "fmt" "log" "math" "net" @@ -24,6 +27,7 @@ var GOTT *Broker // Broker is the main broker struct. Should not be used directly. Use the global GOTT var instead. type Broker struct { listener net.Listener + tlsListener net.Listener clients map[string]*Client mutex sync.RWMutex config Config @@ -40,6 +44,7 @@ type Broker struct { func NewBroker() (*Broker, error) { GOTT = &Broker{ listener: nil, + tlsListener: nil, clients: map[string]*Client{}, config: defaultConfig(), logger: nil, @@ -67,25 +72,69 @@ func NewBroker() (*Broker, error) { return GOTT, nil } -// Listen starts the broker and listens for new connections on the "address" provided in the config file. +// Listen starts the broker and listens for new connections on the address provided in the config file. func (b *Broker) Listen() error { - l, err := net.Listen("tcp", b.config.Listen) - if err != nil { - return err + listening := false + + if b.config.Listen != "" { + l, err := net.Listen("tcp", b.config.Listen) + if err != nil { + return err + } + defer l.Close() + + b.listener = l + log.Println("Broker listening on " + b.listener.Addr().String()) + b.logger.Info("Broker listening on " + b.listener.Addr().String()) + + go func(b *Broker) { + for { + conn, err := b.listener.Accept() + if err != nil { + log.Printf("Couldn't accept connection: %v\n", err) + } else { + go b.handleConnection(conn) + } + } + }(b) + listening = true } - defer l.Close() - b.listener = l - b.logger.Info("Broker listening on " + b.listener.Addr().String()) + if b.config.Tls.Enabled() { + cert, err := tls.LoadX509KeyPair(b.config.Tls.Cert, b.config.Tls.Key) + if err != nil { + return fmt.Errorf("couldn't load cert or key file: %v", err) + } - for { - conn, err := b.listener.Accept() + config := tls.Config{Certificates: []tls.Certificate{cert}} + + tl, err := tls.Listen("tcp", b.config.Tls.Listen, &config) if err != nil { - log.Printf("Couldn't accept connection: %v\n", err) - } else { - go b.handleConnection(conn) + return err } + + b.tlsListener = tl + log.Println("Started TLS listener on " + b.tlsListener.Addr().String()) + b.logger.Info("Started TLS listener on " + b.tlsListener.Addr().String()) + + go func(b *Broker) { + for { + conn, err := b.tlsListener.Accept() + if err != nil { + log.Printf("Couldn't accept connection: %v\n", err) + } else { + go b.handleConnection(conn) + } + } + }(b) + listening = true + } + + if !listening { + return errors.New("no listeners started. Both non-tls and tls listeners are disabled") } + + select {} } func (b *Broker) addClient(client *Client) { @@ -114,7 +163,7 @@ func (b *Broker) handleConnection(conn net.Conn) { return } - //log.Printf("Accepted connection from %v", conn.RemoteAddr().String()) + log.Printf("Accepted connection from %v", conn.RemoteAddr().String()) c := &Client{ connection: conn, diff --git a/client.go b/client.go index 365405f..2f19488 100644 --- a/client.go +++ b/client.go @@ -49,7 +49,6 @@ loop: _, err := sockBuffer.Read(fixedHeader) if err != nil { - GOTT.logger.Info("client disconnected", zap.String("id", c.ClientID)) //log.Println("fixedHeader read error", err) break } @@ -599,6 +598,8 @@ func (c *Client) disconnect() { if connected { GOTT.invokeOnDisconnect(c.ClientID, c.Username, c.gracefulDisconnect) + + GOTT.logger.Info("client disconnected", zap.String("id", c.ClientID), zap.Bool("graceful", c.gracefulDisconnect)) } } diff --git a/config.go b/config.go index 1515efc..e3d3be7 100644 --- a/config.go +++ b/config.go @@ -10,10 +10,19 @@ import ( "gopkg.in/yaml.v2" ) +type tlsConfig struct { + Listen, Cert, Key string +} + +func (t tlsConfig) Enabled() bool { + return t.Listen != "" && t.Cert != "" && t.Key != "" +} + type Config struct { ConfigPath string Listen string - LogLevel string `yaml:"logLevel"` + Tls tlsConfig + LogLevel string `yaml:"log_level"` Plugins []string logLevel zapcore.Level } @@ -21,6 +30,7 @@ type Config struct { func defaultConfig() Config { return Config{ Listen: ":1883", + Tls: tlsConfig{Listen: ":8883", Cert: "", Key: ""}, LogLevel: "error", ConfigPath: "config.yml", } @@ -34,6 +44,7 @@ func (c *Config) loadConfig() error { if err = ioutil.WriteFile("config.yml", []byte(defaultConfigContent), 0664); err != nil { log.Fatalln("Error creating default config.yml file:", err) } + file = []byte(defaultConfigContent) } if err = yaml.Unmarshal(file, c); err != nil { diff --git a/constants.go b/constants.go index dff68b2..2f3b0fb 100644 --- a/constants.go +++ b/constants.go @@ -51,16 +51,29 @@ const ( defaultConfigContent = `# GOTT configuration file # listen property is the address that the broker will listen on, -# in the format hostname_or_ip:port, -# default is ":1883". +# in the format hostname_or_ip:port. +# In case you wanted to enable connections over TLS only, leave this empty to +# disable it. +# Default is ":1883". listen: ":1883" -# logLevel property defines the level to which the broker should log messages, +# tls property defines TLS configurations. +# To disable leave any of the child properties empty. +# tls.listen: Defines the address that the broker will use to serve traffic over +# TLS, in the format hostname_or_ip:port, default is ":8883". +# tls.cert: Absolute path to the certificate file. +# tls.key: Absolute path to the key file. +# Disabled by default. +tls: + listen: ":8883" + cert: "" + key: "" + +# log_level property defines the minimum level to which the broker should log messages, # available levels are "debug", "info", "error" and "fatal", # "debug" is the lowest and "fatal" is the highest, -# each level includes higher levels as well, -# default is "error". -logLevel: "error" +# each level includes higher levels as well, default is "error". +log_level: "error" # plugins property is a collection of plugin names, # all plugins listed here must be placed in the plugins directory to be loaded,