Skip to content

Commit 3fd8f73

Browse files
committed
recv & send
0 parents  commit 3fd8f73

File tree

7 files changed

+389
-0
lines changed

7 files changed

+389
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mailrelay
2+
test
3+
vendor

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Launch",
9+
"type": "go",
10+
"request": "launch",
11+
"mode": "auto",
12+
"program": "${fileDirname}",
13+
"env": {},
14+
"args": []
15+
}
16+
]
17+
}

Gopkg.lock

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Gopkg.toml example
2+
#
3+
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
4+
# for detailed Gopkg.toml documentation.
5+
#
6+
# required = ["github.com/user/thing/cmd/thing"]
7+
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
8+
#
9+
# [[constraint]]
10+
# name = "github.com/user/project"
11+
# version = "1.0.0"
12+
#
13+
# [[constraint]]
14+
# name = "github.com/user/project2"
15+
# branch = "dev"
16+
# source = "github.com/myfork/project2"
17+
#
18+
# [[override]]
19+
# name = "github.com/x/y"
20+
# version = "2.4.0"
21+
#
22+
# [prune]
23+
# non-go = false
24+
# go-tests = true
25+
# unused-packages = true
26+
27+
28+
[[constraint]]
29+
branch = "master"
30+
name = "github.com/flashmob/go-guerrilla"
31+
32+
[prune]
33+
go-tests = true
34+
unused-packages = true

client.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"crypto/tls"
6+
"fmt"
7+
"io"
8+
"net/smtp"
9+
"net/textproto"
10+
11+
"github.com/flashmob/go-guerrilla/mail"
12+
"github.com/pkg/errors"
13+
)
14+
15+
type closeable interface {
16+
Close() error
17+
}
18+
19+
// sendMail sends the contents of the envelope to a SMTP server.
20+
func sendMail(e *mail.Envelope, config *mailRelayConfig) error {
21+
server := fmt.Sprintf("%s:%d", config.Server, config.Port)
22+
to := getTo(e)
23+
24+
var msg bytes.Buffer
25+
msg.Write(e.Data.Bytes())
26+
msg.WriteString("\r\n")
27+
28+
fmt.Println("==== Starting email send ====")
29+
defer fmt.Println("==== Finished email send ====")
30+
var err error
31+
var conn *tls.Conn
32+
var client *smtp.Client
33+
var writer io.WriteCloser
34+
35+
tlsconfig := &tls.Config{
36+
InsecureSkipVerify: true,
37+
ServerName: config.Server,
38+
}
39+
40+
if conn, err = tls.Dial("tcp", server, tlsconfig); err != nil {
41+
return errors.Wrap(err, "dial error")
42+
}
43+
44+
if client, err = smtp.NewClient(conn, config.Server); err != nil {
45+
close(conn, "conn")
46+
return errors.Wrap(err, "newclient error")
47+
}
48+
shouldCloseClient := true
49+
defer func(shouldClose *bool) {
50+
if *shouldClose {
51+
close(client, "client")
52+
}
53+
}(&shouldCloseClient)
54+
55+
auth := smtp.PlainAuth("", config.Username, config.Password, config.Server)
56+
if err = client.Auth(auth); err != nil {
57+
return errors.Wrap(err, "auth error")
58+
}
59+
60+
if err = client.Mail(e.MailFrom.String()); err != nil {
61+
return errors.Wrap(err, "mail error")
62+
}
63+
64+
for _, addy := range to {
65+
if err = client.Rcpt(addy); err != nil {
66+
return errors.Wrap(err, "rcpt error")
67+
}
68+
}
69+
70+
if writer, err = client.Data(); err != nil {
71+
return errors.Wrap(err, "data error")
72+
}
73+
_, err = writer.Write(msg.Bytes())
74+
close(writer, "writer")
75+
if err != nil {
76+
return errors.Wrap(err, "write error")
77+
}
78+
79+
if err = client.Quit(); isQuitError(err) {
80+
return errors.Wrap(err, "quit error")
81+
}
82+
// We only need to close client if some other error prevented us
83+
// from getting to `client.Quit`
84+
shouldCloseClient = false
85+
return nil
86+
}
87+
88+
func close(c closeable, what string) {
89+
err := c.Close()
90+
if err != nil {
91+
fmt.Printf("!!!!! Error closing %s: %v\n", what, err)
92+
}
93+
}
94+
95+
func isQuitError(err error) bool {
96+
if err == nil {
97+
return false
98+
}
99+
e, ok := err.(*textproto.Error)
100+
if ok {
101+
// SMTP codes 221 or 250 are acceptable here
102+
if e.Code == 221 || e.Code == 250 {
103+
return false
104+
}
105+
}
106+
return true
107+
}
108+
109+
// getTo returns the array of email addresses in the envelope.
110+
func getTo(e *mail.Envelope) []string {
111+
var ret []string
112+
for _, addy := range e.RcptTo {
113+
ret = append(ret, addy.String())
114+
}
115+
return ret
116+
}
117+
118+
func display(b []byte) {
119+
s := string(b)
120+
fmt.Println("################################")
121+
fmt.Printf("%s\n", s)
122+
fmt.Println("################################")
123+
}

