list

server

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

curl https://dovel.email/server.tar tar

8b90660

Author: blmayer (bleemayer@gmail.com)

Date: Fri Sep 15 22:01:05 2023 -0300

Parent: eca63cc

Fixed dkim sign

* Added slogs
* Added -O flag to scp

Diff

Makefile

commit 8b90660c528793baf7209ac3e95d9bf401d493f3
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 15 22:01:05 2023 -0300

    Fixed dkim sign
    
    * Added slogs
    * Added -O flag to scp

diff --git a/Makefile b/Makefile
index 44854dd..63b4825 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,9 @@
-.PHONY: clean deploy deploy-site deploy-assets deploy-pages
+.PHONY: deploy
 
 dovel: $(wildcard cmd/dovel/*.go *.go util/*/*.go)
 	GOARCH=arm GOARM=6 go build -o $@ cmd/dovel/*.go
 
 deploy: dovel
-	scp $^ zero:
+	scp -O $^ zero:
 	ssh zero "mv $^ .local/bin/"
 
-deploy-site: index.html www/assets/favicon.ico www/assets/logo.png
-	scp $^ zero:dovel.email/
-
-deploy-assets: $(wildcard www/assets/*)
-	scp $^ zero:blmayer.dev/www/mail/assets/
-
-deploy-pages: $(wildcard www/*.*)
-	scp $^ zero:blmayer.dev/www/mail
-
-deploy-certs: $(wildcard *.pem)
-	scp $^ zero:certs/dovel.email/

cmd/dovel/backend.go

commit 8b90660c528793baf7209ac3e95d9bf401d493f3
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 15 22:01:05 2023 -0300

    Fixed dkim sign
    
    * Added slogs
    * Added -O flag to scp

diff --git a/cmd/dovel/backend.go b/cmd/dovel/backend.go
index 061c6da..754a898 100644
--- a/cmd/dovel/backend.go
+++ b/cmd/dovel/backend.go
@@ -3,8 +3,12 @@ package main
 import (
 	"bufio"
 	"bytes"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
 	"fmt"
 	"io"
+	"log/slog"
 	"net"
 	"net/mail"
 	"net/textproto"
@@ -29,21 +33,24 @@ type Session struct {
 }
 
 func (s *Session) AuthPlain(username, password string) error {
+	slog.Debug("authenticating", "user", username, "pass", password)
 	if !v.Validate(username, password) {
+		slog.Warn(username, "not authorized")
 		return fmt.Errorf("user not authorized")
 	}
+	slog.Debug("authorized", "user", username)
 	s.user = username
 	return nil
 }
 
 func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
-	println("Mail from:", from)
+	slog.Debug("received mail", "from", from)
 	s.from = from
 	return nil
 }
 
 func (s *Session) Rcpt(to string) error {
-	println("Rcpt to:", to)
+	slog.Debug("received rcpt to", "to", to)
 	s.tos = append(s.tos, to)
 	return nil
 }
@@ -53,6 +60,7 @@ func (s *Session) Data(raw io.Reader) error {
 	if err != nil {
 		return err
 	}
+	slog.Debug("received data", "data", cont)
 
 	w := bytes.Buffer{}
 	box := mbox.NewWriter(&w)
@@ -70,20 +78,24 @@ func (s *Session) Data(raw io.Reader) error {
 
 	// sending email
 	dom := strings.Split(s.from, "@")[1]
-	if dom == domain {
+	if dom == cfg.Domain {
+		// check auth
+		if s.user == "" {
+			return smtp.ErrAuthRequired
+		}
 		err = s.Send(s.from, s.tos, strings.NewReader(string(cont)))
 		if err != nil {
-			println("send", err.Error())
+			slog.Error("send error", "msg", err.Error())
 			return err
 		}
 
 		// running handlers is optional
-		h := path.Join(configPath, "hooks", "send-"+domain)
+		h := path.Join(configPath, "hooks", "send-"+cfg.Domain)
 		if f, err := os.Lstat(h); err == nil {
 			if !f.Mode().IsRegular() {
 				h, err = os.Readlink(h)
 				if err != nil {
-					println(domain, "read link", err.Error())
+					slog.Error(cfg.Domain, "read link", err.Error())
 				}
 			}
 
@@ -91,7 +103,7 @@ func (s *Session) Data(raw io.Reader) error {
 			c.Stdin = strings.NewReader(string(mess))
 			c.Stdout = os.Stdout
 			if err := c.Run(); err != nil {
-				println("run script", err.Error())
+				slog.Error("run script", err.Error())
 				return err
 			}
 		}
@@ -101,19 +113,19 @@ func (s *Session) Data(raw io.Reader) error {
 	for _, to := range s.tos {
 		toAddr, err := mail.ParseAddress(to)
 		if err != nil {
-			println("parse to", err.Error())
+			slog.Error("parse to", err.Error())
 			return err
 		}
 		domain := strings.Split(toAddr.Address, "@")[1]
 
 		h := path.Join(configPath, "hooks", "receive-"+domain)
 		if f, err := os.Lstat(h); err != nil {
-			println(domain, "receive error:", err.Error())
+			slog.Error(domain, "receive error:", err.Error())
 			continue
 		} else if !f.Mode().IsRegular() {
 			h, err = os.Readlink(h)
 			if err != nil {
-				println(domain, "read link", err.Error())
+				slog.Error(domain, "read link", err.Error())
 			}
 		}
 
@@ -121,7 +133,7 @@ func (s *Session) Data(raw io.Reader) error {
 		c.Stdin = strings.NewReader(string(mess))
 		c.Stdout = os.Stdout
 		if err := c.Run(); err != nil {
-			println("run script", err.Error())
+			slog.Error("run script", err.Error())
 			continue
 		}
 	}
@@ -132,7 +144,7 @@ func (s *Session) Data(raw io.Reader) error {
 func (s *Session) Reset() {}
 
 func (s *Session) Logout() error {
-	println("logged out")
+	slog.Debug("logged out", "user", s.user)
 	return nil
 }
 
@@ -154,6 +166,7 @@ func (s *Session) Send(from string, tos []string, raw io.Reader) error {
 		return err
 	}
 	for _, to := range tos {
+		slog.Debug("sending email", "to", to)
 		msg := string(body)
 
 		// dns mx for email
@@ -166,13 +179,16 @@ func (s *Session) Send(from string, tos []string, raw io.Reader) error {
 			return err
 		}
 
+		// TODO: use package from emersion
+		slog.Debug("checking wkd key")
 		key, _ := wkd.FetchPGPKey(addr[0], addr[1])
 		if key != "" {
-			email.Header["Content-Type"] = []string{"application/pgp-encrypted"}
+			slog.Debug("found wkd key")
 			msg, err = helper.EncryptMessageArmored(key, msg)
 			if err != nil {
 				return err
 			}
+			email.Header["Content-Type"] = []string{"application/pgp-encrypted"}
 		}
 
 		payload := bytes.Buffer{}
@@ -184,22 +200,37 @@ func (s *Session) Send(from string, tos []string, raw io.Reader) error {
 		payload.Write([]byte(msg))
 
 		// dkim
+		slog.Debug("dkim check")
 		res := bytes.Buffer{}
-		if sig := v.GetUser(s.user).PrivateKey; sig != nil {
+		if keyPath := v.GetUser(s.user).PrivateKey; keyPath != "" {
+			slog.Debug("user has dkim key")
+
+			keyData, err := os.ReadFile(keyPath)
+			if err != nil {
+				return err
+			}
+			block, _ := pem.Decode(keyData)
+			privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+			if err != nil {
+				return(err)
+			}
 			options := &dkim.SignOptions{
 				Domain:   fromdom[1],
 				Selector: "dkim",
-				Signer:   v.GetUser(s.user).PrivateKey,
+				Signer:   privateKey.(*rsa.PrivateKey),
 			}
+
 			err = dkim.Sign(&res, &payload, options)
 			if err != nil {
-				println("failed to sign body:", err.Error())
+				slog.Error("failed to sign body", "err", err)
 			}
 		} else {
+			slog.Debug("no dkim key")
 			io.Copy(&res, &payload)
 		}
 
 		server := mxs[0].Host + ":smtp"
+		slog.Debug("sending", "host", server, "from", from, "to", tos)
 		err = smtp.SendMail(
 			server,
 			nil,
@@ -211,6 +242,8 @@ func (s *Session) Send(from string, tos []string, raw io.Reader) error {
 			return err
 		}
 	}
+
+	slog.Debug("email sent")
 	return nil
 }
 

cmd/dovel/main.go

commit 8b90660c528793baf7209ac3e95d9bf401d493f3
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 15 22:01:05 2023 -0300

    Fixed dkim sign
    
    * Added slogs
    * Added -O flag to scp

diff --git a/cmd/dovel/main.go b/cmd/dovel/main.go
index b99bb50..26ac9c5 100644
--- a/cmd/dovel/main.go
+++ b/cmd/dovel/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"crypto/tls"
 	"encoding/json"
+	"log/slog"
 	"os"
 	"path"
 	"time"
@@ -13,16 +14,24 @@ import (
 )
 
 var (
-	domain     string
+	cfg = email.Config{}
 	configPath string
 	v          vault.Vault[email.User]
 )
 
 func main() {
+	if os.Getenv("DEBUG") != "" {
+		hand := slog.NewTextHandler(
+			os.Stdout,
+			&slog.HandlerOptions{Level: slog.LevelDebug},
+		)
+		slog.SetDefault(slog.New(hand))
+	}
+
 	var err error
 	configPath, err = os.UserConfigDir()
 	if err != nil {
-		println(err, "using ~/.config/dovel/config.json")
+		slog.Error(err.Error(), "using ~/.config/dovel/config.json")
 		configPath = "~/.config"
 	}
 	configPath = path.Join(configPath, "dovel")
@@ -31,19 +40,21 @@ func main() {
 		panic(err)
 	}
 
-	cfg := email.Config{}
 	json.NewDecoder(configFile).Decode(&cfg)
-	domain = cfg.Domain
+	slog.Debug("config loaded", "config", cfg)
 
 	if cfg.VaultFile != "" {
+		slog.Debug("creating vault", "path", cfg.VaultFile)
 		v, err = vault.NewJSONPlainTextVault[email.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)
 		c, err := tls.LoadX509KeyPair(cfg.Certificate, cfg.PrivateKey)
 		if err != nil {
 			panic(err)
@@ -58,11 +69,14 @@ func main() {
 	s.WriteTimeout = 10 * time.Second
 	s.MaxMessageBytes = 1024 * 1024
 	s.MaxRecipients = 5
+	s.AllowInsecureAuth = true
 
 	if len(tlsCfg.Certificates) > 0 {
 		s.TLSConfig = tlsCfg
+		slog.Debug("starting server", "tls", tlsCfg)
 		err = s.ListenAndServeTLS()
 	} else {
+		slog.Debug("starting server")
 		err = s.ListenAndServe()
 	}
 	if err != nil {

model.go

commit 8b90660c528793baf7209ac3e95d9bf401d493f3
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 15 22:01:05 2023 -0300

    Fixed dkim sign
    
    * Added slogs
    * Added -O flag to scp

diff --git a/model.go b/model.go
index 5b0b3c5..f872591 100644
--- a/model.go
+++ b/model.go
@@ -1,9 +1,5 @@
 package email
 
-import (
-	"crypto"
-)
-
 // Config is used to configure your email server.
 // Port is used to specify which port the server should listen
 // to connections, a typicall value is 2525.
@@ -11,7 +7,7 @@ import (
 // VaultFile is the path to a json file containing the list of users
 // that can send email.
 // In order to use TLS connections Certficate and PrivateKey fields
-	// must have valid path to pem encoded keys.
+// must have valid path to pem encoded keys.
 type Config struct {
 	Port        string
 	Domain      string
@@ -27,7 +23,7 @@ type User struct {
 	Name       string
 	Email      string
 	Password   string
-	PrivateKey crypto.Signer
+	PrivateKey string
 }
 
 func (w User) Login() string {