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:
54
config.go
54
config.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
datastore.go
31
datastore.go
@@ -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 {
|
||||||
mailPath := filepath.Join(path, "mail")
|
Error("Error getting datastore path: %v", err)
|
||||||
return &DataStore{path: path, mailPath: mailPath}
|
return nil
|
||||||
}
|
}
|
||||||
rev.ERROR.Printf("No value configured for datastore.path")
|
if path == "" {
|
||||||
return nil
|
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
|
// 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
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
|
* 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...))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user