automatic ssl live reload

This commit is contained in:
Aine
2023-02-12 20:43:33 +00:00
parent 7d0d8cd2e6
commit 0f7af734e5
51 changed files with 26652 additions and 32 deletions

100
smtp/fswatcher.go Normal file
View File

@@ -0,0 +1,100 @@
package smtp
import (
"math"
"path/filepath"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"gitlab.com/etke.cc/go/logger"
)
const fsdelay = 100 * time.Millisecond
type FSWatcher struct {
watcher *fsnotify.Watcher
files []string
log *logger.Logger
mu sync.Mutex
t map[string]*time.Timer
}
func NewFSWatcher(files []string, loglevel string) (*FSWatcher, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
for _, file := range files {
err = watcher.Add(filepath.Dir(file))
if err != nil {
return nil, err
}
}
fswatcher := &FSWatcher{
watcher: watcher,
files: files,
log: logger.New("fs.", loglevel),
t: make(map[string]*time.Timer),
}
return fswatcher, nil
}
func (w *FSWatcher) watch(handler func(e fsnotify.Event)) {
for {
select {
case err, ok := <-w.watcher.Errors:
if !ok {
return
}
w.log.Error("%v", err)
case e, ok := <-w.watcher.Events:
if !ok {
return
}
handler(e)
}
}
}
// Start watcher
func (w *FSWatcher) Start(handler func(e fsnotify.Event)) {
w.watch(func(e fsnotify.Event) {
var found bool
for _, f := range w.files {
if f == e.Name {
found = true
}
}
if !found {
return
}
w.mu.Lock()
t, ok := w.t[e.Name]
w.mu.Unlock()
if !ok {
t = time.AfterFunc(math.MaxInt64, func() {
w.log.Info("handling fs event %+v", e)
handler(e)
})
t.Stop()
w.mu.Lock()
w.t[e.Name] = t
w.mu.Unlock()
}
t.Reset(fsdelay)
})
}
// Stop watcher
func (w *FSWatcher) Stop() {
err := w.watcher.Close()
if err != nil {
w.log.Error("cannot stop fs watcher: %v", err)
}
}

View File

@@ -1,6 +1,7 @@
package smtp
import (
"crypto/tls"
"net"
"gitlab.com/etke.cc/go/logger"
@@ -10,17 +11,28 @@ import (
type Listener struct {
log *logger.Logger
done chan struct{}
tls *tls.Config
listener net.Listener
isBanned func(net.Addr) bool
}
func NewListener(actual net.Listener, isBanned func(net.Addr) bool, log *logger.Logger) *Listener {
func NewListener(port string, tlsConfig *tls.Config, isBanned func(net.Addr) bool, log *logger.Logger) (*Listener, error) {
actual, err := net.Listen("tcp", ":"+port)
if err != nil {
return nil, err
}
return &Listener{
log: log,
done: make(chan struct{}, 1),
tls: tlsConfig,
listener: actual,
isBanned: isBanned,
}
}, nil
}
func (l *Listener) SetTLSConfig(cfg *tls.Config) {
l.tls = cfg
}
// Accept waits for and returns the next connection to the listener.
@@ -43,6 +55,10 @@ func (l *Listener) Accept() (net.Conn, error) {
}
l.log.Info("accepted connection from %q", conn.RemoteAddr())
if l.tls != nil {
return tls.Server(conn, l.tls), nil
}
return conn, nil
}
}

View File

