Get as far as storing an incoming mail in crockery.db
This commit is contained in:
14
DESIGN.md
14
DESIGN.md
@@ -38,3 +38,17 @@ following, though:
|
|||||||
* `Admin`
|
* `Admin`
|
||||||
* `Wildcard`
|
* `Wildcard`
|
||||||
* `PasswordHash`
|
* `PasswordHash`
|
||||||
|
* `Message`
|
||||||
|
* `ID`
|
||||||
|
* `Username`
|
||||||
|
* `Mailbox`
|
||||||
|
* `Header`
|
||||||
|
* `Body`
|
||||||
|
|
||||||
|
I don't seem to be able to get accounts for a list of usernames very easily, or
|
||||||
|
indexed-ly.
|
||||||
|
|
||||||
|
The message body is being stored inefficiently (json []byte, so base64-encoded)
|
||||||
|
|
||||||
|
Message username and mailbox are indexed, but is that good enough? Perhaps we
|
||||||
|
should have a bucket per mailbox or something?
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/mail"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
@@ -74,6 +75,30 @@ func (m *mta) AnonymousLogin() (smtp.User, error) {
|
|||||||
// User implementation for go-smtp after this point
|
// User implementation for go-smtp after this point
|
||||||
// Since only anonymous login is permitted, there's no need to introduce an
|
// Since only anonymous login is permitted, there's no need to introduce an
|
||||||
// intermediary to track the logged-in user.
|
// intermediary to track the logged-in user.
|
||||||
|
//
|
||||||
|
// TODO: wildcard address support. For now, just dump everything directly
|
||||||
|
// into a single hardcoded mailbox per-account
|
||||||
func (m *mta) ServeSMTP(from string, to []string, r io.Reader) error {
|
func (m *mta) ServeSMTP(from string, to []string, r io.Reader) error {
|
||||||
return fmt.Errorf("Not yet implemented")
|
emails := make([]string, len(to))
|
||||||
|
|
||||||
|
for i, entry := range to {
|
||||||
|
addr, err := mail.ParseAddress(entry) // FIXME: should this be AddressList?
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
emails[i] = addr.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("emails: %#v", emails)
|
||||||
|
|
||||||
|
recipients, err := m.store.FindAccounts(emails...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recipients) != len(to) {
|
||||||
|
return fmt.Errorf("At least one recipient is not known locally")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.store.SpoolMessage(recipients, r)
|
||||||
}
|
}
|
||||||
|
@@ -53,9 +53,18 @@ func (c *concrete) FindAccount(username string) (Account, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *concrete) FindAccounts(usernames ...string) ([]Account, error) {
|
func (c *concrete) FindAccounts(usernames ...string) ([]Account, error) {
|
||||||
var accounts []Account
|
accounts := make([]Account, len(usernames))
|
||||||
|
|
||||||
return accounts, c.storm.Find("Username", usernames, &accounts)
|
// FIXME: I don't know how to storm, it turns out
|
||||||
|
for i, username := range usernames {
|
||||||
|
account, err := c.FindAccount(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accounts[i] = account
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *concrete) FindAccountWithPassword(username, password string) (Account, error) {
|
func (c *concrete) FindAccountWithPassword(username, password string) (Account, error) {
|
||||||
|
22
internal/store/message.go
Normal file
22
internal/store/message.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/mail"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessageInterface interface {
|
||||||
|
CreateMessage(Message) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
ID string
|
||||||
|
Username string `storm:"index"` // FK accounts.username
|
||||||
|
Mailbox string `storm:"index"` // The mailbox, e.g. `INBOX` or `Foo/Bar`
|
||||||
|
|
||||||
|
Header mail.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concrete) CreateMessage(message Message) error {
|
||||||
|
return c.storm.Save(&message)
|
||||||
|
}
|
48
internal/store/spool.go
Normal file
48
internal/store/spool.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/mail"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpoolInterface interface {
|
||||||
|
SpoolMessage([]Account, io.Reader) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we don't actually spool for now, we just store it to each user's
|
||||||
|
// mailbox. This might lead to oddness in the partial delivery case.
|
||||||
|
func (c *concrete) SpoolMessage(recipients []Account, r io.Reader) error {
|
||||||
|
message, err := mail.ReadMessage(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we can assign our own ID in this case
|
||||||
|
mid := message.Header.Get("Message-ID")
|
||||||
|
if mid == "" {
|
||||||
|
return fmt.Errorf("No Message-ID for this email")
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(message.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, recipient := range recipients {
|
||||||
|
message := Message{
|
||||||
|
ID: mid,
|
||||||
|
Username: recipient.Username,
|
||||||
|
Mailbox: "INBOX", // FIXME: at some point this will be changeable
|
||||||
|
Header: message.Header,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.CreateMessage(message); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -19,6 +19,8 @@ type Interface interface {
|
|||||||
SetTLS([]byte, []byte) error
|
SetTLS([]byte, []byte) error
|
||||||
|
|
||||||
AccountInterface
|
AccountInterface
|
||||||
|
MessageInterface
|
||||||
|
SpoolInterface
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user