main.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"os"
9+
"os/signal"
10+
)
11+
12+
type loggerLevels struct {
13+
Debug *log.Logger
14+
Error *log.Logger
15+
}
16+
17+
type mailRelayConfig struct {
18+
Server string `json:"server"`
19+
Port int `json:"port"`
20+
Username string `json:"username"`
21+
Password string `json:"password"`
22+
}
23+
24+
// Logger provides application logging.
25+
var Logger loggerLevels
26+
27+
func main() {
28+
29+
Logger.Debug = log.New(os.Stdout, "debug: ", log.Ldate|log.Ltime|log.Lshortfile)
30+
Logger.Error = log.New(os.Stderr, "error: ", log.Ldate|log.Ltime|log.Lshortfile)
31+
32+
var configFile string
33+
flag.StringVar(&configFile, "config", "/etc/mailrelay.json", "specifies JSON config file")
34+
flag.Parse()
35+
36+
appConfig, err := loadConfig(configFile)
37+
if err != nil {
38+
Logger.Error.Fatalf("loading config: %v", err)
39+
}
40+
41+
err = Start(appConfig)
42+
if err != nil {
43+
Logger.Error.Fatalf("starting server: %v", err)
44+
}
45+
46+
// Wait for SIGINT
47+
c := make(chan os.Signal, 1)
48+
signal.Notify(c, os.Interrupt)
49+
signal.Notify(c, os.Kill)
50+
51+
// Block until a signal is received.
52+
s := <-c
53+
fmt.Println("Got signal:", s)
54+
os.Exit(0)
55+
}
56+
57+
func loadConfig(path string) (*mailRelayConfig, error) {
58+
var cfg mailRelayConfig
59+
file, err := os.Open(path)
60+
if err != nil {
61+
return nil, err
62+
}
63+
defer file.Close()
64+
65+
parser := json.NewDecoder(file)
66+
if err := parser.Decode(&cfg); err != nil {
67+
return nil, err
68+
}
69+
return &cfg, nil
70+
}

server.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
guerrilla "github.com/flashmob/go-guerrilla"
7+
"github.com/flashmob/go-guerrilla/backends"
8+
"github.com/flashmob/go-guerrilla/log"
9+
"github.com/flashmob/go-guerrilla/mail"
10+
)
11+
12+
// Start starts the server.
13+
func Start(appConfig *mailRelayConfig) (err error) {
14+
15+
cfg := &guerrilla.AppConfig{LogFile: log.OutputStdout.String(), AllowedHosts: []string{"warpmail.net"}}
16+
sc := guerrilla.ServerConfig{
17+
ListenInterface: "0.0.0.0:2525",
18+
IsEnabled: true,
19+
}
20+
cfg.Servers = append(cfg.Servers, sc)
21+
22+
bcfg := backends.BackendConfig{
23+
"save_workers_size": 3,
24+
"save_process": "HeadersParser|Header|Hasher|Debugger|MailRelay",
25+
"log_received_mails": true,
26+
"primary_mail_host": "homeoffice.com",
27+
"username": appConfig.Username,
28+
"password": appConfig.Password,
29+
"server": appConfig.Server,
30+
"port": appConfig.Port,
31+
}
32+
cfg.BackendConfig = bcfg
33+
34+
d := guerrilla.Daemon{Config: cfg}
35+
d.AddProcessor("MailRelay", mailRelayProcessor)
36+
37+
return d.Start()
38+
}
39+
40+
// mailRelayProcessor decorator relays emails to another SMTP server.
41+
var mailRelayProcessor = func() backends.Decorator {
42+
config := &mailRelayConfig{}
43+
initFunc := backends.InitializeWith(func(backendConfig backends.BackendConfig) error {
44+
configType := backends.BaseConfig(&mailRelayConfig{})
45+
bcfg, err := backends.Svc.ExtractConfig(backendConfig, configType)
46+
if err != nil {
47+
return err
48+
}
49+
config = bcfg.(*mailRelayConfig)
50+
return nil
51+
})
52+
backends.Svc.AddInitializer(initFunc)
53+
54+
return func(p backends.Processor) backends.Processor {
55+
return backends.ProcessWith(
56+
func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
57+
if task == backends.TaskSaveMail {
58+
59+
err := sendMail(e, config)
60+
if err != nil {
61+
fmt.Printf("!!! %v\n", err)
62+
return backends.NewResult(fmt.Sprintf("554 Error: %s", err)), err
63+
}
64+
65+
return p.Process(e, task)
66+
}
67+
return p.Process(e, task)
68+
},
69+
)
70+
}
71+
}

0 commit comments

Comments
 (0)