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 ) var allowedDomains []string func init() { flag.IntVar(&listenPort, "smtp", 2525, "port on which to listen for incoming emails") flag.IntVar(&healthPort, "health", 8080, "port on which to listen for health checks") } func main() { var err error var log *zap.Logger 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") } cm, err := localtls.NewCertManager(os.Getenv("CERTIFICATE_NAME"), os.Getenv("CERTIFICATE_NAMESPACE")) if err != nil { log.Fatal("failed to create certificate manager", zap.Error(err)) } go func() { 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"), RootContext: ctx, RSpam: milter.NewDefaultClient("tcp", "rspamd"), OverallLimiter: rate.NewLimiter(rate.Limit(10), 20), } s := smtp.NewServer(be) defer s.Close() //nolint:errcheck s.Addr = ":2525" s.Domain = "cycore.io" s.WriteTimeout = 10 * time.Second s.WriteTimeout = 10 * time.Second s.MaxMessageBytes = 300 * 1024 * 1024 // 300 MiB s.MaxRecipients = 10 s.TLSConfig = &tls.Config{ GetCertificate: cm.Get, } go runHealthService(cm) log.Fatal("server exited", zap.Error(s.ListenAndServeTLS())) } func runHealthService(cm *localtls.CertManager) { http.HandleFunc("/health", func(w http.ResponseWriter, req *http.Request) { if !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 }