mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47: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"
|
||||
"strings"
|
||||
"time"
|
||||
"github.com/jhillyerd/inbucket/app/inbucket"
|
||||
)
|
||||
|
||||
type State int
|
||||
@@ -235,7 +236,16 @@ func (ss *Session) mailHandler(cmd string, arg string) {
|
||||
|
||||
// DATA
|
||||
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>")
|
||||
for {
|
||||
line, err := ss.readLine()
|
||||
@@ -251,6 +261,9 @@ func (ss *Session) dataHandler() {
|
||||
}
|
||||
if line == ".\r\n" || line == ".\n" {
|
||||
// Mail data complete
|
||||
for _, mo := range mailObjects {
|
||||
mo.Close()
|
||||
}
|
||||
ss.send("250 Mail accepted for delivery")
|
||||
ss.info("Message size %v bytes", msgSize)
|
||||
ss.enterState(READY)
|
||||
@@ -260,7 +273,16 @@ func (ss *Session) dataHandler() {
|
||||
line = line[1:]
|
||||
}
|
||||
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 (
|
||||
"fmt"
|
||||
"net"
|
||||
"github.com/jhillyerd/inbucket/app/inbucket"
|
||||
"github.com/robfig/revel"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Real server code starts here
|
||||
@@ -12,11 +13,14 @@ type Server struct {
|
||||
port int
|
||||
maxRecips int
|
||||
maxIdleSeconds int
|
||||
dataStore *inbucket.DataStore
|
||||
}
|
||||
|
||||
// Init a new Server object
|
||||
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
|
||||
|
||||
@@ -8,6 +8,7 @@ results.pretty=true
|
||||
server.watcher=true
|
||||
smtpd.domain=skynet
|
||||
smtpd.port=2500
|
||||
datastore.path=/tmp/inbucket
|
||||
|
||||
log.trace.output = off
|
||||
log.info.output = stderr
|
||||
@@ -19,6 +20,7 @@ results.pretty=false
|
||||
server.watcher=false
|
||||
smtpd.domain=skynet
|
||||
smtpd.port=2500
|
||||
datastore.path=/tmp/inbucket
|
||||
|
||||
log.trace.output = off
|
||||
log.info.output = off
|
||||
|
||||
Reference in New Issue
Block a user