144 lines
3 KiB
Go
144 lines
3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/emersion/go-milter"
|
|
"github.com/emersion/go-smtp"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/time/rate"
|
|
|
|
localtls "git.cycore.io/cycore/mail/pkg/tls"
|
|
"git.cycore.io/cycore/mail/server"
|
|
)
|
|
|
|
var (
|
|
listenPort int
|
|
healthPort int
|
|
|
|
enableTLS bool
|
|
)
|
|
|
|
var allowedDomains []string
|
|
|
|
func init() {
|
|
flag.IntVar(&listenPort, "smtp", 2525, "port on which to listen for incoming emails")
|
|
flag.IntVar(&listenPort, "smtps", 2526, "port on which to listen for incoming secure emails")
|
|
flag.IntVar(&healthPort, "health", 8080, "port on which to listen for health checks")
|
|
}
|
|
|
|
func main() {
|
|
var err error
|
|
var log *zap.Logger
|
|
|
|
flag.Parse()
|
|
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
if _, ok := os.LookupEnv("DEBUG"); ok {
|
|
log, err = zap.NewDevelopment()
|
|
} else {
|
|
log, err = zap.NewProduction()
|
|
}
|
|
|
|
if err != nil {
|
|
panic("failed to instantiate logger: " + err.Error())
|
|
}
|
|
|
|
if v := os.Getenv("ALLOWED_DOMAINS"); v != "" {
|
|
if err := json.Unmarshal([]byte(v), &allowedDomains); err != nil {
|
|
log.Fatal("failed to read ALLOWED_DOMAINS from environment", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
if len(allowedDomains) < 1 {
|
|
log.Fatal("no allowed domains")
|
|
}
|
|
|
|
var cm *localtls.CertManager
|
|
|
|
if tlsCertFile, ok := os.LookupEnv("TLS_CERT_FILE"); ok {
|
|
enableTLS = true
|
|
|
|
cm, err = localtls.NewCertManager(tlsCertFile, os.Getenv("TLS_KEY_FILE"))
|
|
if err != nil {
|
|
log.Fatal("failed to create certificate manager", zap.Error(err))
|
|
}
|
|
|
|
go func() {
|
|
log.Info("starting certificate manager")
|
|
|
|
if err := cm.Watch(ctx, log); err != nil {
|
|
log.Error("certificate manager exited", zap.Error(err))
|
|
|
|
cancel()
|
|
}
|
|
}()
|
|
}
|
|
|
|
be := &server.Backend{
|
|
AllowedDomains: allowedDomains,
|
|
Log: log,
|
|
LMTPAddress: os.Getenv("LMTP_ADDRESS"),
|
|
RSpam: milter.NewDefaultClient("tcp", "rspamd:11334"),
|
|
OverallLimiter: rate.NewLimiter(rate.Limit(10), 20),
|
|
}
|
|
|
|
log.Info("starting mail service")
|
|
|
|
s := smtp.NewServer(be)
|
|
defer s.Close() //nolint:errcheck
|
|
|
|
s.Addr = ":2525"
|
|
s.Domain = "cycore.io"
|
|
s.Debug = os.Stderr
|
|
s.WriteTimeout = 10 * time.Second
|
|
s.WriteTimeout = 10 * time.Second
|
|
s.MaxMessageBytes = 300 * 1024 * 1024 // 300 MiB
|
|
s.MaxRecipients = 10
|
|
s.AuthDisabled = true
|
|
|
|
log.Info("starting health service")
|
|
|
|
go runHealthService(cm)
|
|
|
|
if enableTLS {
|
|
s.TLSConfig = &tls.Config{
|
|
GetCertificate: cm.Get,
|
|
}
|
|
|
|
go func() {
|
|
defer cancel()
|
|
|
|
if err := s.ListenAndServeTLS(); err != nil {
|
|
log.Error("TLS listener failed", zap.Error(err))
|
|
}
|
|
}()
|
|
}
|
|
|
|
log.Fatal("server exited", zap.Error(s.ListenAndServe()))
|
|
}
|
|
|
|
func runHealthService(cm *localtls.CertManager) {
|
|
http.HandleFunc("/health", func(w http.ResponseWriter, req *http.Request) {
|
|
if cm != nil && !cm.Ready() {
|
|
http.Error(w, "no TLS cert", http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
w.Write([]byte("OK")) //nolint:errcheck
|
|
})
|
|
|
|
http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) //nolint:errcheck
|
|
}
|