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

abf55a1

Author: blmayer (bleemayer@gmail.com)

Date: Fri Sep 8 19:50:01 2023 -0300

Parent: d4e60c7

Organized subpackages

Diff

cmd/dovel/backend.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/cmd/dovel/backend.go b/cmd/dovel/backend.go
index be441ac..c5f9949 100644
--- a/cmd/dovel/backend.go
+++ b/cmd/dovel/backend.go
@@ -15,7 +15,7 @@ import (
 	"strings"
 	"time"
 
-	"git.derelict.garden/dovel/email/util/wkd"
+	"git.derelict.garden/dovel/email/wkd"
 	"github.com/ProtonMail/gopenpgp/v2/helper"
 	"github.com/emersion/go-mbox"
 	"github.com/emersion/go-msgauth/dkim"
@@ -188,7 +188,6 @@ func (s *Session) Send(from string, tos []string, raw io.Reader) error {
 		if err != nil {
 			return err
 		}
-
 	}
 	return nil
 }

cmd/dovel/main.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/cmd/dovel/main.go b/cmd/dovel/main.go
index 6a95bf6..76c9df4 100644
--- a/cmd/dovel/main.go
+++ b/cmd/dovel/main.go
@@ -14,7 +14,7 @@ import (
 var (
 	domain string
 	configPath string
-	v vault.Vault[email.WebUser]
+	v vault.Vault[email.User]
 )
 
 func main() {
@@ -33,7 +33,7 @@ func main() {
 	cfg := email.Config{}
 	json.NewDecoder(configFile).Decode(&cfg)
 
-	v, err = vault.NewJSONPlainTextVault[email.WebUser](cfg.VaultFile)
+	v, err = vault.NewJSONPlainTextVault[email.User](cfg.VaultFile)
 	if err != nil {
 		panic(err)
 	}
@@ -44,8 +44,7 @@ func main() {
 	s.ReadTimeout = 10 * time.Second
 	s.WriteTimeout = 10 * time.Second
 	s.MaxMessageBytes = 1024 * 1024
-	s.MaxRecipients = 2
-	s.AllowInsecureAuth = true
+	s.MaxRecipients = 5
 	
 	if err := s.ListenAndServe(); err != nil {
 		panic(err)

doc.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/doc.go b/doc.go
index c71b848..da7e951 100644
--- a/doc.go
+++ b/doc.go
@@ -1,9 +1,8 @@
-// email is a SMTP server and web interface that is simple to setup and
-// use.
+// email is a SMTP server that is simple to setup and use.
 //
 // This package has two parts:
 //  1. The dovel binary
-//  2. The interfaces library
+//  2. The library
 //
 // # The binary
 //
@@ -18,9 +17,8 @@
 //
 // # The library
 //
-// This package also delivers a subpackage called interfaces, it contains
-// structs that are used through this project. Those are also used in templates
-// and you can use them in other projects.
+// This package also delivers a subpackage called email, it contains
+// structs that are used through this project.
 //
 // # Setting up your email server
 //
@@ -39,19 +37,10 @@
 // [git.derelict.garden/dovel/email/config.Config].
 //
 // The server itself only receives email, for this feature only the MX record
-// is enough. There are 2 handlers included in this project:
-//
-//   - gwi: to be used with the gwi project, saves email by subject, ish.
-//   - file: closer to the standard, saves email using to and subject fields.
-//
-// The "file" handler gives you multiple inboxes, so you can separate by giving
-// different addresses, for example, email1@my.domain and email2@my.domain,
-// will have different inboxes. Inside each inbox emails are separated by
-// subject.
-//
-// The "gwi" handler is intended for mailing lists, or issues on git
-// repositories, email is added to the open folder, and then separated by
-// subject. See the gwi project for more information.
+// is enough. Everytime dovel receives an email it searches for an executable
+// file in $XDG_CONFIG_DIR/dovel/ named receive-DOMAIN, for the DOMAIN the is
+// receiving the email and executes it passing the email in mbox format as
+// standard input.
 //
 // # Configuring DNS records
 //
@@ -59,4 +48,9 @@
 // sending email is more complicated, some receiving servers only need the SPF
 // and PTR records, some also need DKIM and DMARK, adjust according to your
 // needs.
+//
+// # Sending email
+//
+// Dovel also sends email, for that it needs a valid VaultFile path on the
+// config. That means you need at least one user descrived on the json file.
 package email

model.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/model.go b/model.go
index dd0b2d7..94d528a 100644
--- a/model.go
+++ b/model.go
@@ -1,132 +1,36 @@
-// package dovel contains interfaces that are passed to the common
+// package email contains interfaces that are passed to the common
 // functions on the server and web projects. You can import this to use
 // on templates and handlers.
 package email
 
 import (
 	"crypto"
-	"encoding/base64"
-	"fmt"
-	"io"
-	"time"
-
-	"github.com/go-git/go-git/v5/plumbing"
-	"github.com/go-git/go-git/v5/plumbing/object"
-
-	"github.com/OfimaticSRL/parsemail"
 )
 
 // 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.
 // Domain is what the server should respond in a HELO or EHLO request.
-// Handlers is a list of domains that are allowed to send/receive emails.
+// VaultFile is the path to a json file containing the list of users
+// that can send email.
 type Config struct {
 	Port      string
 	Domain    string
 	VaultFile string
 }
 
-type tz struct {
-	Name   string
-	Offset int
-}
-
-type WebUser struct {
+type User struct {
 	Name       string
 	Email      string
 	Password   string
-	TimeZone   tz
 	PrivateKey crypto.Signer
 }
 
-func (w WebUser) Login() string {
+func (w User) Login() string {
 	return w.Name
 }
 
-func (w WebUser) Pass() string {
+func (w User) Pass() string {
 	return w.Password
 }
 
-type File struct {
-	*object.File
-	Size int64
-}
-
-type Info struct {
-	User    string
-	Repo    string
-	Ref     plumbing.Hash
-	RefName string
-	Args    string
-}
-
-type Mailbox struct {
-	Title   string
-	LastMod time.Time
-	Length  int
-}
-
-type Attachment struct {
-	Name        string
-	ContentType string
-	Data        []byte
-}
-
-type Opt struct {
-	Encrypt bool
-	Key     string
-}
-
-type Email struct {
-	Headers     map[string][]string
-	ID          string
-	From        string
-	To          []string
-	Cc          []string
-	BCc         []string
-	Date        time.Time
-	Subject     string
-	Body        string
-	Attachments map[string]Attachment
-	Raw         []byte
-}
-
-func ToEmail(mail parsemail.Email) Email {
-	m := Email{
-		Headers:     mail.Header,
-		From:        mail.From[0].Address,
-		To:          []string{},
-		Cc:          []string{},
-		Subject:     mail.Subject,
-		ID:          mail.MessageID,
-		Date:        mail.Date,
-		Body:        mail.TextBody,
-		Attachments: map[string]Attachment{},
-	}
-	for _, to := range mail.To {
-		m.To = append(m.To, to.Address)
-	}
-	for _, cc := range mail.Cc {
-		m.Cc = append(m.Cc, cc.Address)
-	}
-	for _, a := range mail.Attachments {
-		content, err := io.ReadAll(a.Data)
-		if err != nil {
-			fmt.Println("read attachment", err.Error())
-			continue
-		}
-
-		encContent := base64.StdEncoding.EncodeToString(content)
-		m.Attachments[a.Filename] = Attachment{
-			Name:        a.Filename,
-			ContentType: a.ContentType,
-			Data:        []byte(encContent),
-		}
-	}
-	return m
-}
-
-type Mailer interface {
-	Save(from, to string, raw io.Reader) error
-}

util/decode/multipart.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/util/decode/multipart.go b/util/decode/multipart.go
deleted file mode 100644
index 2df6798..0000000
--- a/util/decode/multipart.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package util
-
-import "strings"
-
-func Decode(header, content string) (string, map[string]string) {
-	if strings.Contains(header, "multipart") {
-		boundary := strings.Split(header, `"`)[1]
-	}
-
-
-}

util/multipart.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/util/multipart.go b/util/multipart.go
deleted file mode 100644
index 698bb27..0000000
--- a/util/multipart.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package util
-
-import (
-	"encoding/base64"
-	"io"
-	"mime"
-	"mime/multipart"
-	"strings"
-
-	"git.derelict.garden/dovel/email/model"
-)
-
-func Decode(header, content string) (string, map[string]model.Attachment, error) {
-	mediaType, params, err := mime.ParseMediaType(header)
-	if err != nil {
-		return content, nil, err
-	}
-	if !strings.HasPrefix(mediaType, "multipart/") {
-		return content, nil, nil
-	}
-
-	var text string
-	attach := map[string]model.Attachment{}
-	r := multipart.NewReader(strings.NewReader(content), params["boundary"])
-	for {
-		p, err := r.NextPart()
-		if err == io.EOF {
-			break
-		}
-		if err != nil {
-			return text, attach, err
-		}
-		cont, err := io.ReadAll(p)
-		if err != nil {
-			return text, attach, err
-		}
-		
-		if p.Header.Get("Content-Transfer-Encoding") == "base64" {
-			cont, err = base64.RawStdEncoding.DecodeString(
-				string(cont),
-			)
-			if err != nil {
-				println("base64", err.Error())
-			}
-		}
-
-		cType := p.Header.Get("Content-Type")
-		if fileName := p.FileName(); fileName != "" { 
-			attach[fileName] = model.Attachment{
-				Name: fileName,
-				Data: cont,
-				ContentType: cType,
-			}
-			continue
-		}
-		if strings.HasPrefix(cType, "text/plain") {
-			text = string(cont)
-		}
-	}
-	return text, attach, nil
-}

util/multipart_test.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/util/multipart_test.go b/util/multipart_test.go
deleted file mode 100644
index 2ea92f0..0000000
--- a/util/multipart_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package util
-
-import (
-	"testing"
-)
-
-var header = `multipart/alternative; boundary="000000000000b05aa10600f3345f"`
-var body = `--000000000000b05aa10600f3345f
-Content-Type: text/plain; charset="UTF-8"
-
-sdfv qergwergwergvd fvds fvv
-
---000000000000b05aa10600f3345f
-Content-Type: text/html; charset="UTF-8"
-
-<div dir="ltr"><div class="gmail_default" style="font-family:arial,sans-serif;font-size:small;color:#000000">sdfv qergwergwergvd fvds fvv<br></div></div>
-
---000000000000b05aa10600f3345f--
-`
-
-func TestDecode(t *testing.T) {
-	text, attach, err := Decode(header, body)
-	if err != nil {
-		t.Error(err)
-	}
-	if text == "" {
-		t.Error("text is empty")
-	}
-	t.Log(text)
-	t.Log(attach)
-}
-

util/parse_test.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/util/parse_test.go b/util/parse_test.go
deleted file mode 100644
index 59d917d..0000000
--- a/util/parse_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package util
-
-import (
-	"testing"
-
-	"git.derelict.garden/dovel/email/model"
-	"git.derelict.garden/dovel/email/model/file"
-)
-
-func TestParseMail(t *testing.T) {
-	h, err := file.NewFileHandler(model.CommonConfig{}, nil, nil)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	mail, err := h.Mail(
-		"mail/test/attachment/base64.txt",
-	)
-	if err != nil {
-		t.Error(err)
-	}
-	if len(mail.Attachments) == 0 {
-		t.Error("attachment not parsed")
-	}
-
-	t.Log(mail)
-}

wkd/wkd.go

commit abf55a1d39e734f0bbf834e7055096bf411436e2
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 19:50:01 2023 -0300

    Organized subpackages

diff --git a/wkd/wkd.go b/wkd/wkd.go
new file mode 100644
index 0000000..1a86698
--- /dev/null
+++ b/wkd/wkd.go
@@ -0,0 +1,33 @@
+package wkd
+
+import (
+	"crypto/sha1"
+	"fmt"
+	"net/http"
+
+	pgp "github.com/ProtonMail/gopenpgp/v2/crypto"
+	"github.com/tv42/zbase32"
+)
+
+const suffix = "/.well-known/openpgpkey/hu/"
+
+func FetchPGPKey(local, domain string) (string, error) {
+	s := sha1.Sum([]byte(local))
+	slug := zbase32.EncodeToString(s[:])
+	res, err := http.Get("https://" + domain + suffix + slug)
+	if err != nil {
+		return "", err
+	}
+
+	if res.StatusCode > 399 {
+		return "", fmt.Errorf("key not found")
+	}
+	defer res.Body.Close()
+
+	key, err := pgp.NewKeyFromReader(res.Body)
+	if err != nil {
+		return "", err
+	}
+
+	return key.GetArmoredPublicKey()
+}