mirror of
https://github.com/wiggin77/mailrelay.git
synced 2024-10-05 01:22:13 +02:00
support STARTTLS
This commit is contained in:
parent
a84781bb3f
commit
a2c76abff0
17
README.md
17
README.md
@ -12,6 +12,22 @@ Run `mailrelay` on a local PC and set your device (e.g. scanner) to send mail to
|
||||
|
||||
`mailrelay` is written in Go, and can be compiled for any Go supported platform including Linux, MacOS, Windows.
|
||||
|
||||
## Encryption
|
||||
|
||||
`mailrelay` uses TLS to connect to your SMTP provider. By default implicit TLS connections are assumed, meaning the connection is established
|
||||
using TLS at the socket level. This is in accordance with [RFC 8314 section 3](https://tools.ietf.org/html/rfc8314#section-3). These connections usually use port 465.
|
||||
|
||||
However, some providers do not adhere to this recommendation (I'm looking at you Office365!) and only support the legacy STARTTLS command, which expects a non-encrypted socket connection at first, which is then upgraded to TLS. To enable this, set `smtp_starttls` to `true` in your config.
|
||||
These connections usually use port 587.
|
||||
|
||||
## Testing your configuration
|
||||
|
||||
You can send a test email using the `-test` flag. A email will be sent using the SMTP provider specified in your `mailrelay.json` configuration.
|
||||
|
||||
```bash
|
||||
./mailrelay -config=./mailrelay.json -test -sender=dlauder@warpmail.net -rcpt=ender.wiggin@warpmail.net
|
||||
```
|
||||
|
||||
## Example (Linux)
|
||||
|
||||
On local PC (192.168.1.54) create file `/etc/mailrelay.json` with contents:
|
||||
@ -22,6 +38,7 @@ On local PC (192.168.1.54) create file `/etc/mailrelay.json` with contents:
|
||||
{
|
||||
"smtp_server": "smtp.fastmail.com",
|
||||
"smtp_port": 465,
|
||||
"smtp_starttls": false,
|
||||
"smtp_username": "username@fastmail.com",
|
||||
"smtp_password": "secretAppPassword",
|
||||
"local_listen_ip": "0.0.0.0",
|
||||
|
30
client.go
30
client.go
@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
|
||||
@ -25,10 +26,9 @@ func sendMail(e *mail.Envelope, config *relayConfig) error {
|
||||
msg.Write(e.Data.Bytes())
|
||||
msg.WriteString("\r\n")
|
||||
|
||||
fmt.Println("==== Starting email send ====")
|
||||
defer fmt.Println("==== Finished email send ====")
|
||||
Logger.Infof("starting email send -- from:%s, starttls:%t", e.MailFrom.String(), config.STARTTLS)
|
||||
var err error
|
||||
var conn *tls.Conn
|
||||
var conn net.Conn
|
||||
var client *smtp.Client
|
||||
var writer io.WriteCloser
|
||||
|
||||
@ -37,8 +37,14 @@ func sendMail(e *mail.Envelope, config *relayConfig) error {
|
||||
ServerName: config.SMTPServer,
|
||||
}
|
||||
|
||||
if conn, err = tls.Dial("tcp", server, tlsconfig); err != nil {
|
||||
return errors.Wrap(err, "dial error")
|
||||
if config.STARTTLS {
|
||||
if conn, err = net.Dial("tcp", server); err != nil {
|
||||
return errors.Wrap(err, "dial error")
|
||||
}
|
||||
} else {
|
||||
if conn, err = tls.Dial("tcp", server, tlsconfig); err != nil {
|
||||
return errors.Wrap(err, "TLS dial error")
|
||||
}
|
||||
}
|
||||
|
||||
if client, err = smtp.NewClient(conn, config.SMTPServer); err != nil {
|
||||
@ -52,6 +58,12 @@ func sendMail(e *mail.Envelope, config *relayConfig) error {
|
||||
}
|
||||
}(&shouldCloseClient)
|
||||
|
||||
if config.STARTTLS {
|
||||
if err = client.StartTLS(tlsconfig); err != nil {
|
||||
return errors.Wrap(err, "starttls error")
|
||||
}
|
||||
}
|
||||
|
||||
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return errors.Wrap(err, "auth error")
|
||||
@ -82,6 +94,7 @@ func sendMail(e *mail.Envelope, config *relayConfig) error {
|
||||
// We only need to close client if some other error prevented us
|
||||
// from getting to `client.Quit`
|
||||
shouldCloseClient = false
|
||||
Logger.Info("email sent with no errors.")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -114,10 +127,3 @@ func getTo(e *mail.Envelope) []string {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func display(b []byte) {
|
||||
s := string(b)
|
||||
fmt.Println("################################")
|
||||
fmt.Printf("%s\n", s)
|
||||
fmt.Println("################################")
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"smtp_server": "smtp.fastmail.com",
|
||||
"smtp_port": 465,
|
||||
"smtp_starttls": false,
|
||||
"smtp_username": "username@fastmail.com",
|
||||
"smtp_password": "secret_app_password",
|
||||
"local_listen_port": 2525,
|
||||
|
110
main.go
110
main.go
@ -4,57 +4,85 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
log "github.com/flashmob/go-guerrilla/log"
|
||||
)
|
||||
|
||||
type loggerLevels struct {
|
||||
Debug *log.Logger
|
||||
Error *log.Logger
|
||||
}
|
||||
var Logger log.Logger
|
||||
|
||||
type mailRelayConfig struct {
|
||||
SMTPServer string `json:"smtp_server"`
|
||||
SMTPPort int `json:"smtp_port"`
|
||||
SMTPUsername string `json:"smtp_username"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
LocalListenIP string `json:"local_listen_ip"`
|
||||
LocalListenPort int `json:"local_listen_port"`
|
||||
AllowedHosts []string `json:"allowed_hosts"`
|
||||
SMTPServer string `json:"smtp_server"`
|
||||
SMTPPort int `json:"smtp_port"`
|
||||
SMTPStartTLS bool `json:"smtp_starttls"`
|
||||
SMTPUsername string `json:"smtp_username"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
LocalListenIP string `json:"local_listen_ip"`
|
||||
LocalListenPort int `json:"local_listen_port"`
|
||||
AllowedHosts []string `json:"allowed_hosts"`
|
||||
}
|
||||
|
||||
// Logger provides application logging.
|
||||
var Logger loggerLevels
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
func run() error {
|
||||
var configFile string
|
||||
var test bool
|
||||
var testsender string
|
||||
var testrcpt string
|
||||
var verbose bool
|
||||
flag.StringVar(&configFile, "config", "/etc/mailrelay.json", "specifies JSON config file")
|
||||
flag.BoolVar(&test, "test", false, "sends a test message to SMTP server")
|
||||
flag.StringVar(&testsender, "sender", "", "used with 'test' to specify sender email address")
|
||||
flag.StringVar(&testrcpt, "rcpt", "", "used with 'test' to specify recipient email address")
|
||||
flag.BoolVar(&verbose, "verbose", false, "verbose output")
|
||||
flag.Parse()
|
||||
|
||||
appConfig, err := loadConfig(configFile)
|
||||
if err != nil {
|
||||
Logger.Error.Fatalf("loading config: %v", err)
|
||||
flag.Usage()
|
||||
return fmt.Errorf("loading config: %w", err)
|
||||
}
|
||||
|
||||
err = Start(appConfig)
|
||||
err = Start(appConfig, verbose)
|
||||
if err != nil {
|
||||
Logger.Error.Fatalf("starting server: %v", err)
|
||||
flag.Usage()
|
||||
return fmt.Errorf("starting server: %w", err)
|
||||
}
|
||||
|
||||
logLevel := "info"
|
||||
if verbose {
|
||||
logLevel = "debug"
|
||||
}
|
||||
Logger, err = log.GetLogger("stdout", logLevel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating logger: %w", err)
|
||||
}
|
||||
|
||||
if test {
|
||||
err = sendTest(testsender, testrcpt, appConfig.LocalListenPort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sending test message: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for SIGINT
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
signal.Notify(c, os.Kill)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Block until a signal is received.
|
||||
s := <-c
|
||||
fmt.Println("Got signal:", s)
|
||||
os.Exit(0)
|
||||
<-c
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfig(path string) (*mailRelayConfig, error) {
|
||||
@ -71,3 +99,33 @@ func loadConfig(path string) (*mailRelayConfig, error) {
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// sendTest sends a test message to the SMTP server specified in mailrelay.json
|
||||
func sendTest(sender string, rcpt string, port int) error {
|
||||
conn, err := smtp.Dial(fmt.Sprintf("localhost:%d", port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.Mail(sender); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.Rcpt(rcpt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writeBody := func(c *smtp.Client) error {
|
||||
wc, err := conn.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wc.Close()
|
||||
_, err = fmt.Fprintf(wc, "From: %s\nSubject: Test message\n\nThis is a test email from mailrelay.\n", sender)
|
||||
return err
|
||||
}
|
||||
if err := writeBody(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.Quit()
|
||||
}
|
||||
|
18
server.go
18
server.go
@ -10,11 +10,20 @@ import (
|
||||
)
|
||||
|
||||
// Start starts the server.
|
||||
func Start(appConfig *mailRelayConfig) (err error) {
|
||||
func Start(appConfig *mailRelayConfig, verbose bool) (err error) {
|
||||
|
||||
listen := fmt.Sprintf("%s:%d", appConfig.LocalListenIP, appConfig.LocalListenPort)
|
||||
|
||||
cfg := &guerrilla.AppConfig{LogFile: log.OutputStdout.String(), AllowedHosts: appConfig.AllowedHosts}
|
||||
logLevel := "info"
|
||||
if verbose {
|
||||
logLevel = "debug"
|
||||
}
|
||||
|
||||
cfg := &guerrilla.AppConfig{
|
||||
LogFile: log.OutputStdout.String(),
|
||||
AllowedHosts: appConfig.AllowedHosts,
|
||||
LogLevel: logLevel,
|
||||
}
|
||||
sc := guerrilla.ServerConfig{
|
||||
ListenInterface: listen,
|
||||
IsEnabled: true,
|
||||
@ -30,6 +39,7 @@ func Start(appConfig *mailRelayConfig) (err error) {
|
||||
"smtp_password": appConfig.SMTPPassword,
|
||||
"smtp_server": appConfig.SMTPServer,
|
||||
"smtp_port": appConfig.SMTPPort,
|
||||
"smtp_starttls": appConfig.SMTPStartTLS,
|
||||
}
|
||||
cfg.BackendConfig = bcfg
|
||||
|
||||
@ -42,6 +52,7 @@ func Start(appConfig *mailRelayConfig) (err error) {
|
||||
type relayConfig struct {
|
||||
SMTPServer string `json:"smtp_server"`
|
||||
SMTPPort int `json:"smtp_port"`
|
||||
STARTTLS bool `json:"smtp_starttls"`
|
||||
SMTPUsername string `json:"smtp_username"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
}
|
||||
@ -67,8 +78,7 @@ var mailRelayProcessor = func() backends.Decorator {
|
||||
|
||||
err := sendMail(e, config)
|
||||
if err != nil {
|
||||
fmt.Printf("!!! %v\n", err)
|
||||
return backends.NewResult(fmt.Sprintf("554 Error: %s", err)), err
|
||||
return backends.NewResult(err.Error()), err
|
||||
}
|
||||
|
||||
return p.Process(e, task)
|
||||
|
Loading…
Reference in New Issue
Block a user