1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-18 10:07:02 +00:00

SMTP server is running with new config engine

Web still not working
This commit is contained in:
James Hillyerd
2012-10-20 21:36:57 -07:00
parent ce9289140a
commit 81fea97a90
6 changed files with 153 additions and 56 deletions

View File

@@ -4,11 +4,27 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"github.com/robfig/goconfig/config" "github.com/robfig/goconfig/config"
"net"
"os" "os"
) )
// SmtpConfig houses the SMTP server configuration - not using pointers
// so that I can pass around copies of the object safely.
type SmtpConfig struct {
Ip4address net.IP
Ip4port int
Domain string
}
var smtpConfig *SmtpConfig
var Config *config.Config var Config *config.Config
// GetSmtpConfig returns a copy of the SmtpConfig
func GetSmtpConfig() SmtpConfig {
return *smtpConfig
}
// LoadConfig loads the specified configuration file into inbucket.Config // LoadConfig loads the specified configuration file into inbucket.Config
// and performs validations on it. // and performs validations on it.
func LoadConfig(filename string) error { func LoadConfig(filename string) error {
@@ -49,6 +65,44 @@ func LoadConfig(filename string) error {
return fmt.Errorf("Failed to validate configuration") return fmt.Errorf("Failed to validate configuration")
} }
err = parseSmtpConfig()
return err
}
// parseSmtpConfig trying to catch config errors early
func parseSmtpConfig() error {
smtpConfig = new(SmtpConfig)
// Parse IP4 address only, error on IP6.
option := "[smtp]ip4.address"
str, err := Config.String("smtp", "ip4.address")
if err != nil {
return fmt.Errorf("Failed to parse %v: %v", option, err)
}
addr := net.ParseIP(str)
if addr == nil {
return fmt.Errorf("Failed to parse %v '%v'", option, str)
}
addr = addr.To4()
if addr == nil {
return fmt.Errorf("Failed to parse %v '%v' not IPv4!", option, str)
}
smtpConfig.Ip4address = addr
option = "[smtp]ip4.port"
smtpConfig.Ip4port, err = Config.Int("smtp", "ip4.port")
if err != nil {
return fmt.Errorf("Failed to parse %v: %v", option, err)
}
option = "[smtp]domain"
str, err = Config.String("smtp", "domain")
if err != nil {
return fmt.Errorf("Failed to parse %v: %v", option, err)
}
smtpConfig.Domain = str
return nil return nil
} }

View File

