added IMAP, LetsEncrypt, update layout

This commit is contained in:
2026-08-12 21:14:19 +01:00
parent 6e103959b0
commit 70fa1a5f2c
222 changed files with 42947 additions and 14038 deletions
+48
View File
@@ -0,0 +1,48 @@
package tlsutil
import (
"os"
"path/filepath"
"testing"
)
func TestCertReloaderReload(t *testing.T) {
dir := t.TempDir()
certFile := filepath.Join(dir, "server.crt")
keyFile := filepath.Join(dir, "server.key")
if err := GenerateSelfSignedCert(certFile, keyFile); err != nil {
t.Fatal(err)
}
reloader, err := NewCertReloader(certFile, keyFile)
if err != nil {
t.Fatal(err)
}
certA, err := reloader.GetCertificate(nil)
if err != nil {
t.Fatal(err)
}
// Overwrite with a fresh cert (delete first, since GenerateSelfSignedCert is
// skip-if-exists).
if err := os.Remove(certFile); err != nil {
t.Fatal(err)
}
if err := os.Remove(keyFile); err != nil {
t.Fatal(err)
}
if err := GenerateSelfSignedCert(certFile, keyFile); err != nil {
t.Fatal(err)
}
if err := reloader.Reload(); err != nil {
t.Fatal(err)
}
certB, err := reloader.GetCertificate(nil)
if err != nil {
t.Fatal(err)
}
if string(certA.Certificate[0]) == string(certB.Certificate[0]) {
t.Fatal("expected Reload to pick up a different certificate, got the same bytes")
}
}
+51
View File
@@ -12,6 +12,7 @@ import (
"math/big"
"os"
"path/filepath"
"sync"
"time"
)
@@ -92,3 +93,53 @@ func CreateSSLContext(certFile, keyFile string) (*tls.Config, error) {
MinVersion: tls.VersionTLS12,
}, nil
}
// CertReloader holds the currently-active certificate behind a tls.Config's
// GetCertificate hook, so a listener can pick up a newly-obtained/renewed certificate
// (see internal/acmecert) without restarting the process.
type CertReloader struct {
certFile, keyFile string
mu sync.RWMutex
cert *tls.Certificate
}
// NewCertReloader loads certFile/keyFile once and returns a reloader ready to hand to
// a tls.Config's GetCertificate field.
func NewCertReloader(certFile, keyFile string) (*CertReloader, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
return &CertReloader{certFile: certFile, keyFile: keyFile, cert: &cert}, nil
}
// GetCertificate satisfies tls.Config.GetCertificate.
func (r *CertReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return r.cert, nil
}
// Reload re-reads certFile/keyFile from disk and atomically swaps the active
// certificate. Called after a successful Let's Encrypt obtain/renew.
func (r *CertReloader) Reload() error {
cert, err := tls.LoadX509KeyPair(r.certFile, r.keyFile)
if err != nil {
return err
}
r.mu.Lock()
r.cert = &cert
r.mu.Unlock()
return nil
}
// NewReloadableTLSConfig builds a tls.Config backed by reloader instead of a fixed
// certificate — used by both the SMTP and IMAP implicit-TLS listeners so a single
// Reload() call (self-signed regeneration or a Let's Encrypt renewal) updates both.
func NewReloadableTLSConfig(reloader *CertReloader) *tls.Config {
return &tls.Config{
GetCertificate: reloader.GetCertificate,
MinVersion: tls.VersionTLS12,
}
}