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

d4e60c7

Author: blmayer (bleemayer@gmail.com)

Date: Fri Sep 8 02:05:56 2023 -0300

Parent: 95eb49e

Removed handlers

Diff

Makefile

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/Makefile b/Makefile
index 4038943..44854dd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 .PHONY: clean deploy deploy-site deploy-assets deploy-pages
 
-dovel: $(wildcard cmd/dovel/*.go model/*/*.go model/*.go util/*/*.go)
+dovel: $(wildcard cmd/dovel/*.go *.go util/*/*.go)
 	GOARCH=arm GOARM=6 go build -o $@ cmd/dovel/*.go
 
 deploy: dovel

cmd/dovel/main.go

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/cmd/dovel/main.go b/cmd/dovel/main.go
index b647637..6a95bf6 100644
--- a/cmd/dovel/main.go
+++ b/cmd/dovel/main.go
@@ -7,14 +7,14 @@ import (
 	"time"
 
 	"git.derelict.garden/bryon/vault"
-	"git.derelict.garden/dovel/email/model"
+	"git.derelict.garden/dovel/email"
 	"github.com/emersion/go-smtp"
 )
 
 var (
 	domain string
 	configPath string
-	v vault.Vault[model.WebUser]
+	v vault.Vault[email.WebUser]
 )
 
 func main() {
@@ -30,10 +30,10 @@ func main() {
 		panic(err)
 	}
 
-	cfg := model.Config{}
+	cfg := email.Config{}
 	json.NewDecoder(configFile).Decode(&cfg)
 
-	v, err = vault.NewJSONPlainTextVault[model.WebUser](cfg.VaultFile)
+	v, err = vault.NewJSONPlainTextVault[email.WebUser](cfg.VaultFile)
 	if err != nil {
 		panic(err)
 	}

model.go

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/model.go b/model.go
new file mode 100644
index 0000000..dd0b2d7
--- /dev/null
+++ b/model.go
@@ -0,0 +1,132 @@
+// package dovel 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.
+type Config struct {
+	Port      string
+	Domain    string
+	VaultFile string
+}
+
+type tz struct {
+	Name   string
+	Offset int
+}
+
+type WebUser struct {
+	Name       string
+	Email      string
+	Password   string
+	TimeZone   tz
+	PrivateKey crypto.Signer
+}
+
+func (w WebUser) Login() string {
+	return w.Name
+}
+
+func (w WebUser) 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
+}

model/file/file.go

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/model/file/file.go b/model/file/file.go
deleted file mode 100644
index 755c245..0000000
--- a/model/file/file.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// file adds the common email file handling, but with a twist: here we save
-// emails first by inbox and then subject. This package also delivers some
-// functions to be used in the templates, for the web interface.
-package file
-
-import (
-	"io"
-	"io/ioutil"
-	"os"
-	"path"
-
-	"git.derelict.garden/dovel/email/model"
-
-	"blmayer.dev/x/vault"
-
-	"github.com/OfimaticSRL/parsemail"
-)
-
-// FileHandler is used to configure the file email handler.
-// Root is where mail should be saved, Templates is an optional field that
-// is the path to templates, to be used in the web server; Password is the
-// password for the x user, for now this is very simple; Domain is used to
-// filter and separate emails, only emails sent to one of your domains are
-// saved, each according to its configuration.
-type FileHandler struct {
-	root       string
-	domain     string
-	privateKey string
-	vault      vault.Vault[model.WebUser]
-}
-
-func NewFileHandler(c model.CommonConfig, v vault.Vault[model.WebUser]) (FileHandler, error) {
-	f := FileHandler{root: c.Root, vault: v, domain: c.Domain}
-	var err error
-	if c.PrivateKey != "" {
-		key, err := ioutil.ReadFile(c.PrivateKey)
-		if err != nil {
-			return f, err
-		}
-		f.privateKey = string(key)
-	}
-
-	return f, err
-}
-
-func (f FileHandler) Save(from, to string, r io.Reader) error {
-	m, err := parsemail.Parse(r)
-	if err != nil {
-		println("mail parse", err.Error())
-		return err
-	}
-
-	email := model.ToEmail(m)
-	mailDir := path.Join(f.root, email.To[0], email.Subject)
-	os.MkdirAll(mailDir, os.ModeDir|0o700)
-
-	file, err := os.Create(path.Join(mailDir, email.ID))
-	if err != nil {
-		println("file create", err.Error())
-		return err
-	}
-	defer file.Close()
-
-	_, err = file.Write(email.Raw)
-	if err != nil {
-		println("file write", err.Error())
-		return err
-	}
-
-	return nil
-}
-

model/gwi/gwi.go

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/model/gwi/gwi.go b/model/gwi/gwi.go
deleted file mode 100644
index 0373693..0000000
--- a/model/gwi/gwi.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// gwi contains the email handler for interacting with gwi.
-package gwi
-
-import (
-	"fmt"
-	"io"
-	"io/ioutil"
-	"os"
-	"path"
-	"strings"
-
-	"git.derelict.garden/dovel/email/model"
-
-	"blmayer.dev/x/vault"
-
-	"github.com/OfimaticSRL/parsemail"
-)
-
-// GWIHandler is used to configure the GWI interface for dovel. Root is the
-// same root as in GWI: the path to the repositories.
-// Commands lets you add functions that will run for each received email, the
-// key of the map specifies a trigger in the form "key!". That is, if the email
-// body starts with key! then the command key is run.
-type GWIHandler struct {
-	root       string
-	notify     bool
-	Commands   map[string]func(email model.Email) error
-	domain     string
-	privateKey string
-	vault      vault.Vault[model.WebUser]
-}
-
-func NewGWIHandler(c model.CommonConfig, vault vault.Vault[model.WebUser]) (GWIHandler, error) {
-	g := GWIHandler{root: c.Root, domain: c.Domain, vault: vault}
-	if c.PrivateKey != "" {
-		key, err := ioutil.ReadFile(c.PrivateKey)
-		if err != nil {
-			return g, err
-		}
-
-		g.privateKey = string(key)
-	}
-	return g, nil
-}
-
-// Save saves emails to the correct repo using the subject to
-// separate them. Subject field must be of form "[%s] %s".
-func (g GWIHandler) Save(from, to string, r io.Reader) error {
-	m, err := parsemail.Parse(r)
-	if err != nil {
-		println("mail parse", err.Error())
-		return err
-	}
-
-	email := model.ToEmail(m)
-	userRepoDomain := strings.Split(email.To[0], "@")
-	userRepo := strings.Split(userRepoDomain[0], "/")
-	if len(userRepo) != 2 {
-		println("received bad to", userRepo)
-		return fmt.Errorf("received bad to: %s", userRepo)
-	}
-
-	user, repo := userRepo[0], userRepo[1]
-	if _, err := os.Stat(path.Join(g.root, user, repo)); err != nil {
-		println("stat repo", err.Error())
-		return err
-	}
-
-	// split by subject
-	start := strings.Index(email.Subject, "[")
-	if start == -1 {
-		println("received bad subject", email.Subject)
-		return fmt.Errorf("Malformed subject field")
-	}
-	title := strings.TrimSpace(email.Subject[start:])
-
-	mailDir := path.Join(g.root, user, repo, "mail", title)
-	os.MkdirAll(mailDir, os.ModeDir|0o700)
-
-	mailFile, err := os.Create(path.Join(mailDir, email.ID))
-	if err != nil {
-		println("create mail file", err.Error())
-		return err
-	}
-	defer mailFile.Close()
-
-	_, err = mailFile.Write(email.Raw)
-	if err != nil {
-		println("write mail file", err.Error())
-		return err
-	}
-
-	// apply commands
-	//go func() {
-	//	println("gwi applying commands")
-	//	for com, f := range g.Commands {
-	//		println("gwi applying", com)
-	//		if !strings.HasPrefix(email.Body, "!"+com) {
-	//			continue
-	//		}
-	//		if err := f(email); err != nil {
-	//			println(com, "error", err.Error())
-	//			continue
-	//		}
-	//		println("gwi", com, "applied")
-	//	}
-	//}()
-
-	// notify owner
-	owner := g.vault.GetUser(user)
-	if owner.Name == "" {
-		return nil
-	}
-	email.To = []string{owner.Email}
-	email.Body = fmt.Sprintf(
-		`You received an email with the subject %s.
-
-Check you project by visiting https://%s/%s/%s
-
-Yours.
-
-The GWI team.`,
-		email.Subject,
-		g.domain,
-		user,
-		repo,
-	)
-	email.Subject = "New mail on project " + repo
-	email.From = fmt.Sprintf("%s/%s@%s", user, repo, g.domain)
-	return nil
-}
-

model/html/html.go

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/model/html/html.go b/model/html/html.go
deleted file mode 100644
index 0c24694..0000000
--- a/model/html/html.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// file adds the common email file handling, but with a twist: here we save
-// emails first by inbox and then subject. This package also delivers some
-// functions to be used in the templates, for the web interface.
-package html
-
-import (
-	"bufio"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"net/mail"
-	"net/textproto"
-	"os"
-	"path"
-	"sort"
-	"strings"
-	"text/template"
-	"time"
-
-	"git.derelict.garden/dovel/email/model"
-	"git.derelict.garden/dovel/email/util"
-
-	"github.com/ProtonMail/gopenpgp/v2/helper"
-
-	pgp "github.com/ProtonMail/gopenpgp/v2/crypto"
-)
-
-// HTMLHandler is used to configure the file email handler.
-// Root is where mail should be saved, Templates is an optional field that
-// is the path to templates, to be used in the web server; Password is the
-// password for the x user, for now this is very simple; Domain is used to
-// filter and separate emails, only emails sent to one of your domains are
-// saved, each according to its configuration.
-type HTMLHandler struct {
-	root       string
-	out        string
-	domain     string
-	privateKey string
-	indexTpl   *template.Template
-	listTpl    *template.Template
-	mailsTpl   *template.Template
-	mailTpl    *template.Template
-}
-
-func NewHTMLHandler(c model.HTMLConfig) (HTMLHandler, error) {
-	f := HTMLHandler{root: c.Root, domain: c.Domain, out: c.Out}
-
-	var err error
-	f.mailTpl, err = template.ParseFiles(c.MailTpl)
-	if err != nil {
-		return f, err
-	}
-	f.mailsTpl, err = template.ParseFiles(c.MailsTpl)
-	if err != nil {
-		return f, err
-	}
-	f.listTpl, err = template.ParseFiles(c.ListTpl)
-	if err != nil {
-		return f, err
-	}
-	f.indexTpl, err = template.ParseFiles(c.IndexTpl)
-	if err != nil {
-		return f, err
-	}
-
-	if c.PrivateKey != "" {
-		key, err := ioutil.ReadFile(c.PrivateKey)
-		if err != nil {
-			return f, err
-		}
-		f.privateKey = string(key)
-	}
-	return f, nil
-}
-
-func (h HTMLHandler) Save(from, to string, r io.Reader) error {
-	mail, err := mail.ReadMessage(r)
-	if err != nil {
-		return err
-	}
-
-	content, err := ioutil.ReadAll(mail.Body)
-	if err != nil {
-		println("file read", err.Error())
-		return err
-	}
-
-	// try to decrypt
-	if h.privateKey != "" && pgp.IsPGPMessage(string(content)) {
-		txt, err := helper.DecryptMessageArmored(
-			h.privateKey,
-			nil,
-			string(content),
-		)
-		if err != nil {
-			println(err)
-		}
-		content = []byte(txt)
-	}
-
-	date := time.Now()
-	if dt, err := mail.Header.Date(); err != nil {
-		date = dt
-	}
-	subj := mail.Header.Get("Subject")
-	mailDir := path.Join(h.root, to, subj)
-	os.MkdirAll(mailDir, os.ModeDir|0o700)
-	name := fmt.Sprintf("%s:%s.mail", from, date.Format(time.RFC3339))
-
-	file, err := os.Create(path.Join(mailDir, name))
-	if err != nil {
-		return err
-	}
-
-	writer := textproto.NewWriter(bufio.NewWriter(file))
-	for k, v := range mail.Header {
-		writer.PrintfLine("%s: %s", k, strings.Join(v, ", "))
-	}
-	writer.PrintfLine("")
-	file.Write(content)
-	file.Close()
-
-	payload := model.Email{
-		Headers: mail.Header,
-		Date:    date,
-		From:    from,
-		To:      mail.Header["To"],
-		Subject: subj,
-		Body:    string(content),
-	}
-	if mail.Header.Get("MIME-Version") != "" {
-		payload.Body, payload.Attachments, err = util.Decode(
-			mail.Header.Get("Content-Type"),
-			string(content),
-		)
-		if err != nil {
-			println("decode", err.Error())
-		}
-	}
-
-	htmlDir := path.Join(h.out, to, subj)
-	os.MkdirAll(htmlDir, os.ModeDir|0o700)
-	htmlName := fmt.Sprintf("%s:%s.html", from, date.Format(time.RFC3339))
-	file, err = os.Create(path.Join(htmlDir, htmlName))
-	if err != nil {
-		return err
-	}
-	err = h.mailTpl.Execute(file, payload)
-	if err != nil {
-		return err
-	}
-	file.Close()
-
-	dir, err := os.ReadDir(path.Join(h.out))
-	if err != nil {
-		fmt.Println("readDir error:", err.Error())
-		return err
-	}
-
-	var threads []struct {
-		Title   string
-		LastMod time.Time
-	}
-	for _, d := range dir {
-		if !d.IsDir() {
-			continue
-		}
-
-		info, err := d.Info()
-		if err != nil {
-			fmt.Println("dir info", err.Error())
-			continue
-		}
-		t := struct {
-			Title   string
-			LastMod time.Time
-		}{
-			Title:   d.Name(),
-			LastMod: info.ModTime(),
-		}
-
-		threads = append(threads, t)
-	}
-	sort.Slice(
-		threads,
-		func(i, j int) bool {
-			return threads[i].LastMod.After(threads[j].LastMod)
-		},
-	)
-
-	file, err = os.Create(path.Join(h.out, "index.html"))
-	if err != nil {
-		return err
-	}
-	err = h.indexTpl.Execute(file, threads)
-	if err != nil {
-		return err
-	}
-	file.Close()
-
-	dir, err = os.ReadDir(path.Join(h.out, to))
-	if err != nil {
-		fmt.Println("readDir error:", err.Error())
-		return err
-	}
-
-	threads = []struct {
-		Title   string
-		LastMod time.Time
-	}{}
-	for _, d := range dir {
-		if !d.IsDir() {
-			continue
-		}
-
-		info, err := d.Info()
-		if err != nil {
-			fmt.Println("dir info", err.Error())
-			continue
-		}
-		t := struct {
-			Title   string
-			LastMod time.Time
-		}{
-			Title:   d.Name(),
-			LastMod: info.ModTime(),
-		}
-
-		threads = append(threads, t)
-	}
-	sort.Slice(
-		threads,
-		func(i, j int) bool {
-			return threads[i].LastMod.After(threads[j].LastMod)
-		},
-	)
-
-	file, err = os.Create(path.Join(h.out, to, "index.html"))
-	if err != nil {
-		return err
-	}
-	err = h.listTpl.Execute(file, threads)
-	if err != nil {
-		return err
-	}
-	file.Close()
-
-	dir, err = os.ReadDir(path.Join(h.out, to, subj))
-	if err != nil {
-		fmt.Println("read subj dir error:", err.Error())
-		return err
-	}
-
-	threads = []struct {
-		Title   string
-		LastMod time.Time
-	}{}
-	for _, d := range dir {
-		info, err := d.Info()
-		if err != nil {
-			fmt.Println("dir info", err.Error())
-			continue
-		}
-		t := struct {
-			Title   string
-			LastMod time.Time
-		}{
-			Title:   d.Name(),
-			LastMod: info.ModTime(),
-		}
-
-		threads = append(threads, t)
-	}
-	sort.Slice(
-		threads,
-		func(i, j int) bool {
-			return threads[i].LastMod.Before(threads[j].LastMod)
-		},
-	)
-
-	file, err = os.Create(path.Join(h.out, to, subj, "index.html"))
-	if err != nil {
-		return err
-	}
-	err = h.mailsTpl.Execute(file, threads)
-	if err != nil {
-		return err
-	}
-	return file.Close()
-}

model/raw/raw.go

commit d4e60c7a37f4552dd4eacfc2e98144f378bcc51f
Author: blmayer <bleemayer@gmail.com>
Date:   Fri Sep 8 02:05:56 2023 -0300

    Removed handlers

diff --git a/model/raw/raw.go b/model/raw/raw.go
deleted file mode 100644
index d8b8e04..0000000
--- a/model/raw/raw.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// web adds the common email file handling, but with a twist: here we save
-// emails first by inbox and then subject. This package also delivers some
-// functions to be used in the templates, for the web interface.
-package raw
-
-import (
-	"bufio"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"net/mail"
-	"net/textproto"
-	"os"
-	"path"
-	"strings"
-	"time"
-
-	"git.derelict.garden/dovel/email/model"
-
-	pgp "github.com/ProtonMail/gopenpgp/v2/crypto"
-	"github.com/ProtonMail/gopenpgp/v2/helper"
-)
-
-// EawHandler is used to configure the raw email handler.
-// Root is where mail should be saved, Templates is an optional field that
-// is the path to templates, to be used in the web server; Password is the
-// password for the x user, for now this is very simple; Domain is used to
-// filter and separate emails, only emails sent to one of your domains are
-// saved, each according to its configuration.
-type RawHandler struct {
-	root       string
-	domain     string
-	privateKey string
-}
-
-func NewRawHandler(c model.CommonConfig) (RawHandler, error) {
-	h := RawHandler{root: c.Root, domain: c.Domain}
-
-	if c.PrivateKey != "" {
-		key, err := ioutil.ReadFile(c.PrivateKey)
-		if err != nil {
-			return h, err
-		}
-		h.privateKey = string(key)
-	}
-
-	return h, nil
-}
-
-func (h RawHandler) Save(from, to string, raw io.Reader) error {
-	mail, err := mail.ReadMessage(raw)
-	if err != nil {
-		return err
-	}
-
-	content, err := ioutil.ReadAll(mail.Body)
-	if err != nil {
-		println("file read", err.Error())
-		return err
-	}
-
-	// try to decrypt
-	if h.privateKey != "" && pgp.IsPGPMessage(string(content)) {
-		println("decrypting")
-		txt, err := helper.DecryptMessageArmored(
-			h.privateKey,
-			nil,
-			string(content),
-		)
-		if err != nil {
-			println(err)
-		}
-		content = []byte(txt)
-	}
-
-	now := time.Now().Format(time.RFC3339)
-	subj := mail.Header.Get("Subject")
-	mailDir := path.Join(h.root, to, subj)
-	os.MkdirAll(mailDir, os.ModeDir|0o700)
-	name := fmt.Sprintf("%s:%s.mail", from, now)
-
-	file, err := os.Create(path.Join(mailDir, name))
-	if err != nil {
-		return err
-	}
-
-	writer := textproto.NewWriter(bufio.NewWriter(file))
-	for k, v := range mail.Header {
-		writer.PrintfLine("%s: %s", k, strings.Join(v, ", "))
-	}
-	writer.PrintfLine("")
-	file.Write(content)
-
-	return file.Close()
-}
-
-func (f RawHandler) Mailboxes(folder string) ([]model.Mailbox, error) {
-	return nil, nil
-}
-
-func (f RawHandler) Mails(folder string) ([]model.Email, error) {
-	return nil, nil
-}
-
-func (f RawHandler) Mail(file string) (model.Email, error) {
-	return model.Email{}, nil
-}
-
-// Move is used to move messages between folders. The id parameter
-// is the message id to be moved and to is the destination folder.
-// Root is added automatically.
-func (f RawHandler) Move(id, to string) error {
-	return nil
-}
-
-// Delete is used to delete messages. The id parameter
-// is the message id to be deleted.
-// Root is added automatically.
-func (f RawHandler) Delete(id string) error {
-	return nil
-}