mirror of
https://github.com/wiggin77/mailrelay.git
synced 2025-08-09 23:27:37 +02:00
recv & send
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mailrelay
|
||||||
|
test
|
||||||
|
vendor
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${fileDirname}",
|
||||||
|
"env": {},
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
71
Gopkg.lock
generated
Normal file
71
Gopkg.lock
generated
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:0a2a75a7b0d611bf7ecb4e5a0054242815dc27e857b4b9f8ec62225993fd11b7"
|
||||||
|
name = "github.com/asaskevich/EventBus"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "68a521d7cbbb7a859c2608b06342f384b3bd5f5a"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:9d8902545dea4a3e1440ff60428426de594266b60e2981394540a133128a03ce"
|
||||||
|
name = "github.com/flashmob/go-guerrilla"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"backends",
|
||||||
|
"log",
|
||||||
|
"mail",
|
||||||
|
"response",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "1c628e503aedc9237ea6a9b13b3d3cdd0aab8f22"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
|
||||||
|
name = "github.com/sirupsen/logrus"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||||
|
version = "v1.0.5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:38f553aff0273ad6f367cb0a0f8b6eecbaef8dc6cb8b50e57b6a81c1d5b1e332"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = ["ssh/terminal"]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "e657309f52e71501f9934566ac06dc5c2f7f11a1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:f343f077a5b0bc3a3788b3a04e24dd417e3e25b2acb529c413e212d2c42416ef"
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = [
|
||||||
|
"unix",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "62eef0e2fa9b2c385f7b2778e763486da6880d37"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
input-imports = [
|
||||||
|
"github.com/flashmob/go-guerrilla",
|
||||||
|
"github.com/flashmob/go-guerrilla/backends",
|
||||||
|
"github.com/flashmob/go-guerrilla/log",
|
||||||
|
"github.com/flashmob/go-guerrilla/mail",
|
||||||
|
"github.com/pkg/errors",
|
||||||
|
]
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
34
Gopkg.toml
Normal file
34
Gopkg.toml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
#
|
||||||
|
# [prune]
|
||||||
|
# non-go = false
|
||||||
|
# go-tests = true
|
||||||
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/flashmob/go-guerrilla"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
123
client.go
Normal file
123
client.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/smtp"
|
||||||
|
"net/textproto"
|
||||||
|
|
||||||
|
"github.com/flashmob/go-guerrilla/mail"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type closeable interface {
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendMail sends the contents of the envelope to a SMTP server.
|
||||||
|
func sendMail(e *mail.Envelope, config *mailRelayConfig) error {
|
||||||
|
server := fmt.Sprintf("%s:%d", config.Server, config.Port)
|
||||||
|
to := getTo(e)
|
||||||
|
|
||||||
|
var msg bytes.Buffer
|
||||||
|
msg.Write(e.Data.Bytes())
|
||||||
|
msg.WriteString("\r\n")
|
||||||
|
|
||||||
|
fmt.Println("==== Starting email send ====")
|
||||||
|
defer fmt.Println("==== Finished email send ====")
|
||||||
|
var err error
|
||||||
|
var conn *tls.Conn
|
||||||
|
var client *smtp.Client
|
||||||
|
var writer io.WriteCloser
|
||||||
|
|
||||||
|
tlsconfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: config.Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn, err = tls.Dial("tcp", server, tlsconfig); err != nil {
|
||||||
|
return errors.Wrap(err, "dial error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if client, err = smtp.NewClient(conn, config.Server); err != nil {
|
||||||
|
close(conn, "conn")
|
||||||
|
return errors.Wrap(err, "newclient error")
|
||||||
|
}
|
||||||
|
shouldCloseClient := true
|
||||||
|
defer func(shouldClose *bool) {
|
||||||
|
if *shouldClose {
|
||||||
|
close(client, "client")
|
||||||
|
}
|
||||||
|
}(&shouldCloseClient)
|
||||||
|
|
||||||
|
auth := smtp.PlainAuth("", config.Username, config.Password, config.Server)
|
||||||
|
if err = client.Auth(auth); err != nil {
|
||||||
|
return errors.Wrap(err, "auth error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Mail(e.MailFrom.String()); err != nil {
|
||||||
|
return errors.Wrap(err, "mail error")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addy := range to {
|
||||||
|
if err = client.Rcpt(addy); err != nil {
|
||||||
|
return errors.Wrap(err, "rcpt error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if writer, err = client.Data(); err != nil {
|
||||||
|
return errors.Wrap(err, "data error")
|
||||||
|
}
|
||||||
|
_, err = writer.Write(msg.Bytes())
|
||||||
|
close(writer, "writer")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "write error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Quit(); isQuitError(err) {
|
||||||
|
return errors.Wrap(err, "quit error")
|
||||||
|
}
|
||||||
|
// We only need to close client if some other error prevented us
|
||||||
|
// from getting to `client.Quit`
|
||||||
|
shouldCloseClient = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func close(c closeable, what string) {
|
||||||
|
err := c.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("!!!!! Error closing %s: %v\n", what, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuitError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
e, ok := err.(*textproto.Error)
|
||||||
|
if ok {
|
||||||
|
// SMTP codes 221 or 250 are acceptable here
|
||||||
|
if e.Code == 221 || e.Code == 250 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTo returns the array of email addresses in the envelope.
|
||||||
|
func getTo(e *mail.Envelope) []string {
|
||||||
|
var ret []string
|
||||||
|
for _, addy := range e.RcptTo {
|
||||||
|
ret = append(ret, addy.String())
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func display(b []byte) {
|
||||||
|
s := string(b)
|
||||||
|
fmt.Println("################################")
|
||||||
|
fmt.Printf("%s\n", s)
|
||||||
|
fmt.Println("################################")
|
||||||
|
}
|
70
main.go
Normal file
70
main.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggerLevels struct {
|
||||||
|
Debug *log.Logger
|
||||||
|
Error *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type mailRelayConfig struct {
|
||||||
|
Server string `json:"server"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger provides application logging.
|
||||||
|
var Logger loggerLevels
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
Logger.Debug = log.New(os.Stdout, "debug: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
Logger.Error = log.New(os.Stderr, "error: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
|
var configFile string
|
||||||
|
flag.StringVar(&configFile, "config", "/etc/mailrelay.json", "specifies JSON config file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
appConfig, err := loadConfig(configFile)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Error.Fatalf("loading config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Start(appConfig)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Error.Fatalf("starting server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for SIGINT
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
signal.Notify(c, os.Kill)
|
||||||
|
|
||||||
|
// Block until a signal is received.
|
||||||
|
s := <-c
|
||||||
|
fmt.Println("Got signal:", s)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(path string) (*mailRelayConfig, error) {
|
||||||
|
var cfg mailRelayConfig
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
parser := json.NewDecoder(file)
|
||||||
|
if err := parser.Decode(&cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
71
server.go
Normal file
71
server.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
guerrilla "github.com/flashmob/go-guerrilla"
|
||||||
|
"github.com/flashmob/go-guerrilla/backends"
|
||||||
|
"github.com/flashmob/go-guerrilla/log"
|
||||||
|
"github.com/flashmob/go-guerrilla/mail"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start starts the server.
|
||||||
|
func Start(appConfig *mailRelayConfig) (err error) {
|
||||||
|
|
||||||
|
cfg := &guerrilla.AppConfig{LogFile: log.OutputStdout.String(), AllowedHosts: []string{"warpmail.net"}}
|
||||||
|
sc := guerrilla.ServerConfig{
|
||||||
|
ListenInterface: "0.0.0.0:2525",
|
||||||
|
IsEnabled: true,
|
||||||
|
}
|
||||||
|
cfg.Servers = append(cfg.Servers, sc)
|
||||||
|
|
||||||
|
bcfg := backends.BackendConfig{
|
||||||
|
"save_workers_size": 3,
|
||||||
|
"save_process": "HeadersParser|Header|Hasher|Debugger|MailRelay",
|
||||||
|
"log_received_mails": true,
|
||||||
|
"primary_mail_host": "homeoffice.com",
|
||||||
|
"username": appConfig.Username,
|
||||||
|
"password": appConfig.Password,
|
||||||
|
"server": appConfig.Server,
|
||||||
|
"port": appConfig.Port,
|
||||||
|
}
|
||||||
|
cfg.BackendConfig = bcfg
|
||||||
|
|
||||||
|
d := guerrilla.Daemon{Config: cfg}
|
||||||
|
d.AddProcessor("MailRelay", mailRelayProcessor)
|
||||||
|
|
||||||
|
return d.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// mailRelayProcessor decorator relays emails to another SMTP server.
|
||||||
|
var mailRelayProcessor = func() backends.Decorator {
|
||||||
|
config := &mailRelayConfig{}
|
||||||
|
initFunc := backends.InitializeWith(func(backendConfig backends.BackendConfig) error {
|
||||||
|
configType := backends.BaseConfig(&mailRelayConfig{})
|
||||||
|
bcfg, err := backends.Svc.ExtractConfig(backendConfig, configType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config = bcfg.(*mailRelayConfig)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
backends.Svc.AddInitializer(initFunc)
|
||||||
|
|
||||||
|
return func(p backends.Processor) backends.Processor {
|
||||||
|
return backends.ProcessWith(
|
||||||
|
func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
|
||||||
|
if task == backends.TaskSaveMail {
|
||||||
|
|
||||||
|
err := sendMail(e, config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("!!! %v\n", err)
|
||||||
|
return backends.NewResult(fmt.Sprintf("554 Error: %s", err)), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Process(e, task)
|
||||||
|
}
|
||||||
|
return p.Process(e, task)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user