This is the main dovel repository, it has the Go code to run dovel SMTP server.
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
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/
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 }
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 {
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 {