summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorYves Fischer <yvesf+git@xapek.org>2021-02-17 22:58:31 +0100
committerYves Fischer <yvesf+git@xapek.org>2021-02-18 23:01:44 +0100
commit7e420c5d5b2bb4c638fe2278ac69a69e4a6c06e6 (patch)
tree5d30d89d7dce89f75c8a1395696fd085cac395a2 /main.go
downloadsmtp-forward-7e420c5d5b2bb4c638fe2278ac69a69e4a6c06e6.tar.gz
smtp-forward-7e420c5d5b2bb4c638fe2278ac69a69e4a6c06e6.zip
smtp-forward
Diffstat (limited to 'main.go')
-rw-r--r--main.go142
1 files changed, 142 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..37ebedc
--- /dev/null
+++ b/main.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "flag"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/mail"
+ "net/smtp"
+ "net/textproto"
+ "strings"
+
+ "github.com/mhale/smtpd"
+ "github.com/pkg/errors"
+)
+
+var flagListen = flag.String(`l`, `:25`, `Address to listen on`)
+var flagHostname = flag.String(`h`, `HOSTNAME-NOT-SET`, `Server flagHostname`)
+var flagMap = flag.String(`m`, ``, `-m prefix-matcher1:target@targethost,prefix-matcher2:target@targethost`)
+var flagCertFile = flag.String(`c`, ``, ``)
+var flagKeyFile = flag.String(`k`, ``, ``)
+
+func logWrapper(handler smtpd.Handler) smtpd.Handler {
+ return func(remoteAddr net.Addr, from string, to []string, data []byte) (err error) {
+ log.Printf(`received email remoteAddr=%v from=%v`, remoteAddr, from)
+ err = handler(remoteAddr, from, to, data)
+ if err != nil {
+ log.Printf(`failed to forward: %v`, err.Error())
+ return err
+ }
+ log.Printf(`forwarded email remoteAddr=%v from=%v`, remoteAddr, from)
+ return nil
+ }
+}
+
+func transformEmail(data []byte) (headers mail.Header, msgData []byte, err error) {
+ msg, err := mail.ReadMessage(bytes.NewReader(data))
+ if err != nil {
+ return nil, nil, errors.Wrap(err, `failed to parse email`)
+ }
+ headers = make(mail.Header)
+
+ msgData, err = ioutil.ReadAll(msg.Body)
+ if err != nil {
+ return nil, nil, errors.Wrap(err, `failed to parse email body`)
+ }
+
+ for headerKey, headerVal := range msg.Header {
+ headers[headerKey] = headerVal
+ }
+
+ return headers, msgData, nil
+}
+
+func forward(targetEmail string, data []byte) error {
+ toAddress := strings.SplitN(targetEmail, `@`, 2)
+ if len(toAddress) != 2 {
+ return errors.New(`invalid targetEmail address: ` + targetEmail)
+ }
+ mxes, err := net.DefaultResolver.LookupMX(context.Background(), toAddress[1])
+ if err != nil || len(mxes) == 0 {
+ return errors.Wrap(err, `failed targetEmail resolve mx`)
+ }
+
+ headers, msgData, err := transformEmail(data)
+ if err != nil {
+ return errors.Wrap(err, `failed targetEmail transform email`)
+ }
+ headers[textproto.CanonicalMIMEHeaderKey(`To`)] = []string{targetEmail}
+ headers[textproto.CanonicalMIMEHeaderKey(`From`)] = []string{`forwarder@localnet.cc`}
+ headers[textproto.CanonicalMIMEHeaderKey(`Subject`)] = []string{`Forwarded: ` + headers.Get(`subject`)}
+
+ var builder bytes.Buffer
+ for headerName, headerValues := range headers {
+ for _, value := range headerValues {
+ builder.WriteString(textproto.CanonicalMIMEHeaderKey(headerName))
+ builder.WriteString(`: `)
+ builder.WriteString(value)
+ builder.WriteString("\r\n")
+ }
+ }
+ builder.WriteString("\r\n")
+ builder.Write(msgData)
+
+ err = smtp.SendMail(mxes[0].Host+":25", nil, `forwarder@localnet.cc`, []string{targetEmail}, builder.Bytes())
+ if err != nil {
+ return errors.Wrap(err, `failed targetEmail send mail via smtp`)
+ }
+ log.Printf("forwarded targetEmail=%v", targetEmail)
+
+ return nil
+}
+
+func makeHandleEmail(mapping map[string]string) smtpd.Handler {
+ return func(remoteAddr net.Addr, from string, to []string, data []byte) error {
+ for prefix, targetEmail := range mapping {
+ for _, to := range to {
+ if strings.HasPrefix(to, prefix) {
+ err := forward(targetEmail, data)
+ if err != nil {
+ log.Print(`forwarded failed `, to, ` to `, targetEmail, err.Error())
+ }
+ }
+ }
+ }
+ return nil
+ }
+}
+
+func main() {
+ flag.Parse()
+ var mapping = make(map[string]string)
+ for _, m := range strings.Split(*flagMap, `,`) {
+ if m == `` {
+ continue
+ }
+ m := strings.SplitN(m, `:`, 2)
+ if len(m) != 2 {
+ panic(`invalid flag -m: ` + *flagMap)
+ }
+ mapping[m[0]] = m[1]
+ }
+
+ var tlsConfig *tls.Config
+ if *flagCertFile != `` && *flagKeyFile != `` {
+ cert, err := tls.LoadX509KeyPair(*flagCertFile, *flagKeyFile)
+ if err != nil {
+ panic(err)
+ }
+ tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
+ }
+ server := &smtpd.Server{
+ TLSConfig: tlsConfig,
+ Addr: *flagListen,
+ Hostname: *flagHostname,
+ Handler: logWrapper(makeHandleEmail(mapping)),
+ }
+ _ = server.ListenAndServe()
+}