From 6c1634799ad18cccf28f7810f90618c53538f34a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 4 Jun 2020 20:10:17 +0200 Subject: [PATCH] Allow multiple listeners, default to ircs Users can now specify multiple "listen" directives in their configuration file. If -listen is specified on the CLI, it's added to the list of listeners. Listeners are now parsed as URLs. If the scheme is missing "ircs" is assumed. URLs allow to enable/disable TLS on a per-listener basis and will be used for Unix sockets too. The default listening address is changed from irc+insecure://:6667 to ircs://:6697. This avoids setting up an insecure listener opened to everybody. --- README.md | 2 +- cmd/soju/main.go | 79 ++++++++++++++++++++++++++++++++++-------------- config/config.go | 7 +++-- doc/soju.1.scd | 19 +++++++++--- 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index c2c4e7c..13af0a5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A user-friendly IRC bouncer. ## Usage go run ./cmd/sojuctl create-user - go run ./cmd/soju + go run ./cmd/soju -listen irc+insecure://127.0.0.1:6667 Then connect with username `/chat.freenode.net` and join `#soju`. diff --git a/cmd/soju/main.go b/cmd/soju/main.go index 5f80fb4..1eced71 100644 --- a/cmd/soju/main.go +++ b/cmd/soju/main.go @@ -5,15 +5,17 @@ import ( "flag" "log" "net" + "net/url" + "strings" "git.sr.ht/~emersion/soju" "git.sr.ht/~emersion/soju/config" ) func main() { - var addr, configPath string + var listen, configPath string var debug bool - flag.StringVar(&addr, "listen", "", "listening address") + flag.StringVar(&listen, "listen", "", "listening address") flag.StringVar(&configPath, "config", "", "path to configuration file") flag.BoolVar(&debug, "debug", false, "enable debug logging") flag.Parse() @@ -29,8 +31,11 @@ func main() { cfg = config.Defaults() } - if addr != "" { - cfg.Addr = addr + if listen != "" { + cfg.Listen = append(cfg.Listen, listen) + } + if len(cfg.Listen) == 0 { + cfg.Listen = []string{":6697"} } db, err := soju.OpenSQLDB(cfg.SQLDriver, cfg.SQLSource) @@ -38,24 +43,13 @@ func main() { log.Fatalf("failed to open database: %v", err) } - var ln net.Listener + var tlsCfg *tls.Config if cfg.TLS != nil { cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath) if err != nil { log.Fatalf("failed to load TLS certificate and key: %v", err) } - - tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}} - ln, err = tls.Listen("tcp", cfg.Addr, tlsCfg) - if err != nil { - log.Fatalf("failed to start TLS listener: %v", err) - } - } else { - var err error - ln, err = net.Listen("tcp", cfg.Addr) - if err != nil { - log.Fatalf("failed to start listener: %v", err) - } + tlsCfg = &tls.Config{Certificates: []tls.Certificate{cert}} } srv := soju.NewServer(db) @@ -64,11 +58,50 @@ func main() { srv.LogPath = cfg.LogPath srv.Debug = debug - log.Printf("server listening on %q", cfg.Addr) - go func() { - if err := srv.Run(); err != nil { - log.Fatal(err) + for _, listen := range cfg.Listen { + listenURI := listen + if !strings.Contains(listenURI, ":/") { + // This is a raw domain name, make it an URL with an empty scheme + listenURI = "//" + listenURI } - }() - log.Fatal(srv.Serve(ln)) + u, err := url.Parse(listenURI) + if err != nil { + log.Fatalf("failed to parse listen URI %q: %v", listen, err) + } + + switch u.Scheme { + case "ircs", "": + if tlsCfg == nil { + log.Fatalf("failed to listen on %q: missing TLS configuration", listen) + } + host := u.Host + if _, _, err := net.SplitHostPort(host); err != nil { + host = host + ":6697" + } + ln, err := tls.Listen("tcp", host, tlsCfg) + if err != nil { + log.Fatalf("failed to start TLS listener on %q: %v", listen, err) + } + go func() { + log.Fatal(srv.Serve(ln)) + }() + case "irc+insecure": + host := u.Host + if _, _, err := net.SplitHostPort(host); err != nil { + host = host + ":6667" + } + ln, err := net.Listen("tcp", host) + if err != nil { + log.Fatalf("failed to start listener on %q: %v", listen, err) + } + go func() { + log.Fatal(srv.Serve(ln)) + }() + default: + log.Fatalf("failed to listen on %q: unsupported scheme", listen) + } + + log.Printf("server listening on %q", listen) + } + log.Fatal(srv.Run()) } diff --git a/config/config.go b/config/config.go index 72c4a3b..81f7722 100644 --- a/config/config.go +++ b/config/config.go @@ -14,7 +14,7 @@ type TLS struct { } type Server struct { - Addr string + Listen []string Hostname string TLS *TLS SQLDriver string @@ -28,7 +28,6 @@ func Defaults() *Server { hostname = "localhost" } return &Server{ - Addr: ":6667", Hostname: hostname, SQLDriver: "sqlite3", SQLSource: "soju.db", @@ -68,9 +67,11 @@ func Parse(r io.Reader) (*Server, error) { for _, d := range directives { switch d.Name { case "listen": - if err := d.parseParams(&srv.Addr); err != nil { + var uri string + if err := d.parseParams(&uri); err != nil { return nil, err } + srv.Listen = append(srv.Listen, uri) case "hostname": if err := d.parseParams(&srv.Hostname); err != nil { return nil, err diff --git a/doc/soju.1.scd b/doc/soju.1.scd index db0a84d..8306439 100644 --- a/doc/soju.1.scd +++ b/doc/soju.1.scd @@ -56,15 +56,25 @@ be done by adding a "@" suffix to the username. Enable debug logging (this will leak sensitive information such as passwords). -*-listen*
- Listening address (default: ":6667"). +*-listen* + Listening URI (default: ":6697"). # CONFIG FILE The config file has one directive per line. -*listen*
- Listening address (default: ":6667"). +*listen* + Listening URI (default: ":6697"). + + The following URIs are supported: + + - _[ircs://][host][:port]_ listens with TLS over TCP (default port if + omitted: 6697) + - _irc+insecure://[host][:port]_ listens with plain-text over TCP (default + port if omitted: 6667) + + If the scheme is omitted, "ircs" is assumed. If multiple *listen* + directives are specified, soju will listen on each of them. *hostname* Server hostname (default: system hostname). @@ -97,6 +107,7 @@ abbreviated form, for instance *network* can be abbreviated as *net* or just Connect to a new network at _addr_. _-addr_ is mandatory. _addr_ supports several connection types: + - _[ircs://]host[:port]_ connects with TLS over TCP - _irc+insecure://host[:port]_ connects with plain-text TCP