@@ -5,7 +5,6 @@ import (
"encoding/gob" "encoding/gob"
"errors" "errors"
"fmt" "fmt"
"github.com/robfig/revel"
"io/ioutil" "io/ioutil"
"net/mail" "net/mail"
"os" "os"
@@ -38,16 +37,20 @@ type DataStore struct {
mailPath string mailPath string
} }
// NewDataStore creates a new DataStore object. It uses the Revel Config object to // NewDataStore creates a new DataStore object. It uses the inbucket.Config object to
// construct it's path. // construct it's path.
func NewDataStore() *DataStore { func NewDataStore() *DataStore {
path, found := rev.Config.String("datastore.path") path, err := Config.String("datastore", "path")
if found { if err != nil {
Error("Error getting datastore path: %v", err)
return nil
}
if path == "" {
Error("No value configured for datastore path")
return nil
}
mailPath := filepath.Join(path, "mail") mailPath := filepath.Join(path, "mail")
return &DataStore{path: path, mailPath: mailPath} return &DataStore{path: path, mailPath: mailPath}
}
rev.ERROR.Printf("No value configured for datastore.path")
return nil
} }
// Retrieves the Mailbox object for a specified email address, if the mailbox // Retrieves the Mailbox object for a specified email address, if the mailbox
@@ -57,7 +60,7 @@ func (ds *DataStore) MailboxFor(emailAddress string) (*Mailbox, error) {
dir := HashMailboxName(name) dir := HashMailboxName(name)
path := filepath.Join(ds.mailPath, dir) path := filepath.Join(ds.mailPath, dir)
if err := os.MkdirAll(path, 0770); err != nil { if err := os.MkdirAll(path, 0770); err != nil {
rev.ERROR.Printf("Failed to create directory %v, %v", path, err) Error("Failed to create directory %v, %v", path, err)
return nil, err return nil, err
} }
return &Mailbox{store: ds, name: name, dirName: dir, path: path}, nil return &Mailbox{store: ds, name: name, dirName: dir, path: path}, nil
@@ -83,7 +86,7 @@ func (mb *Mailbox) GetMessages() ([]*Message, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
rev.TRACE.Printf("Scanning %v files for %v", len(files), mb) Trace("Scanning %v files for %v", len(files), mb)
messages := make([]*Message, 0, len(files)) messages := make([]*Message, 0, len(files))
for _, f := range files { for _, f := range files {
@@ -100,7 +103,7 @@ func (mb *Mailbox) GetMessages() ([]*Message, error) {
} }
file.Close() file.Close()
msg.mailbox = mb msg.mailbox = mb
rev.TRACE.Printf("Found: %v", msg) Trace("Found: %v", msg)
messages = append(messages, msg) messages = append(messages, msg)
} }
} }
@@ -121,7 +124,7 @@ func (mb *Mailbox) GetMessage(id string) (*Message, error) {
} }
file.Close() file.Close()
msg.mailbox = mb msg.mailbox = mb
rev.TRACE.Printf("Found: %v", msg) Trace("Found: %v", msg)
return msg, nil return msg, nil
} }
@@ -157,8 +160,8 @@ func (m *Message) gobPath() string {
} }
func (m *Message) rawPath() string { func (m *Message) rawPath() string {
rev.TRACE.Println(m.mailbox.path) Trace(m.mailbox.path)
rev.TRACE.Println(m.Id) Trace(m.Id)
return filepath.Join(m.mailbox.path, m.Id+".raw") return filepath.Join(m.mailbox.path, m.Id+".raw")
} }

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/jhillyerd/inbucket" "github.com/jhillyerd/inbucket"
"github.com/jhillyerd/inbucket/smtpd" "github.com/jhillyerd/inbucket/smtpd"
"log"
"os" "os"
) )
@@ -26,15 +27,20 @@ func main() {
os.Exit(1) os.Exit(1)
} }
err := inbucket.LoadConfig(flag.Arg(0)) err := inbucket.LoadConfig(flag.Arg(0))
configError(err) if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse config: %v\n", err)
os.Exit(1)
}
log.Println("Logger test")
inbucket.Trace("trace test")
inbucket.Info("info test")
inbucket.Warn("warn test")
inbucket.Error("error test")
// Startup SMTP server // Startup SMTP server
domain, err := inbucket.Config.String("smtp", "domain") server := smtpd.New()
configError(err) server.Start()
port, err := inbucket.Config.Int("smtp", "ip4.port")
configError(err)
server := smtpd.New(domain, port)
go server.Start()
} }
func init() { func init() {
@@ -43,10 +49,3 @@ func init() {
flag.PrintDefaults() flag.PrintDefaults()
} }
} }
func configError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n", err)
os.Exit(1)
}
}

46
logging.go Normal file
View File

@@ -0,0 +1,46 @@
package inbucket
import (
"log"
)
type LogLevel int
const (
ERROR LogLevel = iota
WARN
INFO
TRACE
)
var MaxLogLevel LogLevel = TRACE
// Error logs a message to the 'standard' Logger (always)
func Error(msg string, args ...interface{}) {
msg = "[ERROR] " + msg
log.Printf(msg, args...)
}
// Warn logs a message to the 'standard' Logger if MaxLogLevel is >= WARN
func Warn(msg string, args ...interface{}) {
if MaxLogLevel >= WARN {
msg = "[WARN ] " + msg
log.Printf(msg, args...)
}
}
// Info logs a message to the 'standard' Logger if MaxLogLevel is >= INFO
func Info(msg string, args ...interface{}) {
if MaxLogLevel >= INFO {
msg = "[INFO ] " + msg
log.Printf(msg, args...)
}
}
// Trace logs a message to the 'standard' Logger if MaxLogLevel is >= TRACE
func Trace(msg string, args ...interface{}) {
if MaxLogLevel >= TRACE {
msg = "[TRACE] " + msg
log.Printf(msg, args...)
}
}

