This is the main dovel repository, it has the Go code to run dovel SMTP server.
Author: blmayer (bleemayer@gmail.com)
Date: Fri Sep 8 02:05:56 2023 -0300
Parent: 95eb49e
Removed handlers
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
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)
}
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
+}
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
-}
-
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
-}
-
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()
-}
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
-}