initial commit
This commit is contained in:
commit
3530843c4c
350 changed files with 197839 additions and 0 deletions
198
main.go
Normal file
198
main.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// 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>`
|
||||
Loading…
Add table
Add a link
Reference in a new issue