mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-18 18:17:03 +00:00
Server now writes raw smtp data to datastore. Does not yet create gob files.
This commit is contained in:
113
app/inbucket/datastore.go
Normal file
113
app/inbucket/datastore.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package inbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"github.com/robfig/revel"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotWritable = errors.New("MailObject not writable")
|
||||||
|
|
||||||
|
// Global because we only want one regardless of the number of DataStore objects
|
||||||
|
var countChannel = make(chan int, 10)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Start generator
|
||||||
|
go countGenerator(countChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populates the channel with numbers
|
||||||
|
func countGenerator(c chan int) {
|
||||||
|
for i := 0; true; i = (i + 1) % 10000 {
|
||||||
|
c <- i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataStore struct {
|
||||||
|
path string
|
||||||
|
mailPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataStore() *DataStore {
|
||||||
|
path, found := rev.Config.String("datastore.path")
|
||||||
|
if found {
|
||||||
|
mailPath := filepath.Join(path, "mail")
|
||||||
|
return &DataStore{path: path, mailPath: mailPath}
|
||||||
|
}
|
||||||
|
rev.ERROR.Printf("No value configured for datastore.path")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailObject struct {
|
||||||
|
store *DataStore
|
||||||
|
mailbox string
|
||||||
|
rawPath string
|
||||||
|
gobPath string
|
||||||
|
writable bool
|
||||||
|
writerFile *os.File
|
||||||
|
writer *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds *DataStore) NewMailObject(emailAddress string) *MailObject {
|
||||||
|
mailbox := ParseMailboxName(emailAddress)
|
||||||
|
maildir := HashMailboxName(mailbox)
|
||||||
|
fileBase := time.Now().Format("20060102T150405") + "-" + fmt.Sprintf("%04d", <-countChannel)
|
||||||
|
boxPath := filepath.Join(ds.mailPath, maildir)
|
||||||
|
if err := os.MkdirAll(boxPath, 0770); err != nil {
|
||||||
|
rev.ERROR.Printf("Failed to create directory %v, %v", boxPath, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pathBase := filepath.Join(boxPath, fileBase)
|
||||||
|
|
||||||
|
return &MailObject{store: ds, mailbox: mailbox, rawPath: pathBase + ".raw",
|
||||||
|
gobPath: pathBase + ".gob", writable: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MailObject) Mailbox() string {
|
||||||
|
return m.mailbox
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MailObject) Append(data []byte) error {
|
||||||
|
// Prevent Appending to a pre-existing MailObject
|
||||||
|
if !m.writable {
|
||||||
|
return ErrNotWritable
|
||||||
|
}
|
||||||
|
// Open file for writing if we haven't yet
|
||||||
|
if m.writer == nil {
|
||||||
|
file, err := os.Create(m.rawPath)
|
||||||
|
if err != nil {
|
||||||
|
// Set writable false just in case something calls me a million times
|
||||||
|
m.writable = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.writerFile = file
|
||||||
|
m.writer = bufio.NewWriter(file)
|
||||||
|
}
|
||||||
|
_, err := m.writer.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MailObject) Close() error {
|
||||||
|
// nil out the fields so they can't be used
|
||||||
|
writer := m.writer
|
||||||
|
writerFile := m.writerFile
|
||||||
|
m.writer = nil
|
||||||
|
m.writerFile = nil
|
||||||
|
|
||||||
|
if (writer != nil) {
|
||||||
|
if err := writer.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (writerFile != nil) {
|
||||||
|
if err := writerFile.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/jhillyerd/inbucket/app/inbucket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type State int
|
type State int
|
||||||
@@ -235,7 +236,16 @@ func (ss *Session) mailHandler(cmd string, arg string) {
|
|||||||
|
|
||||||
// DATA
|
// DATA
|
||||||
func (ss *Session) dataHandler() {
|
func (ss *Session) dataHandler() {
|
||||||
var msgSize uint64 = 0
|
msgSize := uint64(0)
|
||||||
|
|
||||||
|
// Get a MailObject for each recipient
|
||||||
|
mailObjects := make([]*inbucket.MailObject, ss.recipients.Len())
|
||||||
|
i := 0
|
||||||
|
for e := ss.recipients.Front(); e != nil; e = e.Next() {
|
||||||
|
mailObjects[i] = ss.server.dataStore.NewMailObject(e.Value.(string))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
ss.send("354 Start mail input; end with <CRLF>.<CRLF>")
|
ss.send("354 Start mail input; end with <CRLF>.<CRLF>")
|
||||||
for {
|
for {
|
||||||
line, err := ss.readLine()
|
line, err := ss.readLine()
|
||||||
@@ -251,6 +261,9 @@ func (ss *Session) dataHandler() {
|
|||||||
}
|
}
|
||||||
if line == ".\r\n" || line == ".\n" {
|
if line == ".\r\n" || line == ".\n" {
|
||||||
// Mail data complete
|
// Mail data complete
|
||||||
|
for _, mo := range mailObjects {
|
||||||
|
mo.Close()
|
||||||
|
}
|
||||||
ss.send("250 Mail accepted for delivery")
|
ss.send("250 Mail accepted for delivery")
|
||||||
ss.info("Message size %v bytes", msgSize)
|
ss.info("Message size %v bytes", msgSize)
|
||||||
ss.enterState(READY)
|
ss.enterState(READY)
|
||||||
@@ -260,7 +273,16 @@ func (ss *Session) dataHandler() {
|
|||||||
line = line[1:]
|
line = line[1:]
|
||||||
}
|
}
|
||||||
msgSize += uint64(len(line))
|
msgSize += uint64(len(line))
|
||||||
// TODO: Add variable line to something!
|
// Append to message objects
|
||||||
|
for _, mo := range mailObjects {
|
||||||
|
if err := mo.Append([]byte(line)); err != nil {
|
||||||
|
ss.error("Failed to append to mailbox %v: %v", mo.Mailbox(), err)
|
||||||
|
ss.send("554 Something went wrong")
|
||||||
|
ss.enterState(READY)
|
||||||
|
// TODO: Should really cleanup the crap on filesystem...
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package smtpd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"github.com/jhillyerd/inbucket/app/inbucket"
|
||||||
"github.com/robfig/revel"
|
"github.com/robfig/revel"
|
||||||
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Real server code starts here
|
// Real server code starts here
|
||||||
@@ -12,11 +13,14 @@ type Server struct {
|
|||||||
port int
|
port int
|
||||||
maxRecips int
|
maxRecips int
|
||||||
maxIdleSeconds int
|
maxIdleSeconds int
|
||||||
|
dataStore *inbucket.DataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init a new Server object
|
// Init a new Server object
|
||||||
func New(domain string, port int) *Server {
|
func New(domain string, port int) *Server {
|
||||||
return &Server{domain: domain, port: port, maxRecips: 100, maxIdleSeconds: 60}
|
ds := inbucket.NewDataStore()
|
||||||
|
return &Server{domain: domain, port: port, maxRecips: 100, maxIdleSeconds: 60,
|
||||||
|
dataStore: ds}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loggers
|
// Loggers
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ results.pretty=true
|
|||||||
server.watcher=true
|
server.watcher=true
|
||||||
smtpd.domain=skynet
|
smtpd.domain=skynet
|
||||||
smtpd.port=2500
|
smtpd.port=2500
|
||||||
|
datastore.path=/tmp/inbucket
|
||||||
|
|
||||||
log.trace.output = off
|
log.trace.output = off
|
||||||
log.info.output = stderr
|
log.info.output = stderr
|
||||||
@@ -19,6 +20,7 @@ results.pretty=false
|
|||||||
server.watcher=false
|
server.watcher=false
|
||||||
smtpd.domain=skynet
|
smtpd.domain=skynet
|
||||||
smtpd.port=2500
|
smtpd.port=2500
|
||||||
|
datastore.path=/tmp/inbucket
|
||||||
|
|
||||||
log.trace.output = off
|
log.trace.output = off
|
||||||
log.info.output = off
|
log.info.output = off
|
||||||
|
|||||||
Reference in New Issue
Block a user