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.
This commit is contained in:
Simon Ser 2020-06-04 20:10:17 +02:00
parent 754adc36fb
commit 6c1634799a
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
4 changed files with 76 additions and 31 deletions

View File

@ -13,7 +13,7 @@ A user-friendly IRC bouncer.
## Usage ## Usage
go run ./cmd/sojuctl create-user <username> go run ./cmd/sojuctl create-user <username>
go run ./cmd/soju go run ./cmd/soju -listen irc+insecure://127.0.0.1:6667
Then connect with username `<username>/chat.freenode.net` and join `#soju`. Then connect with username `<username>/chat.freenode.net` and join `#soju`.

View File

@ -5,15 +5,17 @@ import (
"flag" "flag"
"log" "log"
"net" "net"
"net/url"
"strings"
"git.sr.ht/~emersion/soju" "git.sr.ht/~emersion/soju"
"git.sr.ht/~emersion/soju/config" "git.sr.ht/~emersion/soju/config"
) )
func main() { func main() {
var addr, configPath string var listen, configPath string
var debug bool var debug bool
flag.StringVar(&addr, "listen", "", "listening address") flag.StringVar(&listen, "listen", "", "listening address")
flag.StringVar(&configPath, "config", "", "path to configuration file") flag.StringVar(&configPath, "config", "", "path to configuration file")
flag.BoolVar(&debug, "debug", false, "enable debug logging") flag.BoolVar(&debug, "debug", false, "enable debug logging")
flag.Parse() flag.Parse()
@ -29,8 +31,11 @@ func main() {
cfg = config.Defaults() cfg = config.Defaults()
} }
if addr != "" { if listen != "" {
cfg.Addr = addr cfg.Listen = append(cfg.Listen, listen)
}
if len(cfg.Listen) == 0 {
cfg.Listen = []string{":6697"}
} }
db, err := soju.OpenSQLDB(cfg.SQLDriver, cfg.SQLSource) db, err := soju.OpenSQLDB(cfg.SQLDriver, cfg.SQLSource)
@ -38,24 +43,13 @@ func main() {
log.Fatalf("failed to open database: %v", err) log.Fatalf("failed to open database: %v", err)
} }
var ln net.Listener var tlsCfg *tls.Config
if cfg.TLS != nil { if cfg.TLS != nil {
cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath) cert, err := tls.LoadX509KeyPair(cfg.TLS.CertPath, cfg.TLS.KeyPath)
if err != nil { if err != nil {
log.Fatalf("failed to load TLS certificate and key: %v", err) log.Fatalf("failed to load TLS certificate and key: %v", err)
} }
tlsCfg = &tls.Config{Certificates: []tls.Certificate{cert}}
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)
}
} }
srv := soju.NewServer(db) srv := soju.NewServer(db)
@ -64,11 +58,50 @@ func main() {
srv.LogPath = cfg.LogPath srv.LogPath = cfg.LogPath
srv.Debug = debug srv.Debug = debug
log.Printf("server listening on %q", cfg.Addr) for _, listen := range cfg.Listen {
go func() { listenURI := listen
if err := srv.Run(); err != nil { if !strings.Contains(listenURI, ":/") {
log.Fatal(err) // This is a raw domain name, make it an URL with an empty scheme
listenURI = "//" + listenURI
} }
}() u, err := url.Parse(listenURI)
log.Fatal(srv.Serve(ln)) 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())
} }

View File

@ -14,7 +14,7 @@ type TLS struct {
} }
type Server struct { type Server struct {
Addr string Listen []string
Hostname string Hostname string
TLS *TLS TLS *TLS
SQLDriver string SQLDriver string
@ -28,7 +28,6 @@ func Defaults() *Server {
hostname = "localhost" hostname = "localhost"
} }
return &Server{ return &Server{
Addr: ":6667",
Hostname: hostname, Hostname: hostname,
SQLDriver: "sqlite3", SQLDriver: "sqlite3",
SQLSource: "soju.db", SQLSource: "soju.db",
@ -68,9 +67,11 @@ func Parse(r io.Reader) (*Server, error) {
for _, d := range directives { for _, d := range directives {
switch d.Name { switch d.Name {
case "listen": case "listen":
if err := d.parseParams(&srv.Addr); err != nil { var uri string
if err := d.parseParams(&uri); err != nil {
return nil, err return nil, err
} }
srv.Listen = append(srv.Listen, uri)
case "hostname": case "hostname":
if err := d.parseParams(&srv.Hostname); err != nil { if err := d.parseParams(&srv.Hostname); err != nil {
return nil, err return nil, err

View File

@ -56,15 +56,25 @@ be done by adding a "@<client>" suffix to the username.
Enable debug logging (this will leak sensitive information such as Enable debug logging (this will leak sensitive information such as
passwords). passwords).
*-listen* <address> *-listen* <uri>
Listening address (default: ":6667"). Listening URI (default: ":6697").
# CONFIG FILE # CONFIG FILE
The config file has one directive per line. The config file has one directive per line.
*listen* <address> *listen* <uri>
Listening address (default: ":6667"). 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* <name> *hostname* <name>
Server hostname (default: system 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. Connect to a new network at _addr_. _-addr_ is mandatory.
_addr_ supports several connection types: _addr_ supports several connection types:
- _[ircs://]host[:port]_ connects with TLS over TCP - _[ircs://]host[:port]_ connects with TLS over TCP
- _irc+insecure://host[:port]_ connects with plain-text TCP - _irc+insecure://host[:port]_ connects with plain-text TCP