aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2019-03-02 06:33:13 +0000
committerMike Crute <mike@crute.us>2019-03-02 07:02:51 +0000
commit837762f56718fab3a4d4b34f1dc0151065ed38c2 (patch)
tree7dad48f3f23ba245d317878aff925c7ef7682cad
downloadses-smtpd-proxy-837762f56718fab3a4d4b34f1dc0151065ed38c2.tar.bz2
ses-smtpd-proxy-837762f56718fab3a4d4b34f1dc0151065ed38c2.tar.xz
ses-smtpd-proxy-837762f56718fab3a4d4b34f1dc0151065ed38c2.zip
Initial import
-rw-r--r--.dockerignore5
-rw-r--r--.gitignore1
-rw-r--r--Dockerfile6
-rw-r--r--LICENSE.txt19
-rw-r--r--Makefile16
-rw-r--r--README.md58
-rw-r--r--go.mod9
-rw-r--r--go.sum17
-rw-r--r--main.go91
9 files changed, 222 insertions, 0 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..e9eafac
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
1Dockerfile
2go.mod
3go.sum
4main.go
5Makefile
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8769110
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
ses-smtpd-proxy
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f32f39f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,6 @@
1FROM alpine:latest
2
3RUN apk add --no-cache ca-certificates
4ADD ses-smtpd-proxy /
5
6CMD [ "/ses-smtpd-proxy" ]
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..205da82
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,19 @@
1Copyright (c) 2019 Michael Crute
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of
4this software and associated documentation files (the "Software"), to deal in
5the Software without restriction, including without limitation the rights to
6use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7of the Software, and to permit persons to whom the Software is furnished to do
8so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all
11copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..76bbcc6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
1DOCKER_REGISTRY ?= docker.crute.me
2DOCKER_IMAGE_NAME ?= ses-email-proxy
3DOCKER_TAG ?= latest
4
5DOCKER_IMAGE_SPEC = $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_NAME):$(DOCKER_TAG)
6
7.PHONY: docker
8docker: ses-smtpd-proxy
9 docker build -t $(DOCKER_IMAGE_SPEC) .
10
11.PHONY: publish
12publish: docker
13 docker push $(DOCKER_IMAGE_SPEC)
14
15ses-smtpd-proxy: main.go
16 CGO_ENABLED=0 go build -o $@ $<
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..080e39c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
1# SMTP to SES Mail Proxy
2
3This is a tiny little proxy that speaks unauthenticated SMTP on the front side
4and makes calls to the SES
5[SendRawEmail](https://docs.aws.amazon.com/ses/latest/APIReference/API_SendRawEmail.html)
6on the back side.
7
8Everything this software does is possible with a more fully-featured mail
9server like Postfix but requires setting up Postfix (which is complicated) and,
10if following best practices, rotating credentials every 90 days (which is
11annoying). Because this integrates with the AWS SDK it can be configured
12through the normal SDK configuration channels such as the instance metadata
13service which provides dynamic credentials or environment variables, in which
14case you should still manually rotate credentials but have one choke-point to
15do that.
16
17## Usage
18By default the command takes no arguments and will listen on port 2500 on all
19interfaces. The listen interfaces and port can be specified as the only
20argument separated with a colon like so:
21
22```
23./ses-smtpd-proxy 127.0.0.1:2600
24```
25
26## Security Warning
27This server speaks plain unauthenticated SMTP (no TLS) so it's not suitable for
28use in an untrusted environment nor on the public internet. I don't have these
29use-cases but I would accept pull requests implementing these features if you
30do have the use-case and want to add them.
31
32## Building
33To build the binary run `make ses-smtpd-proxy`.
34
35To build a Docker image, which is based on Alpine Latest, run `make docker` or
36`make publish`. The later command will build and push the image. To override
37the defaults specify `DOCKER_REGISTRY`, `DOCKER_IMAGE_NAME`, and `DOCKER_TAG`
38in the make command like so:
39
40```
41make DOCKER_REGISTRY=reg.example.com DOCKER_IMAGE_NAME=ses-proxy DOCKER_TAG=foo docker
42```
43## Contributing
44If you would like to contribute please visit the project's GitHub page and open
45a pull request with your changes. To have the best experience contributing,
46please:
47
48* Don't break backwards compatibility of public interfaces
49* Update the readme, if necessary
50* Follow the coding style of the current code-base
51* Ensure that your code is formatted by gofmt
52* Validate that your changes work with Go 1.11+
53
54All code is reviewed before acceptance and changes may be requested to better
55follow the conventions of the existing API.
56
57## Contributors
58* Mike Crute (@mcrute)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..d0651d8
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,9 @@
1module github.com/mcrute/ses-smtpd-proxy
2
3require (
4 github.com/aws/aws-sdk-go v1.17.9
5 github.com/mcrute/go-smtpd v0.0.0-20190302041702-3bbdd47ced7e
6 github.com/stretchr/testify v1.3.0 // indirect
7 golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 // indirect
8 golang.org/x/text v0.3.0 // indirect
9)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..232ee49
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,17 @@
1github.com/aws/aws-sdk-go v1.17.9 h1:umGyqfZNxB4waFNvARXzBalEwoYz+8Cqk3xM45No9GI=
2github.com/aws/aws-sdk-go v1.17.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
3github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
4github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
6github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
7github.com/mcrute/go-smtpd v0.0.0-20190302041702-3bbdd47ced7e h1:/FsR+P4lNGpbB/yh/PEg36RLbPHPM6dVi3mSY0Rwu5Q=
8github.com/mcrute/go-smtpd v0.0.0-20190302041702-3bbdd47ced7e/go.mod h1:m8WdV6PyiYKhiWhfdLfBG0UksKK1cKtZzNE/DZgiVTg=
9github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
10github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
11github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
12github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
13github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
14golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU=
15golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
16golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
17golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..0e1beed
--- /dev/null
+++ b/main.go
@@ -0,0 +1,91 @@
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "log"
7 "os"
8
9 "github.com/aws/aws-sdk-go/aws/session"
10 "github.com/aws/aws-sdk-go/service/ses"
11 "github.com/mcrute/go-smtpd/smtpd"
12)
13
14const (
15 SES_SIZE_LIMIT = 10000000
16 DEFAULT_ADDR = ":2500"
17)
18
19var sesClient *ses.SES
20
21type Envelope struct {
22 rcpts []*string
23 b bytes.Buffer
24}
25
26func (e *Envelope) AddRecipient(rcpt smtpd.MailAddress) error {
27 email := rcpt.Email()
28 e.rcpts = append(e.rcpts, &email)
29 return nil
30}
31
32func (e *Envelope) BeginData() error {
33 if len(e.rcpts) == 0 {
34 return smtpd.SMTPError("554 5.5.1 Error: no valid recipients")
35 }
36 return nil
37}
38
39func (e *Envelope) Write(line []byte) error {
40 e.b.Write(line)
41 if e.b.Len() > SES_SIZE_LIMIT { // SES limitation
42 log.Println("message size %d exceeds SES limit of %d", e.b.Len(), SES_SIZE_LIMIT)
43 return smtpd.SMTPError("554 5.5.1 Error: maximum message size exceeded")
44 }
45 return nil
46}
47
48func (e *Envelope) logMessageSend() {
49 dr := make([]string, len(e.rcpts))
50 for i := range e.rcpts {
51 dr[i] = *e.rcpts[i]
52 }
53 log.Printf("sending message to %+v", dr)
54}
55
56func (e *Envelope) Close() error {
57 e.logMessageSend()
58 r := &ses.SendRawEmailInput{
59 Destinations: e.rcpts,
60 RawMessage: &ses.RawMessage{Data: e.b.Bytes()},
61 }
62 _, err := sesClient.SendRawEmail(r)
63 if err != nil {
64 log.Printf("ERROR: ses: %v", err)
65 return smtpd.SMTPError(fmt.Sprintf("554 5.5.1 Error: %v", err))
66 }
67 return err
68}
69
70func main() {
71 sesClient = ses.New(session.New())
72 addr := DEFAULT_ADDR
73
74 if len(os.Args) == 2 {
75 addr = os.Args[1]
76 } else if len(os.Args) > 2 {
77 log.Fatalf("usage: %s [listen_host:port]", os.Args[0])
78 }
79
80 s := &smtpd.Server{
81 Addr: addr,
82 OnNewMail: func(c smtpd.Connection, from smtpd.MailAddress) (smtpd.Envelope, error) {
83 return &Envelope{}, nil
84 },
85 }
86 log.Printf("ListenAndServe on %s", addr)
87 err := s.ListenAndServe()
88 if err != nil {
89 log.Fatalf("ListenAndServe: %v", err)
90 }
91}