@@ -4,9 +4,11 @@ import (
"context"
"crypto/tls"
"net"
"sync"
"time"
"github.com/emersion/go-smtp"
"github.com/fsnotify/fsnotify"
"gitlab.com/etke.cc/go/logger"
"maunium.net/go/mautrix/id"
@@ -28,15 +30,24 @@ type Config struct {
Callers []Caller
}
type TLSConfig struct {
Listener *Listener
Config *tls.Config
Certs []string
Keys []string
Port string
Mu sync.Mutex
}
type Manager struct {
log *logger.Logger
bot matrixbot
fsw *FSWatcher
smtp *smtp.Server
errs chan error
port string
tlsPort string
tlsCfg *tls.Config
port string
tls TLSConfig
}
type matrixbot interface {
@@ -84,14 +95,39 @@ func NewManager(cfg *Config) *Manager {
s.Debug = loggerWriter{func(s string) { log.Info(s) }}
}
m := &Manager{
smtp: s,
bot: cfg.Bot,
log: log,
port: cfg.Port,
tlsPort: cfg.TLSPort,
fsw, err := NewFSWatcher(append(cfg.TLSCerts, cfg.TLSKeys...), cfg.LogLevel)
if err != nil {
log.Error("cannot start FS watcher: %v", err)
}
m := &Manager{
smtp: s,
bot: cfg.Bot,
log: log,
fsw: fsw,
port: cfg.Port,
tls: TLSConfig{
Certs: cfg.TLSCerts,
Keys: cfg.TLSKeys,
Port: cfg.TLSPort,
},
}
m.tls.Mu.Lock()
m.loadTLSConfig()
m.tls.Mu.Unlock()
if m.fsw != nil {
go m.fsw.Start(func(_ fsnotify.Event) {
m.tls.Mu.Lock()
defer m.tls.Mu.Unlock()
ok := m.loadTLSConfig()
if ok {
m.tls.Listener.SetTLSConfig(m.tls.Config)
}
})
}
m.loadTLSConfig(cfg.TLSCerts, cfg.TLSKeys)
return m
}
@@ -99,8 +135,8 @@ func NewManager(cfg *Config) *Manager {
func (m *Manager) Start() error {
m.errs = make(chan error, 1)
go m.listen(m.port, nil)
if m.tlsCfg != nil {
go m.listen(m.tlsPort, m.tlsCfg)
if m.tls.Config != nil {
go m.listen(m.tls.Port, m.tls.Config)
}
return <-m.errs
@@ -108,6 +144,7 @@ func (m *Manager) Start() error {
// Stop SMTP server
func (m *Manager) Stop() {
m.fsw.Stop()
err := m.smtp.Close()
if err != nil {
m.log.Error("cannot stop SMTP server properly: %v", err)
@@ -115,20 +152,16 @@ func (m *Manager) Stop() {
m.log.Info("SMTP server has been stopped")
}
func (m *Manager) listen(port string, tlsCfg *tls.Config) {
var l net.Listener
var err error
if tlsCfg != nil {
l, err = tls.Listen("tcp", ":"+port, tlsCfg)
} else {
l, err = net.Listen("tcp", ":"+port)
}
func (m *Manager) listen(port string, tlsConfig *tls.Config) {
lwrapper, err := NewListener(port, tlsConfig, m.bot.IsBanned, m.log)
if err != nil {
m.log.Error("cannot start listener on %s: %v", port, err)
m.errs <- err
return
}
lwrapper := NewListener(l, m.bot.IsBanned, m.log)
if tlsConfig != nil {
m.tls.Listener = lwrapper
}
m.log.Info("Starting SMTP server on port %s", port)
err = m.smtp.Serve(lwrapper)
@@ -139,15 +172,17 @@ func (m *Manager) listen(port string, tlsCfg *tls.Config) {
}
}
func (m *Manager) loadTLSConfig(certs, keys []string) {
if len(certs) == 0 || len(keys) == 0 {
// loadTLSConfig returns true if certs were loaded and false if not
func (m *Manager) loadTLSConfig() bool {
m.log.Debug("loading SSL certs...")
if len(m.tls.Certs) == 0 || len(m.tls.Keys) == 0 {
m.log.Warn("SSL certificates are not provided")
return
return false
}
certificates := make([]tls.Certificate, 0, len(certs))
for i, path := range certs {
tlsCert, err := tls.LoadX509KeyPair(path, keys[i])
certificates := make([]tls.Certificate, 0, len(m.tls.Certs))
for i, path := range m.tls.Certs {
tlsCert, err := tls.LoadX509KeyPair(path, m.tls.Keys[i])
if err != nil {
m.log.Error("cannot load SSL certificate: %v", err)
continue
@@ -155,9 +190,10 @@ func (m *Manager) loadTLSConfig(certs, keys []string) {
certificates = append(certificates, tlsCert)
}
if len(certificates) == 0 {
return
return false
}
m.tlsCfg = &tls.Config{Certificates: certificates}
m.smtp.TLSConfig = m.tlsCfg
m.tls.Config = &tls.Config{Certificates: certificates}
m.smtp.TLSConfig = m.tls.Config
return true
}