list

server

This is the main dovel repository, it has the Go code to run dovel SMTP server.

curl -O https://dovel.email/server.tar.gz tar.gz

2b51c98

Author: brian (git@myr.sh)

Date: Sun Dec 31 15:58:26 2023 -0300

Parent: 1a87fa1

Updated vault package

* Updated smtp server package

Diff

TODO.txt

diff --git a/TODO.txt b/TODO.txt
index 11e2cd8..9bf7f49 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -5,6 +5,6 @@
 ✓ Support PGP
 ✓ Use XDG desktop for config path
 ✓ Support SMTP for sending email
+✓ Change PGP to external package
 ☐ Add denylist filtering
 ☐ Improve documentation
-☐ Change PGP to external package

backend.go

diff --git a/backend.go b/backend.go
index 73741d2..b9a80c1 100644
--- a/backend.go
+++ b/backend.go
@@ -18,14 +18,14 @@ import (
 
 	"github.com/emersion/go-mbox"
 	"github.com/emersion/go-msgauth/dkim"
-	"github.com/emersion/go-openpgp-wkd"
+	wkd "github.com/emersion/go-openpgp-wkd"
 	"github.com/emersion/go-smtp"
 	"golang.org/x/crypto/openpgp"
 )
 
 // A Session is returned after EHLO.
 type Session struct {
-	user string
+	user User
 	from string
 	tos  []string
 }
@@ -37,7 +37,7 @@ func (s *Session) AuthPlain(username, password string) error {
 		return fmt.Errorf("user not authorized")
 	}
 	slog.Debug("authorized", "user", username)
-	s.user = username
+	s.user = v.GetUser(username)
 	return nil
 }
 
@@ -47,7 +47,7 @@ func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
 	return nil
 }
 
-func (s *Session) Rcpt(to string) error {
+func (s *Session) Rcpt(to string, opts *smtp.RcptOptions) error {
 	slog.Debug("received rcpt to", "to", to)
 	s.tos = append(s.tos, to)
 	return nil
@@ -83,7 +83,7 @@ func (s *Session) Data(raw io.Reader) error {
 	dom := strings.Split(s.from, "@")[1]
 	if dom == cfg.Domain {
 		// check auth
-		if s.user == "" {
+		if s.user.Name == "" {
 			return smtp.ErrAuthRequired
 		}
 		err = s.Send(s.from, tos, strings.NewReader(string(cont)))
@@ -180,7 +180,6 @@ func (s *Session) Send(from string, tos []*mail.Address, raw io.Reader) error {
 			return err
 		}
 
-		// TODO: use external package
 		slog.Debug("checking wkd key")
 		key, _ := wkd.Discover(to.Address)
 		if key != nil {
@@ -210,7 +209,7 @@ func (s *Session) Send(from string, tos []*mail.Address, raw io.Reader) error {
 		// dkim
 		slog.Debug("dkim check")
 		res := bytes.Buffer{}
-		if keyPath := v.GetUser(s.user).PrivateKey; keyPath != "" {
+		if keyPath := s.user.PrivateKey; keyPath != "" {
 			slog.Debug("user has dkim key")
 
 			keyData, err := os.ReadFile(keyPath)

go.mod

diff --git a/go.mod b/go.mod
index c31be00..41811e6 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,10 @@ module dovel.email/server
 go 1.19
 
 require (
-	git.derelict.garden/bryon/vault v0.3.0
 	github.com/emersion/go-mbox v1.0.3
 	github.com/emersion/go-msgauth v0.6.6
 	github.com/emersion/go-openpgp-wkd v0.0.0-20200304100729-bbe7fac61be2
-	github.com/emersion/go-smtp v0.16.0
+	github.com/emersion/go-smtp v0.20.0
 	golang.org/x/crypto v0.7.0
 )
 

go.sum

diff --git a/go.sum b/go.sum
index da7ec0f..b32889c 100644
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,8 @@ github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8b
 github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
 github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
 github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
+github.com/emersion/go-smtp v0.20.0 h1:eTD2iPzYKRrFF+NDHPwKgsBt5ZmB2jO6P2rP2LlVYWI=
+github.com/emersion/go-smtp v0.20.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
 github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
 github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
 github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=

main.go

diff --git a/main.go b/main.go
index 7490266..e57d5a4 100644
--- a/main.go
+++ b/main.go
@@ -8,14 +8,13 @@ import (
 	"path"
 	"time"
 
-	"git.derelict.garden/bryon/vault"
 	"github.com/emersion/go-smtp"
 )
 
 var (
 	cfg        = Config{}
 	configPath string
-	v          vault.Vault[User]
+	v          store
 )
 
 func main() {
@@ -42,15 +41,6 @@ func main() {
 	json.NewDecoder(configFile).Decode(&cfg)
 	slog.Debug("config loaded", "config", cfg)
 
-	if cfg.VaultFile != "" {
-		slog.Debug("creating vault", "path", cfg.VaultFile)
-		v, err = vault.NewJSONPlainTextVault[User](cfg.VaultFile)
-		if err != nil {
-			panic(err)
-		}
-		slog.Debug("vault created", "vault", v)
-	}
-
 	tlsCfg := &tls.Config{}
 	if cfg.Certificate != "" {
 		slog.Debug("loading certs", "cert", cfg.Certificate, "key", cfg.PrivateKey)
@@ -72,10 +62,10 @@ func main() {
 
 	if len(tlsCfg.Certificates) > 0 {
 		s.TLSConfig = tlsCfg
-		slog.Debug("starting server", "tls", tlsCfg)
+		slog.Info("server started", "tls", tlsCfg, "port", cfg.Port)
 		err = s.ListenAndServeTLS()
 	} else {
-		slog.Debug("starting server")
+		slog.Info("server started", "port", cfg.Port)
 		err = s.ListenAndServe()
 	}
 	if err != nil {

model.go

diff --git a/model.go b/model.go
index 56c314b..6bb2bc3 100644
--- a/model.go
+++ b/model.go
@@ -11,11 +11,16 @@ package main
 type Config struct {
 	Port        string
 	Domain      string
-	VaultFile   string
 	Certificate string
 	PrivateKey  string
 }
 
+// Vault interface gives validation and user fetching.
+type Vault interface {
+	Validate(n, p string) bool
+	GetUser(n string) User
+}
+
 // User represents a user that should be able to send emails. This struct is
 // found in the users json file, that is on the path pointed by VaultFile field
 // in [Config]. PrivateKey is the path to a private key for the DKIM signature.
@@ -26,10 +31,20 @@ type User struct {
 	PrivateKey string
 }
 
-func (w User) Login() string {
-	return w.Name
+type store struct {
+	users []User
+}
+
+func (s store) Validate(n, p string) bool {
+	u := s.GetUser(n)
+	return u.Password == p
 }
 
-func (w User) Pass() string {
-	return w.Password
+func (s store) GetUser(n string) User {
+	for _, u := range s.users {
+		if u.Name == n {
+			return u
+		}
+	}
+	return User{}
 }