mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
SMTP server is running with new config engine
Web still not working
This commit is contained in:
54
config.go
54
config.go
@@ -4,11 +4,27 @@ import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"github.com/robfig/goconfig/config"
|
||||
"net"
|
||||
"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
|
||||
|
||||
// GetSmtpConfig returns a copy of the SmtpConfig
|
||||
func GetSmtpConfig() SmtpConfig {
|
||||
return *smtpConfig
|
||||
}
|
||||
|
||||
// LoadConfig loads the specified configuration file into inbucket.Config
|
||||
// and performs validations on it.
|
||||
func LoadConfig(filename string) error {
|
||||
@@ -49,6 +65,44 @@ func LoadConfig(filename string) error {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
31
datastore.go
31
datastore.go
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/robfig/revel"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"os"
|
||||
@@ -38,16 +37,20 @@ type DataStore struct {
|
||||
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.
|
||||
func NewDataStore() *DataStore {
|
||||
path, found := rev.Config.String("datastore.path")
|
||||
if found {
|
||||
mailPath := filepath.Join(path, "mail")
|
||||
return &DataStore{path: path, mailPath: mailPath}
|
||||
path, err := Config.String("datastore", "path")
|
||||
if err != nil {
|
||||
Error("Error getting datastore path: %v", err)
|
||||
return nil
|
||||
}
|
||||
rev.ERROR.Printf("No value configured for datastore.path")
|
||||
return nil
|
||||
if path == "" {
|
||||
Error("No value configured for datastore path")
|
||||
return nil
|
||||
}
|
||||
mailPath := filepath.Join(path, "mail")
|
||||
return &DataStore{path: path, mailPath: mailPath}
|
||||
}
|
||||
|
||||
// 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)
|
||||
path := filepath.Join(ds.mailPath, dir)
|
||||
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 &Mailbox{store: ds, name: name, dirName: dir, path: path}, nil
|
||||
@@ -83,7 +86,7 @@ func (mb *Mailbox) GetMessages() ([]*Message, error) {
|
||||
if err != nil {
|
||||
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))
|
||||
for _, f := range files {
|
||||
@@ -100,7 +103,7 @@ func (mb *Mailbox) GetMessages() ([]*Message, error) {
|
||||
}
|
||||
file.Close()
|
||||
msg.mailbox = mb
|
||||
rev.TRACE.Printf("Found: %v", msg)
|
||||
Trace("Found: %v", msg)
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
}
|
||||
@@ -121,7 +124,7 @@ func (mb *Mailbox) GetMessage(id string) (*Message, error) {
|
||||
}
|
||||
file.Close()
|
||||
msg.mailbox = mb
|
||||
rev.TRACE.Printf("Found: %v", msg)
|
||||
Trace("Found: %v", msg)
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
@@ -157,8 +160,8 @@ func (m *Message) gobPath() string {
|
||||
}
|
||||
|
||||
func (m *Message) rawPath() string {
|
||||
rev.TRACE.Println(m.mailbox.path)
|
||||
rev.TRACE.Println(m.Id)
|
||||
Trace(m.mailbox.path)
|
||||
Trace(m.Id)
|
||||
return filepath.Join(m.mailbox.path, m.Id+".raw")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/jhillyerd/inbucket"
|
||||
"github.com/jhillyerd/inbucket/smtpd"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -26,15 +27,20 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
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
|
||||
domain, err := inbucket.Config.String("smtp", "domain")
|
||||
configError(err)
|
||||
port, err := inbucket.Config.Int("smtp", "ip4.port")
|
||||
configError(err)
|
||||
server := smtpd.New(domain, port)
|
||||
go server.Start()
|
||||
server := smtpd.New()
|
||||
server.Start()
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -43,10 +49,3 @@ func init() {
|
||||
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
46
logging.go
Normal 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...)
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (ss *Session) String() string {
|
||||
* 5. Goto 2
|
||||
*/
|
||||
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()
|
||||
|
||||
ss := NewSession(s, id, conn)
|
||||
@@ -476,17 +476,17 @@ func (ss *Session) ooSeq(cmd string) {
|
||||
|
||||
// Session specific logging methods
|
||||
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{}) {
|
||||
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{}) {
|
||||
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{}) {
|
||||
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...))
|
||||
}
|
||||
|
||||
@@ -3,14 +3,12 @@ package smtpd
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jhillyerd/inbucket"
|
||||
"github.com/robfig/revel"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Real server code starts here
|
||||
type Server struct {
|
||||
domain string
|
||||
port int
|
||||
maxRecips int
|
||||
maxIdleSeconds int
|
||||
maxMessageBytes int
|
||||
@@ -18,39 +16,36 @@ type Server struct {
|
||||
}
|
||||
|
||||
// Init a new Server object
|
||||
func New(domain string, port int) *Server {
|
||||
func New() *Server {
|
||||
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}
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *Server) Start() {
|
||||
s.trace("Server Start() called")
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%v", s.port))
|
||||
cfg := inbucket.GetSmtpConfig()
|
||||
addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v",
|
||||
cfg.Ip4address, cfg.Ip4port))
|
||||
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)
|
||||
}
|
||||
|
||||
for sid := 1; ; sid++ {
|
||||
if conn, err := ln.Accept(); err != nil {
|
||||
// TODO Implement a max error counter before shutdown?
|
||||
// or maybe attempt to restart smtpd
|
||||
panic(err)
|
||||
} else {
|
||||
go s.startSession(sid, conn)
|
||||
|
||||
Reference in New Issue
Block a user