198 lines
4.4 KiB
Go
198 lines
4.4 KiB
Go
// 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 = `<!doctype html><html lang="en">
|
|
<head>
|
|
<title>MailGo</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#009670">
|
|
<meta name="description" content="MailGo">
|
|
<style>
|
|
body {
|
|
font: 20px Helvetica, sans-serif;
|
|
background-color: #303030;
|
|
color: #e6e6e6;
|
|
margin: 0px;
|
|
padding: 0 150px 0 150px;
|
|
}
|
|
@media only screen and (max-width: 800px) {
|
|
body {
|
|
padding: 0;
|
|
}
|
|
}
|
|
div {
|
|
margin-bottom: 10px;
|
|
}
|
|
label {
|
|
display: inline-block;
|
|
width: 100px;
|
|
text-align: left;
|
|
}
|
|
input, textarea {
|
|
color: #e2e2e2;
|
|
background-color: #444444;
|
|
border-radius: 0.375rem;
|
|
border: 1px solid #222;
|
|
font-size: 1rem;
|
|
font-weight: 400;
|
|
}
|
|
textarea {
|
|
width: 80%;
|
|
}
|
|
h3 {
|
|
background-color: #009670;
|
|
margin: 0 0 20px 0;
|
|
padding: 10px 0 10px 0;
|
|
text-align: center;
|
|
color: #fff;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h3>MailGo</h3>
|
|
<center>
|
|
<form action="/send" method="post" autocomplete="off">
|
|
<div><label for="from">From:</label></div>
|
|
<div><input type="text" id="from" name="from" required autofocus></input></div>
|
|
<div><label for="to">To:</label></div>
|
|
<div><input type="text" id="to" name="to" required></input></div>
|
|
<div><label for="subject">Subject:</label></div>
|
|
<div><input type="text" id="subject" name="subject" required></input></div>
|
|
<div><label for="message">Message:</label></div>
|
|
<div><textarea id="message" name="message" rows="10" spellcheck="false"></textarea></div>
|
|
<input type="submit" style="font-size:1em;" value="Send"></input>
|
|
</form>
|
|
</center></body></html>`
|