View File

@@ -87,7 +87,7 @@ func (ss *Session) String() string {
* 5. Goto 2 * 5. Goto 2
*/ */
func (s *Server) startSession(id int, conn net.Conn) { func (s *Server) startSession(id int, conn net.Conn) {
s.info("Connection from %v, starting session <%v>", conn.RemoteAddr(), id) inbucket.Info("Connection from %v, starting session <%v>", conn.RemoteAddr(), id)
defer conn.Close() defer conn.Close()
ss := NewSession(s, id, conn) ss := NewSession(s, id, conn)
@@ -476,17 +476,17 @@ func (ss *Session) ooSeq(cmd string) {
// Session specific logging methods // Session specific logging methods
func (ss *Session) trace(msg string, args ...interface{}) { func (ss *Session) trace(msg string, args ...interface{}) {
ss.server.trace("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) inbucket.Trace("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...))
} }
func (ss *Session) info(msg string, args ...interface{}) { func (ss *Session) info(msg string, args ...interface{}) {
ss.server.info("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) inbucket.Info("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...))
} }
func (ss *Session) warn(msg string, args ...interface{}) { func (ss *Session) warn(msg string, args ...interface{}) {
ss.server.warn("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) inbucket.Warn("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...))
} }
func (ss *Session) error(msg string, args ...interface{}) { func (ss *Session) error(msg string, args ...interface{}) {
ss.server.error("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) inbucket.Error("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...))
} }

View File

@@ -3,14 +3,12 @@ package smtpd
import ( import (
"fmt" "fmt"
"github.com/jhillyerd/inbucket" "github.com/jhillyerd/inbucket"
"github.com/robfig/revel"
"net" "net"
) )
// Real server code starts here // Real server code starts here
type Server struct { type Server struct {
domain string domain string
port int
maxRecips int maxRecips int
maxIdleSeconds int maxIdleSeconds int
maxMessageBytes int maxMessageBytes int
@@ -18,39 +16,36 @@ type Server struct {
} }
// Init a new Server object // Init a new Server object
func New(domain string, port int) *Server { func New() *Server {
ds := inbucket.NewDataStore() ds := inbucket.NewDataStore()
return &Server{domain: domain, port: port, maxRecips: 100, maxIdleSeconds: 300, // TODO Make more of these configurable
return &Server{domain: inbucket.GetSmtpConfig().Domain, maxRecips: 100, maxIdleSeconds: 300,
dataStore: ds, maxMessageBytes: 2048000} dataStore: ds, maxMessageBytes: 2048000}
} }
// Loggers
func (s *Server) trace(msg string, args ...interface{}) {
rev.TRACE.Printf(msg, args...)
}
func (s *Server) info(msg string, args ...interface{}) {
rev.INFO.Printf(msg, args...)
}
func (s *Server) warn(msg string, args ...interface{}) {
rev.WARN.Printf(msg, args...)
}
func (s *Server) error(msg string, args ...interface{}) {
rev.ERROR.Printf(msg, args...)
}
// Main listener loop // Main listener loop
func (s *Server) Start() { func (s *Server) Start() {
s.trace("Server Start() called") cfg := inbucket.GetSmtpConfig()
ln, err := net.Listen("tcp", fmt.Sprintf(":%v", s.port)) addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v",
cfg.Ip4address, cfg.Ip4port))
if err != nil { if err != nil {
inbucket.Error("Failed to build tcp4 address: %v", err)
// TODO More graceful early-shutdown procedure
panic(err)
}
inbucket.Info("Listening on TCP4 %v", addr)
ln, err := net.ListenTCP("tcp4", addr)
if err != nil {
inbucket.Error("Failed to start tcp4 listener: %v", err)
// TODO More graceful early-shutdown procedure
panic(err) panic(err)
} }
for sid := 1; ; sid++ { for sid := 1; ; sid++ {
if conn, err := ln.Accept(); err != nil { if conn, err := ln.Accept(); err != nil {
// TODO Implement a max error counter before shutdown?
// or maybe attempt to restart smtpd
panic(err) panic(err)
} else { } else {
go s.startSession(sid, conn) go s.startSession(sid, conn)