-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ad04a46
Showing
6 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Tyler Stuyfzand | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
BUILDDIR=./build | ||
GOTIFY_VERSION=master | ||
PLUGIN_NAME=smtp | ||
PLUGIN_ENTRY=plugin.go | ||
GO_VERSION=`cat $(BUILDDIR)/gotify-server-go-version` | ||
DOCKER_BUILD_IMAGE=gotify/build | ||
DOCKER_WORKDIR=/proj | ||
DOCKER_RUN=docker run --rm -v "$$PWD/.:${DOCKER_WORKDIR}" -v "`go env GOPATH`/pkg/mod/.:/go/pkg/mod:ro" -w ${DOCKER_WORKDIR} | ||
DOCKER_GO_BUILD=go build -mod=readonly -a -installsuffix cgo -ldflags "$$LD_FLAGS" -buildmode=plugin | ||
|
||
download-tools: | ||
GO111MODULE=off go get -u github.com/gotify/plugin-api/cmd/gomod-cap | ||
|
||
create-build-dir: | ||
mkdir -p ${BUILDDIR} || true | ||
|
||
update-go-mod: create-build-dir download-tools | ||
wget -LO ${BUILDDIR}/gotify-server.mod https://raw.githubusercontent.com/gotify/server/${GOTIFY_VERSION}/go.mod | ||
gomod-cap -from ${BUILDDIR}/gotify-server.mod -to go.mod | ||
rm ${BUILDDIR}/gotify-server.mod || true | ||
go mod edit -require=github.com/gotify/server$(shell echo "/${GOTIFY_VERSION}" | egrep -o "^/v[2-9][0-9]*")@${GOTIFY_VERSION} | ||
go mod tidy | ||
|
||
get-gotify-server-go-version: create-build-dir | ||
rm ${BUILDDIR}/gotify-server-go-version || true | ||
wget -LO ${BUILDDIR}/gotify-server-go-version https://raw.githubusercontent.com/gotify/server/${GOTIFY_VERSION}/GO_VERSION | ||
|
||
build-linux-amd64: get-gotify-server-go-version update-go-mod | ||
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-amd64 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-amd64${FILE_SUFFIX}.so ${DOCKER_WORKDIR} | ||
|
||
build-linux-arm-7: get-gotify-server-go-version update-go-mod | ||
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm-7 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-arm-7${FILE_SUFFIX}.so ${DOCKER_WORKDIR} | ||
|
||
build-linux-arm64: get-gotify-server-go-version update-go-mod | ||
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm64 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-arm64${FILE_SUFFIX}.so ${DOCKER_WORKDIR} | ||
|
||
build: build-linux-arm-7 build-linux-amd64 build-linux-arm64 | ||
|
||
.PHONY: build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Gotify-SMTP | ||
=========== | ||
|
||
A plugin for piping email messages into [Gotify](https://gotify.net/) without ever hitting an email service. Inspiration for this comes from MailHog and similar implementations where there is no backing email service, it simply forwards/receives messages as needed. | ||
|
||
There are other versions of this (specifically using the API), however this is a standalone plugin that can be loaded by Gotify. | ||
|
||
Usage | ||
----- | ||
|
||
Download the plugin from the releases page, or build it from source (using the Makefile). | ||
|
||
Point your application settings at GOTIFY_IP port 1025, with the username being the name of the account you'd like to send messages to. | ||
|
||
Limitations | ||
----------- | ||
|
||
Currently, HTML messages aren't supported. Markdown might be possible, but currently not planned as most if not all messages include a text/plain variation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module github.com/tystuyfzand/gotify-smtp | ||
|
||
go 1.16 | ||
|
||
require ( | ||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 | ||
github.com/emersion/go-smtp v0.15.0 | ||
github.com/gotify/plugin-api v1.0.0 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= | ||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= | ||
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8= | ||
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= | ||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= | ||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= | ||
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= | ||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= | ||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= | ||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
github.com/gotify/plugin-api v1.0.0 h1:kab40p2TEPLzjmcafOc7JOz75aTsYQyS2PXtElH8xmI= | ||
github.com/gotify/plugin-api v1.0.0/go.mod h1:xZfEyqVK/Zvu3RwA/CtpuiwFmzFDxifrrqMaH9BHnyU= | ||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= | ||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= | ||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= | ||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= | ||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4= | ||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | ||
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb h1:1w588/yEchbPNpa9sEvOcMZYbWHedwJjg4VOAdDHWHk= | ||
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= | ||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= | ||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= | ||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | ||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"errors" | ||
"fmt" | ||
"github.com/emersion/go-smtp" | ||
"github.com/gotify/plugin-api" | ||
"io" | ||
"io/ioutil" | ||
"mime" | ||
"mime/multipart" | ||
"mime/quotedprintable" | ||
"net/mail" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
var ( | ||
s *smtp.Server | ||
users = make(map[string]*Plugin) | ||
userLock = &sync.RWMutex{} | ||
) | ||
|
||
// GetGotifyPluginInfo returns gotify plugin info | ||
func GetGotifyPluginInfo() plugin.Info { | ||
return plugin.Info{ | ||
Name: "SMTP", | ||
ModulePath: "github.com/tystuyfzand/gotify-smtp", | ||
Author: "Tyler Stuyfzand", | ||
Website: "https://meow.tf", | ||
} | ||
} | ||
|
||
// startServer sets up the SMTP server. | ||
// This is only called once, and uses usernames to authenticate to different users. | ||
func startServer() { | ||
s = smtp.NewServer(&Backend{}) | ||
|
||
s.Addr = ":1025" | ||
s.Domain = "0.0.0.0" | ||
s.ReadTimeout = 10 * time.Second | ||
s.WriteTimeout = 10 * time.Second | ||
s.MaxMessageBytes = 1024 * 1024 | ||
s.MaxRecipients = 50 | ||
s.AllowInsecureAuth = true | ||
|
||
go s.ListenAndServe() | ||
} | ||
|
||
// Plugin is plugin instance | ||
type Plugin struct { | ||
userCtx plugin.UserContext | ||
msgHandler plugin.MessageHandler | ||
} | ||
|
||
// SetMessageHandler implements plugin.Messenger | ||
// Invoked during initialization | ||
func (c *Plugin) SetMessageHandler(h plugin.MessageHandler) { | ||
c.msgHandler = h | ||
} | ||
|
||
// Enable adds users to the context map which maps to a Plugin. | ||
func (c *Plugin) Enable() error { | ||
userLock.Lock() | ||
users[c.userCtx.Name] = c | ||
userLock.Unlock() | ||
return nil | ||
} | ||
|
||
// Disable removes users from the context map. | ||
func (c *Plugin) Disable() error { | ||
userLock.Lock() | ||
delete(users, c.userCtx.Name) | ||
userLock.Unlock() | ||
return nil | ||
} | ||
|
||
// NewGotifyPluginInstance creates a plugin instance for a user context. | ||
func NewGotifyPluginInstance(ctx plugin.UserContext) plugin.Plugin { | ||
if s == nil { | ||
startServer() | ||
} | ||
|
||
return &Plugin{userCtx: ctx} | ||
} | ||
|
||
// The Backend implements SMTP server methods. | ||
type Backend struct { | ||
} | ||
|
||
// Login handles a login command with username and password. | ||
func (bkd *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) { | ||
userLock.RLock() | ||
defer userLock.RUnlock() | ||
|
||
if instance, ok := users[username]; ok { | ||
return &Session{instance}, nil | ||
} | ||
|
||
return nil, errors.New("user not found") | ||
} | ||
|
||
// AnonymousLogin requires clients to authenticate using SMTP AUTH before sending emails | ||
func (bkd *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { | ||
return nil, smtp.ErrAuthRequired | ||
} | ||
|
||
type Session struct { | ||
c *Plugin | ||
} | ||
|
||
func (s *Session) Mail(from string, opts smtp.MailOptions) error { | ||
return nil | ||
} | ||
|
||
func (s *Session) Rcpt(to string) error { | ||
return nil | ||
} | ||
|
||
func (s *Session) Data(r io.Reader) error { | ||
if m, err := mail.ReadMessage(r); err != nil { | ||
return err | ||
} else { | ||
var subject string | ||
|
||
if subjectHeader, ok := m.Header["Subject"]; ok && len(subjectHeader) > 0 { | ||
subject = subjectHeader[0] | ||
} | ||
|
||
mediaType, params, err := mime.ParseMediaType(m.Header.Get("Content-Type")) | ||
|
||
var message string | ||
|
||
if err == nil && strings.HasPrefix(mediaType, "multipart/") { | ||
message = ParsePart(m.Body, params["boundary"]) | ||
} else { | ||
b, err := ioutil.ReadAll(m.Body) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
message = string(b) | ||
} | ||
|
||
if s.c != nil && s.c.msgHandler != nil { | ||
s.c.msgHandler.SendMessage(plugin.Message{ | ||
Title: subject, | ||
Message: message, | ||
}) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *Session) Reset() { | ||
|
||
} | ||
|
||
func (s *Session) Logout() error { | ||
return nil | ||
} | ||
|
||
|
||
// ParsePart will find the first text/plain part from a multipart body. | ||
// Adapted from https://github.com/kirabou/parseMIMEemail.go | ||
func ParsePart(body io.Reader, boundary string) string { | ||
reader := multipart.NewReader(body, boundary) | ||
|
||
if reader == nil { | ||
return "" | ||
} | ||
|
||
// Go through each of the MIME part of the message Body with NextPart(), | ||
for { | ||
part, err := reader.NextPart() | ||
|
||
if err == io.EOF { | ||
break | ||
} | ||
|
||
if err != nil { | ||
fmt.Println("Error going through the MIME parts -", err) | ||
break | ||
} | ||
|
||
mediaType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) | ||
|
||
if err == nil && strings.HasPrefix(mediaType, "multipart/") { | ||
// This is a new multipart to be handled recursively | ||
str := ParsePart(part, params["boundary"]) | ||
|
||
if str != "" { | ||
return str | ||
} | ||
} else { | ||
if strings.HasPrefix(mediaType, "text/plain") { | ||
b, err := ioutil.ReadAll(part) | ||
|
||
if err != nil { | ||
continue | ||
} | ||
|
||
encoding := strings.ToLower(part.Header.Get("Content-Transfer-Encoding")) | ||
|
||
switch { | ||
case strings.Compare(encoding, "base64") == 0: | ||
b, err = base64.StdEncoding.DecodeString(string(b)) | ||
if err != nil { | ||
continue | ||
} | ||
|
||
case strings.Compare(encoding, "quoted-printable") == 0: | ||
b, err = ioutil.ReadAll(quotedprintable.NewReader(bytes.NewReader(b))) | ||
if err != nil { | ||
continue | ||
} | ||
} | ||
|
||
return string(b) | ||
} | ||
} | ||
} | ||
|
||
return "" | ||
} | ||
|
||
func main() { | ||
panic("Program must be compiled as a Go plugin") | ||
} |