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 19:50:01 2023 -0300
Parent: d4e60c7
Organized subpackages
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 }
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)
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
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 -}
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] - } - - -}
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 -}
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) -} -
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) -}
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() +}