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 rspamAddr string 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") flag.StringVar(&rspamAddr, "rspamd", "rspamd:11332", "address on which rspamd may be found") } 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", rspamAddr), 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 defer s.Close() // nolint:errcheck 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)) } }() } go func() { defer cancel() if err := s.ListenAndServe(); err != nil { log.Error("listener failed", zap.Error(err)) } }() <-ctx.Done() log.Info("exiting") } 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 }