// Copyright (C) 2026 Marius Schellenberger package main import ( "errors" "flag" "fmt" "log/slog" "net/http" "net/smtp" "os" "strings" "git.giftfish.de/ston1th/godrop/v2" "github.com/go-logr/logr" ) var ( version string listen string addr string domain string log logr.Logger ) var allowedFiles = []string{ "/etc/resolv.conf", "/etc/localtime", "/etc/hosts", "/etc/ssl/cert.pem", } func fatal(err error) { log.Error(err, "fatal error") os.Exit(1) } func main() { flag.StringVar(&listen, "listen", ":7979", "listen addr") flag.StringVar(&addr, "addr", "", "smtp server addr") flag.StringVar(&domain, "domain", "", "base sender domain (example: example.com)") flag.Parse() log = logr.FromSlogHandler(slog.NewTextHandler(os.Stdout, nil)) log.Info("starting mailgo", "version", version) if addr == "" { fatal(errors.New("missing smtp server addr")) } if domain == "" { fatal(errors.New("missing base sender domain")) } log.Info("config", "listen", listen, "addr", addr, "domain", domain, ) if err := godrop.PledgePromises("stdio inet rpath unveil"); err != nil { fatal(err) } for _, file := range allowedFiles { if err := godrop.Unveil(file, "r"); err != nil { fatal(err) } } if err := godrop.UnveilBlock(); err != nil { fatal(err) } mc := &mailClient{ server: addr, domain: domain, } http.HandleFunc("/send", func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Redirect(w, r, "/?err=method", http.StatusFound) return } log := log.WithName("send") err := r.ParseForm() if err != nil { http.Redirect(w, r, "/?err=parse", http.StatusFound) return } from := r.PostForm.Get("from") to := r.PostForm.Get("to") subject := r.PostForm.Get("subject") if from == "" || to == "" || subject == "" { http.Redirect(w, r, "/?err=missing", http.StatusFound) return } if !strings.ContainsAny(to, "@") { http.Redirect(w, r, "/?err=invalid", http.StatusFound) return } log = log.WithValues("to", to, "from", from, "subject", subject) log.Info("sending mail") msg := r.PostForm.Get("message") err = mc.sendMail(from, to, subject, msg) if err != nil { http.Redirect(w, r, "/?err=send", http.StatusFound) log.Error(err, "mail send failed") return } http.Redirect(w, r, "/", http.StatusFound) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(page)) }) err := http.ListenAndServe(listen, nil) if err != nil { fatal(err) } } type mailClient struct { server string domain string } const msgTmpl = "From: %s\r\n" + "To: %s\r\n" + "Subject: %s\r\n\r\n" + "%s\r\n" func (mc *mailClient) sendMail(from, to, subj, msg string) error { from += "@" + mc.domain message := fmt.Sprintf(msgTmpl, from, to, subj, msg) return smtp.SendMail(mc.server, nil, from, []string{to}, []byte(message)) } const page = ` MailGo

MailGo

`