Переглянути джерело

- renamed envelope package to mail because *envelope.Envelope didn't make sense. *email.Envelope reads better
- renamed saveMailChan to conveyor to be more concise
- merged validateRcptChan to conveyor - simplified
- ProcessorLine renamed ProcessorStack to better describe structure
- Default processor now a constant, used when no processor set in config
- Renamed Shutdown and Initialize types used for helping to pass closures that meet Shutdowner/Initializer interfaces, improves readability
- config extractor now supports omitempty json struct tag
- server now checks RCPTO using backend gateway
- rename RemoteAddress to RemoteIP for clarity (form a module point of view, it now looks like mail.Address{} instead of envelope.EmailAddress{})
- updated the sample config
- backend_name removed from config

flashmob 8 роки тому
батько
коміт
f7a20328cc

+ 21 - 11
backends/backend.go

@@ -2,8 +2,8 @@ package backends
 
 import (
 	"fmt"
-	"github.com/flashmob/go-guerrilla/envelope"
 	"github.com/flashmob/go-guerrilla/log"
+	"github.com/flashmob/go-guerrilla/mail"
 	"reflect"
 	"strconv"
 	"strings"
@@ -34,8 +34,8 @@ type processorConstructor func() Decorator
 // whether the message was processed successfully.
 type Backend interface {
 	// Public methods
-	Process(*envelope.Envelope) Result
-	ValidateRcpt(e *envelope.Envelope) RcptError
+	Process(*mail.Envelope) Result
+	ValidateRcpt(e *mail.Envelope) RcptError
 	Initialize(BackendConfig) error
 	Shutdown() error
 }
@@ -92,18 +92,18 @@ type ProcessorShutdowner interface {
 	Shutdown() error
 }
 
-type Initialize func(backendConfig BackendConfig) error
-type Shutdown func() error
+type InitializeWith func(backendConfig BackendConfig) error
+type ShutdownWith func() error
 
 // Satisfy ProcessorInitializer interface
 // So we can now pass an anonymous function that implements ProcessorInitializer
-func (i Initialize) Initialize(backendConfig BackendConfig) error {
+func (i InitializeWith) Initialize(backendConfig BackendConfig) error {
 	// delegate to the anonymous function
 	return i(backendConfig)
 }
 
-// satisfy ProcessorShutdowner interface, same concept as Initialize type
-func (s Shutdown) Shutdown() error {
+// satisfy ProcessorShutdowner interface, same concept as InitializeWith type
+func (s ShutdownWith) Shutdown() error {
 	// delegate
 	return s()
 }
@@ -137,6 +137,10 @@ func New(backendName string, backendConfig BackendConfig, l log.Logger) (Backend
 	return b, nil
 }
 
+func convertError(name string) error {
+	return fmt.Errorf("failed to load backend config (%s)", name)
+}
+
 func GetBackend() Backend {
 	return b
 }
@@ -242,11 +246,17 @@ func (s *Service) ExtractConfig(configData BackendConfig, configType BaseConfig)
 		f := v.Field(i)
 		// read the tags of the config struct
 		field_name := t.Field(i).Tag.Get("json")
+		omitempty := false
 		if len(field_name) > 0 {
 			// parse the tag to
 			// get the field name from struct tag
 			split := strings.Split(field_name, ",")
 			field_name = split[0]
+			if len(split) > 1 {
+				if split[1] == "omitempty" {
+					omitempty = true
+				}
+			}
 		} else {
 			// could have no tag
 			// so use the reflected field name
@@ -258,21 +268,21 @@ func (s *Service) ExtractConfig(configData BackendConfig, configType BaseConfig)
 				v.Field(i).SetInt(int64(intVal))
 			} else if intVal, converted := configData[field_name].(int); converted {
 				v.Field(i).SetInt(int64(intVal))
-			} else {
+			} else if !omitempty {
 				return configType, convertError("property missing/invalid: '" + field_name + "' of expected type: " + f.Type().Name())
 			}
 		}
 		if f.Type().Name() == "string" {
 			if stringVal, converted := configData[field_name].(string); converted {
 				v.Field(i).SetString(stringVal)
-			} else {
+			} else if !omitempty {
 				return configType, convertError("missing/invalid: '" + field_name + "' of type: " + f.Type().Name())
 			}
 		}
 		if f.Type().Name() == "bool" {
 			if boolVal, converted := configData[field_name].(bool); converted {
 				v.Field(i).SetBool(boolVal)
-			} else {
+			} else if !omitempty {
 				return configType, convertError("missing/invalid: '" + field_name + "' of type: " + f.Type().Name())
 			}
 		}

+ 29 - 29
backends/gateway.go

@@ -7,7 +7,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/response"
 	"strings"
 )
@@ -17,10 +17,9 @@ import (
 // via a channel. Shutting down via Shutdown() will stop all workers.
 // The rest of this program always talks to the backend via this gateway.
 type BackendGateway struct {
-	// channel for sending envelopes to process and save
-	saveMailChan chan *workerMsg
-	// channel for validating the last recipient added to the envelope
-	validateRcptChan chan *workerMsg
+	// channel for distributing envelopes to workers
+	conveyor chan *workerMsg
+
 	// waits for backend workers to start/stop
 	wg sync.WaitGroup
 	w  *Worker
@@ -33,15 +32,18 @@ type BackendGateway struct {
 }
 
 type GatewayConfig struct {
-	WorkersSize   int    `json:"save_workers_size,omitempty"`
-	ProcessorLine string `json:"process_stack,omitempty"`
+	WorkersSize    int    `json:"save_workers_size,omitempty"`
+	ProcessorStack string `json:"process_stack,omitempty"`
 }
 
-// savePayload is what get placed on the BackendGateway.saveMailChan channel
+// workerMsg is what get placed on the BackendGateway.saveMailChan channel
 type workerMsg struct {
-	mail *envelope.Envelope
+	// The email data
+	e *mail.Envelope
 	// savedNotify is used to notify that the save operation completed
 	notifyMe chan *notifyMsg
+	// select the task type
+	task SelectTask
 }
 
 // possible values for state
@@ -49,9 +51,10 @@ const (
 	BackendStateRunning = iota
 	BackendStateShuttered
 	BackendStateError
-)
 
-const ProcessTimeout = time.Second * 30
+	processTimeout   = time.Second * 30
+	defaultProcessor = "Debugger"
+)
 
 type backendState int
 
@@ -60,13 +63,13 @@ func (s backendState) String() string {
 }
 
 // Process distributes an envelope to one of the backend workers
-func (gw *BackendGateway) Process(e *envelope.Envelope) Result {
+func (gw *BackendGateway) Process(e *mail.Envelope) Result {
 	if gw.State != BackendStateRunning {
 		return NewResult(response.Canned.FailBackendNotRunning + gw.State.String())
 	}
 	// place on the channel so that one of the save mail workers can pick it up
 	savedNotify := make(chan *notifyMsg)
-	gw.saveMailChan <- &workerMsg{e, savedNotify}
+	gw.conveyor <- &workerMsg{e, savedNotify, TaskSaveMail}
 	// wait for the save to complete
 	// or timeout
 	select {
@@ -76,7 +79,7 @@ func (gw *BackendGateway) Process(e *envelope.Envelope) Result {
 		}
 		return NewResult(response.Canned.SuccessMessageQueued + status.queuedID)
 
-	case <-time.After(ProcessTimeout):
+	case <-time.After(processTimeout):
 		Log().Infof("Backend has timed out")
 		return NewResult(response.Canned.FailBackendTimeout)
 	}
@@ -85,13 +88,13 @@ func (gw *BackendGateway) Process(e *envelope.Envelope) Result {
 
 // ValidateRcpt asks one of the workers to validate the recipient
 // Only the last recipient appended to e.RcptTo will be validated.
-func (gw *BackendGateway) ValidateRcpt(e *envelope.Envelope) RcptError {
+func (gw *BackendGateway) ValidateRcpt(e *mail.Envelope) RcptError {
 	if gw.State != BackendStateRunning {
 		return StorageNotAvailable
 	}
 	// place on the channel so that one of the save mail workers can pick it up
 	notify := make(chan *notifyMsg)
-	gw.validateRcptChan <- &workerMsg{e, notify}
+	gw.conveyor <- &workerMsg{e, notify, TaskValidateRcpt}
 	// wait for the validation to complete
 	// or timeout
 	select {
@@ -112,7 +115,7 @@ func (gw *BackendGateway) Shutdown() error {
 	gw.Lock()
 	defer gw.Unlock()
 	if gw.State != BackendStateShuttered {
-		close(gw.saveMailChan) // workers will stop
+		close(gw.conveyor) // workers will stop
 		// wait for workers to stop
 		gw.wg.Wait()
 		Svc.shutdown()
@@ -141,10 +144,11 @@ func (gw *BackendGateway) Reinitialize() error {
 // This function uses the config value process_stack to figure out which Decorator to use
 func (gw *BackendGateway) newProcessorLine() Processor {
 	var decorators []Decorator
-	if len(gw.gwConfig.ProcessorLine) == 0 {
-		return nil
+	cfg := strings.ToLower(strings.TrimSpace(gw.gwConfig.ProcessorStack))
+	if len(cfg) == 0 {
+		cfg = defaultProcessor
 	}
-	line := strings.Split(strings.ToLower(gw.gwConfig.ProcessorLine), "|")
+	line := strings.Split(cfg, "|")
 	for i := range line {
 		name := line[len(line)-1-i] // reverse order, since decorators are stacked
 		if makeFunc, ok := processors[name]; ok {
@@ -159,12 +163,9 @@ func (gw *BackendGateway) newProcessorLine() Processor {
 // loadConfig loads the config for the GatewayConfig
 func (gw *BackendGateway) loadConfig(cfg BackendConfig) error {
 	configType := BaseConfig(&GatewayConfig{})
-	if _, ok := cfg["process_stack"]; !ok {
-		cfg["process_stack"] = "Debugger"
-	}
-	if _, ok := cfg["save_workers_size"]; !ok {
-		cfg["save_workers_size"] = 1
-	}
+	// Note: treat config values as immutable
+	// if you need to change a config value, change in the file then
+	// send a SIGHUP
 	bcfg, err := Svc.ExtractConfig(cfg, configType)
 	if err != nil {
 		return err
@@ -192,13 +193,12 @@ func (gw *BackendGateway) Initialize(cfg BackendConfig) error {
 		if err := Svc.initialize(cfg); err != nil {
 			return err
 		}
-		gw.saveMailChan = make(chan *workerMsg, workersSize)
-		gw.validateRcptChan = make(chan *workerMsg, workersSize)
+		gw.conveyor = make(chan *workerMsg, workersSize)
 		// start our workers
 		gw.wg.Add(workersSize)
 		for i := 0; i < workersSize; i++ {
 			go func(workerId int) {
-				gw.w.workDispatcher(gw.saveMailChan, gw.validateRcptChan, lines[workerId], workerId+1)
+				gw.w.workDispatcher(gw.conveyor, lines[workerId], workerId+1)
 				gw.wg.Done()
 			}(i)
 		}

+ 2 - 2
backends/p_compressor.go

@@ -3,7 +3,7 @@ package backends
 import (
 	"bytes"
 	"compress/zlib"
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"io"
 	"sync"
 )
@@ -91,7 +91,7 @@ func (c *compressor) clear() {
 
 func Compressor() Decorator {
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 			if task == TaskSaveMail {
 				compressor := newCompressor()
 				compressor.set([]byte(e.DeliveryHeader), &e.Data)

+ 4 - 4
backends/p_debugger.go

@@ -1,7 +1,7 @@
 package backends
 
 import (
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 )
 
 // ----------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ import (
 // Output        : none (only output to the log if enabled)
 // ----------------------------------------------------------------------------------
 func init() {
-	processors["debugger"] = func() Decorator {
+	processors[defaultProcessor] = func() Decorator {
 		return Debugger()
 	}
 }
@@ -27,7 +27,7 @@ type debuggerConfig struct {
 
 func Debugger() Decorator {
 	var config *debuggerConfig
-	initFunc := Initialize(func(backendConfig BackendConfig) error {
+	initFunc := InitializeWith(func(backendConfig BackendConfig) error {
 		configType := BaseConfig(&debuggerConfig{})
 		bcfg, err := Svc.ExtractConfig(backendConfig, configType)
 		if err != nil {
@@ -38,7 +38,7 @@ func Debugger() Decorator {
 	})
 	Svc.AddInitializer(initFunc)
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 			if task == TaskSaveMail {
 				if config.LogReceivedMails {
 					Log().Infof("Mail from: %s / to: %v", e.MailFrom.String(), e.RcptTo)

+ 7 - 11
backends/p_guerrilla_db_redis.go

@@ -5,7 +5,7 @@ import (
 	"compress/zlib"
 	"database/sql"
 	"fmt"
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/garyburd/redigo/redis"
 	"github.com/go-sql-driver/mysql"
 	"io"
@@ -27,7 +27,7 @@ import (
 // Output        :
 // ----------------------------------------------------------------------------------
 func init() {
-	processors["guerrilla-redis-db"] = func() Decorator {
+	processors["GuerrillaRedisDB"] = func() Decorator {
 		return GuerrillaDbReddis()
 	}
 }
@@ -60,10 +60,6 @@ type guerrillaDBAndRedisConfig struct {
 	PrimaryHost        string `json:"primary_mail_host"`
 }
 
-func convertError(name string) error {
-	return fmt.Errorf("failed to load backend config (%s)", name)
-}
-
 // Load the backend config for the backend. It has already been unmarshalled
 // from the main config file 'backend' config "backend_config"
 // Now we need to convert each type and copy into the guerrillaDBAndRedisConfig struct
@@ -317,7 +313,7 @@ func GuerrillaDbReddis() Decorator {
 
 	var redisErr error
 
-	Svc.AddInitializer(Initialize(func(backendConfig BackendConfig) error {
+	Svc.AddInitializer(InitializeWith(func(backendConfig BackendConfig) error {
 		configType := BaseConfig(&guerrillaDBAndRedisConfig{})
 		bcfg, err := Svc.ExtractConfig(backendConfig, configType)
 		if err != nil {
@@ -366,9 +362,9 @@ func GuerrillaDbReddis() Decorator {
 	data := newCompressedData()
 
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 			if task == TaskSaveMail {
-				Log().Debug("Got mail from chan", e.RemoteAddress)
+				Log().Debug("Got mail from chan", e.RemoteIP)
 				to = trimToLimit(strings.TrimSpace(e.RcptTo[0].User)+"@"+g.config.PrimaryHost, 255)
 				e.Helo = trimToLimit(e.Helo, 255)
 				e.RcptTo[0].Host = trimToLimit(e.RcptTo[0].Host, 255)
@@ -382,7 +378,7 @@ func GuerrillaDbReddis() Decorator {
 				// Add extra headers
 				var addHead string
 				addHead += "Delivered-To: " + to + "\r\n"
-				addHead += "Received: from " + e.Helo + " (" + e.Helo + "  [" + e.RemoteAddress + "])\r\n"
+				addHead += "Received: from " + e.Helo + " (" + e.Helo + "  [" + e.RemoteIP + "])\r\n"
 				addHead += "	by " + e.RcptTo[0].Host + " with SMTP id " + hash + "@" + e.RcptTo[0].Host + ";\r\n"
 				addHead += "	" + time.Now().Format(time.RFC1123Z) + "\r\n"
 
@@ -414,7 +410,7 @@ func GuerrillaDbReddis() Decorator {
 					data.String(),
 					hash,
 					trimToLimit(to, 255),
-					e.RemoteAddress,
+					e.RemoteIP,
 					trimToLimit(e.MailFrom.String(), 255),
 					e.TLS)
 				return c.Process(e, task)

+ 2 - 2
backends/p_hasher.go

@@ -7,7 +7,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 )
 
 // ----------------------------------------------------------------------------------
@@ -32,7 +32,7 @@ func init() {
 // It appends the hashes to envelope's Hashes slice.
 func Hasher() Decorator {
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 
 			if task == TaskSaveMail {
 				// base hash, use subject from and timestamp-nano

+ 4 - 4
backends/p_header.go

@@ -1,7 +1,7 @@
 package backends
 
 import (
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"strings"
 	"time"
 )
@@ -36,7 +36,7 @@ func Header() Decorator {
 
 	var config *HeaderConfig
 
-	Svc.AddInitializer(Initialize(func(backendConfig BackendConfig) error {
+	Svc.AddInitializer(InitializeWith(func(backendConfig BackendConfig) error {
 		configType := BaseConfig(&HeaderConfig{})
 		bcfg, err := Svc.ExtractConfig(backendConfig, configType)
 		if err != nil {
@@ -47,7 +47,7 @@ func Header() Decorator {
 	}))
 
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 			if task == TaskSaveMail {
 				to := strings.TrimSpace(e.RcptTo[0].User) + "@" + config.PrimaryHost
 				hash := "unknown"
@@ -56,7 +56,7 @@ func Header() Decorator {
 				}
 				var addHead string
 				addHead += "Delivered-To: " + to + "\n"
-				addHead += "Received: from " + e.Helo + " (" + e.Helo + "  [" + e.RemoteAddress + "])\n"
+				addHead += "Received: from " + e.Helo + " (" + e.Helo + "  [" + e.RemoteIP + "])\n"
 				if len(e.RcptTo) > 0 {
 					addHead += "	by " + e.RcptTo[0].Host + " with SMTP id " + hash + "@" + e.RcptTo[0].Host + ";\n"
 				}

+ 2 - 2
backends/p_headers_parser.go

@@ -1,7 +1,7 @@
 package backends
 
 import (
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 )
 
 // ----------------------------------------------------------------------------------
@@ -23,7 +23,7 @@ func init() {
 
 func HeadersParser() Decorator {
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 			if task == TaskSaveMail {
 				e.ParseHeaders()
 				// next processor

+ 5 - 5
backends/p_mysql.go

@@ -5,7 +5,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/go-sql-driver/mysql"
 
 	"github.com/flashmob/go-guerrilla/response"
@@ -134,7 +134,7 @@ func MySql() Decorator {
 	var db *sql.DB
 	mp := &MysqlProcessor{}
 
-	Svc.AddInitializer(Initialize(func(backendConfig BackendConfig) error {
+	Svc.AddInitializer(InitializeWith(func(backendConfig BackendConfig) error {
 		configType := BaseConfig(&MysqlProcessorConfig{})
 		bcfg, err := Svc.ExtractConfig(backendConfig, configType)
 		if err != nil {
@@ -151,7 +151,7 @@ func MySql() Decorator {
 	}))
 
 	// shutdown
-	Svc.AddShutdowner(Shutdown(func() error {
+	Svc.AddShutdowner(ShutdownWith(func() error {
 		if db != nil {
 			return db.Close()
 		}
@@ -159,7 +159,7 @@ func MySql() Decorator {
 	}))
 
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 
 			if task == TaskSaveMail {
 				var to, body string
@@ -202,7 +202,7 @@ func MySql() Decorator {
 				vals = append(vals,
 					hash,
 					to,
-					e.RemoteAddress,
+					e.RemoteIP,
 					trimToLimit(e.MailFrom.String(), 255),
 					e.TLS)
 

+ 4 - 4
backends/p_redis.go

@@ -3,7 +3,7 @@ package backends
 import (
 	"fmt"
 
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/response"
 
 	"github.com/garyburd/redigo/redis"
@@ -61,7 +61,7 @@ func Redis() Decorator {
 	var config *RedisProcessorConfig
 	redisClient := &RedisProcessor{}
 	// read the config into RedisProcessorConfig
-	Svc.AddInitializer(Initialize(func(backendConfig BackendConfig) error {
+	Svc.AddInitializer(InitializeWith(func(backendConfig BackendConfig) error {
 		configType := BaseConfig(&RedisProcessorConfig{})
 		bcfg, err := Svc.ExtractConfig(backendConfig, configType)
 		if err != nil {
@@ -75,7 +75,7 @@ func Redis() Decorator {
 		return nil
 	}))
 	// When shutting down
-	Svc.AddShutdowner(Shutdown(func() error {
+	Svc.AddShutdowner(ShutdownWith(func() error {
 		if redisClient.isConnected {
 			return redisClient.conn.Close()
 		}
@@ -85,7 +85,7 @@ func Redis() Decorator {
 	var redisErr error
 
 	return func(c Processor) Processor {
-		return ProcessWith(func(e *envelope.Envelope, task SelectTask) (Result, error) {
+		return ProcessWith(func(e *mail.Envelope, task SelectTask) (Result, error) {
 
 			if task == TaskSaveMail {
 				hash := ""

+ 5 - 5
backends/processor.go

@@ -1,7 +1,7 @@
 package backends
 
 import (
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 )
 
 type SelectTask int
@@ -25,14 +25,14 @@ var BackendResultOK = NewResult("200 OK")
 
 // Our processor is defined as something that processes the envelope and returns a result and error
 type Processor interface {
-	Process(*envelope.Envelope, SelectTask) (Result, error)
+	Process(*mail.Envelope, SelectTask) (Result, error)
 }
 
 // Signature of Processor
-type ProcessWith func(*envelope.Envelope, SelectTask) (Result, error)
+type ProcessWith func(*mail.Envelope, SelectTask) (Result, error)
 
 // Make ProcessorFunc will satisfy the Processor interface
-func (f ProcessWith) Process(e *envelope.Envelope, task SelectTask) (Result, error) {
+func (f ProcessWith) Process(e *mail.Envelope, task SelectTask) (Result, error) {
 	// delegate to the anonymous function
 	return f(e, task)
 }
@@ -43,6 +43,6 @@ type DefaultProcessor struct{}
 
 // do nothing except return the result
 // (this is the last call in the decorator stack, if it got here, then all is good)
-func (w DefaultProcessor) Process(e *envelope.Envelope, task SelectTask) (Result, error) {
+func (w DefaultProcessor) Process(e *mail.Envelope, task SelectTask) (Result, error) {
 	return BackendResultOK, nil
 }

+ 23 - 19
backends/worker.go

@@ -7,7 +7,7 @@ import (
 
 type Worker struct{}
 
-func (w *Worker) workDispatcher(workIn chan *workerMsg, validateRcpt chan *workerMsg, p Processor, workerId int) {
+func (w *Worker) workDispatcher(workIn chan *workerMsg, p Processor, workerId int) {
 
 	defer func() {
 		if r := recover(); r != nil {
@@ -22,29 +22,33 @@ func (w *Worker) workDispatcher(workIn chan *workerMsg, validateRcpt chan *worke
 	for {
 		select {
 		case msg := <-workIn:
+
 			if msg == nil {
 				Log().Debug("No more messages from saveMail")
 				return
 			}
-			// process the email here
-			// TODO we should check the err
-			result, _ := p.Process(msg.mail, TaskSaveMail)
-			if result.Code() < 300 {
-				// if all good, let the gateway know that it was queued
-				msg.notifyMe <- &notifyMsg{nil, msg.mail.QueuedId}
-			} else {
-				// notify the gateway about the error
-				msg.notifyMe <- &notifyMsg{err: errors.New(result.String())}
-			}
-		case msg := <-validateRcpt:
-			_, err := p.Process(msg.mail, TaskValidateRcpt)
-			if err != nil {
-				// validation failed
-				msg.notifyMe <- &notifyMsg{err: err}
-			} else {
-				// all good.
-				msg.notifyMe <- &notifyMsg{err: nil}
+			if msg.task == TaskSaveMail {
+				// process the email here
+				// TODO we should check the err
+				result, _ := p.Process(msg.e, TaskSaveMail)
+				if result.Code() < 300 {
+					// if all good, let the gateway know that it was queued
+					msg.notifyMe <- &notifyMsg{nil, msg.e.QueuedId}
+				} else {
+					// notify the gateway about the error
+					msg.notifyMe <- &notifyMsg{err: errors.New(result.String())}
+				}
+			} else if msg.task == TaskValidateRcpt {
+				_, err := p.Process(msg.e, TaskValidateRcpt)
+				if err != nil {
+					// validation failed
+					msg.notifyMe <- &notifyMsg{err: err}
+				} else {
+					// all good.
+					msg.notifyMe <- &notifyMsg{err: nil}
+				}
 			}
+
 		}
 
 	}

+ 4 - 4
client.go

@@ -5,8 +5,8 @@ import (
 	"bytes"
 	"crypto/tls"
 	"fmt"
-	"github.com/flashmob/go-guerrilla/envelope"
 	"github.com/flashmob/go-guerrilla/log"
+	"github.com/flashmob/go-guerrilla/mail"
 	"net"
 	"net/textproto"
 	"sync"
@@ -30,7 +30,7 @@ const (
 )
 
 type client struct {
-	*envelope.Envelope
+	*mail.Envelope
 	ID          uint64
 	ConnectedAt time.Time
 	KilledAt    time.Time
@@ -54,7 +54,7 @@ type client struct {
 func NewClient(conn net.Conn, clientID uint64, logger log.Logger) *client {
 	c := &client{
 		conn:        conn,
-		Envelope:    envelope.NewEnvelope(getRemoteAddr(conn), clientID),
+		Envelope:    mail.NewEnvelope(getRemoteAddr(conn), clientID),
 		ConnectedAt: time.Now(),
 		bufin:       newSMTPBufferedReader(conn),
 		bufout:      bufio.NewWriter(conn),
@@ -118,7 +118,7 @@ func (c *client) resetTransaction() {
 // A transaction starts after a MAIL command gets issued by the client.
 // Call resetTransaction to end the transaction
 func (c *client) isInTransaction() bool {
-	isMailFromEmpty := c.MailFrom == (envelope.EmailAddress{})
+	isMailFromEmpty := c.MailFrom == (mail.Address{})
 	if isMailFromEmpty {
 		return false
 	}

+ 7 - 9
cmd/guerrillad/serve.go

@@ -95,14 +95,14 @@ func subscribeBackendEvent(event guerrilla.Event, backend backends.Backend, app
 			logger.WithError(err).Warn("Backend failed to shutdown")
 			return
 		}
-		newBackend, newErr := backends.New(cmdConfig.BackendName, cmdConfig.BackendConfig, logger)
+		newBackend, newErr := backends.New("", cmdConfig.BackendConfig, logger)
 		if newErr != nil {
 			// this will continue using old backend
 			logger.WithError(newErr).Error("Error while loading the backend")
 		} else {
 			// swap to the bew backend (assuming old backend was shutdown so it can be safely swapped)
 			backend = newBackend
-			logger.Info("Backend started:", cmdConfig.BackendName)
+			logger.Info("Backend started")
 		}
 	})
 }
@@ -132,7 +132,7 @@ func serve(cmd *cobra.Command, args []string) {
 
 	// Backend setup
 	var backend backends.Backend
-	backend, err = backends.New(cmdConfig.BackendName, cmdConfig.BackendConfig, mainlog)
+	backend, err = backends.New("", cmdConfig.BackendConfig, mainlog)
 	if err != nil {
 		mainlog.WithError(err).Fatalf("Error while loading the backend")
 	}
@@ -163,7 +163,6 @@ func serve(cmd *cobra.Command, args []string) {
 		mainlog.WithError(err).Error("Error(s) when starting server(s)")
 	}
 	subscribeBackendEvent(guerrilla.EventConfigBackendConfig, backend, app)
-	subscribeBackendEvent(guerrilla.EventConfigBackendName, backend, app)
 	// Write out our PID
 	writePid(cmdConfig.PidFile)
 	// ...and write out our pid whenever the file name changes in the config
@@ -185,7 +184,6 @@ func serve(cmd *cobra.Command, args []string) {
 // the the command line interface.
 type CmdConfig struct {
 	guerrilla.AppConfig
-	BackendName   string                 `json:"backend_name"`
 	BackendConfig backends.BackendConfig `json:"backend_config"`
 }
 
@@ -204,16 +202,16 @@ func (c *CmdConfig) emitChangeEvents(oldConfig *CmdConfig, app guerrilla.Guerril
 	if !reflect.DeepEqual((*c).BackendConfig, (*oldConfig).BackendConfig) {
 		app.Publish(guerrilla.EventConfigBackendConfig, c)
 	}
-	if c.BackendName != oldConfig.BackendName {
-		app.Publish(guerrilla.EventConfigBackendName, c)
-	}
 	// call other emitChangeEvents
 	c.AppConfig.EmitChangeEvents(&oldConfig.AppConfig, app)
 }
 
 // ReadConfig which should be called at startup, or when a SIG_HUP is caught
 func readConfig(path string, pidFile string, config *CmdConfig) error {
-	// load in the config.
+	// Load in the config.
+	// Note here is the only place we can make an exception to the
+	// "treat config values as immutable". For example, here the
+	// command line flags can override config values
 	data, err := ioutil.ReadFile(path)
 	if err != nil {
 		return fmt.Errorf("Could not read config file: %s", err.Error())

+ 3 - 7
cmd/guerrillad/serve_test.go

@@ -32,7 +32,6 @@ var configJsonA = `
       "guerrillamail.net",
       "guerrillamail.org"
     ],
-    "backend_name": "dummy",
     "backend_config": {
         "log_received_mails": true
     },
@@ -80,7 +79,6 @@ var configJsonB = `
       "guerrillamail.net",
       "guerrillamail.org"
     ],
-    "backend_name": "dummy",
     "backend_config": {
         "log_received_mails": false
     },
@@ -172,7 +170,6 @@ var configJsonD = `
       "guerrillamail.net",
       "guerrillamail.org"
     ],
-    "backend_name": "dummy",
     "backend_config": {
         "log_received_mails": false
     },
@@ -253,8 +250,7 @@ func TestCmdConfigChangeEvents(t *testing.T) {
 
 	expectedEvents := map[guerrilla.Event]bool{
 		guerrilla.EventConfigBackendConfig: false,
-		guerrilla.EventConfigBackendName:   false,
-		guerrilla.EventConfigEvServerNew:   false,
+		guerrilla.EventConfigServerNew:     false,
 	}
 	mainlog, _ = log.GetLogger("off")
 
@@ -366,7 +362,7 @@ func TestServe(t *testing.T) {
 	}
 	if read, err := ioutil.ReadAll(fd); err == nil {
 		logOutput := string(read)
-		if i := strings.Index(logOutput, "Backend started:dummy"); i < 0 {
+		if i := strings.Index(logOutput, "Backend started"); i < 0 {
 			t.Error("Dummy backend not restared")
 		}
 	}
@@ -648,7 +644,7 @@ func TestAllowedHostsEvent(t *testing.T) {
 
 	// now change the config by adding a host to allowed hosts
 
-	newConf := conf // copy the cmdConfg
+	newConf := conf
 	newConf.AllowedHosts = append(newConf.AllowedHosts, "grr.la")
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
 		ioutil.WriteFile("configJsonD.json", []byte(jsonbytes), 0644)

+ 1 - 1
config.go

@@ -98,7 +98,7 @@ func (c *AppConfig) EmitChangeEvents(oldConfig *AppConfig, app Guerrilla) {
 			newServer.emitChangeEvents(oldServer, app)
 		} else {
 			// start new server
-			app.Publish(EventConfigEvServerNew, newServer)
+			app.Publish(EventConfigServerNew, newServer)
 		}
 
 	}

+ 1 - 3
config_test.go

@@ -24,7 +24,6 @@ var configJsonA = `
     "log_level" : "debug",
     "pid_file" : "/var/run/go-guerrilla.pid",
     "allowed_hosts": ["spam4.me","grr.la"],
-    "backend_name" : "dummy",
     "backend_config" :
         {
             "log_received_mails" : true
@@ -98,7 +97,6 @@ var configJsonB = `
     "log_level" : "debug",
     "pid_file" : "/var/run/different-go-guerrilla.pid",
     "allowed_hosts": ["spam4.me","grr.la","newhost.com"],
-    "backend_name" : "dummy",
     "backend_config" :
         {
             "log_received_mails" : true
@@ -215,7 +213,7 @@ func TestConfigChangeEvents(t *testing.T) {
 		EventConfigLogFile:         false,
 		EventConfigLogLevel:        false,
 		EventConfigAllowedHosts:    false,
-		EventConfigEvServerNew:     false, // 127.0.0.1:4654 will be added
+		EventConfigServerNew:       false, // 127.0.0.1:4654 will be added
 		EventConfigServerRemove:    false, // 127.0.0.1:9999 server removed
 		EventConfigServerStop:      false, // 127.0.0.1:3333: server (disabled)
 		EventConfigServerLogFile:   false, // 127.0.0.1:2526

+ 1 - 7
event.go

@@ -19,12 +19,10 @@ const (
 	EventConfigLogReopen
 	// when log level changed
 	EventConfigLogLevel
-	// when the backend changed
-	EventConfigBackendName
 	// when the backend's config changed
 	EventConfigBackendConfig
 	// when a new server was added
-	EventConfigEvServerNew
+	EventConfigServerNew
 	// when an existing server was removed
 	EventConfigServerRemove
 	// when a new server config was detected (general event)
@@ -53,7 +51,6 @@ var eventList = [...]string{
 	"config_change:reopen_log_file",
 	"config_change:log_level",
 	"config_change:backend_config",
-	"config_change:backend_name",
 	"server_change:new_server",
 	"server_change:remove_server",
 	"server_change:update_config",
@@ -64,9 +61,6 @@ var eventList = [...]string{
 	"server_change:timeout",
 	"server_change:max_clients",
 	"server_change:tls_config",
-	"backend:proc_config_load",
-	"backend:proc_init",
-	"backend:proc_shutdown",
 }
 
 func (e Event) String() string {

+ 0 - 1
goguerrilla.conf.sample

@@ -9,7 +9,6 @@
       "guerrillamail.org"
     ],
     "pid_file" : "/var/run/go-guerrilla.pid",
-    "backend_name": "dummy",
     "backend_config": {
         "log_received_mails": true
     },

+ 2 - 2
guerrilla.go

@@ -194,7 +194,7 @@ func (g *guerrilla) subscribeEvents() {
 				// it will change server's logger when the next client gets accepted
 				server.mainlogStore.Store(l)
 			})
-			g.mainlog().Infof("main log for new clients changed to to [%s]", c.LogFile)
+			g.mainlog().Infof("main log for new clients changed to [%s]", c.LogFile)
 		} else {
 			g.mainlog().WithError(err).Errorf("main logging change failed [%s]", c.LogFile)
 		}
@@ -222,7 +222,7 @@ func (g *guerrilla) subscribeEvents() {
 	})
 
 	// add a new server to the config & start
-	g.Subscribe(EventConfigEvServerNew, func(sc *ServerConfig) {
+	g.Subscribe(EventConfigServerNew, func(sc *ServerConfig) {
 		if _, err := g.findServer(sc.ListenInterface); err != nil {
 			// not found, lets add it
 			if err := g.makeServers(); err != nil {

+ 20 - 19
envelope/envelope.go → mail/envelope.go

@@ -1,4 +1,4 @@
-package envelope
+package mail
 
 import (
 	"bufio"
@@ -17,32 +17,32 @@ import (
 	"time"
 )
 
-const maxHeaderChunk = iota + (1<<10)*3 // 3KB
+const maxHeaderChunk = iota + (3 << 10) // 3KB
 
-// EmailAddress encodes an email address of the form `<user@host>`
-type EmailAddress struct {
+// Address encodes an email address of the form `<user@host>`
+type Address struct {
 	User string
 	Host string
 }
 
-func (ep *EmailAddress) String() string {
+func (ep *Address) String() string {
 	return fmt.Sprintf("%s@%s", ep.User, ep.Host)
 }
 
-func (ep *EmailAddress) IsEmpty() bool {
+func (ep *Address) IsEmpty() bool {
 	return ep.User == "" && ep.Host == ""
 }
 
 // Email represents a single SMTP message.
 type Envelope struct {
 	// Remote IP address
-	RemoteAddress string
+	RemoteIP string
 	// Message sent in EHLO command
 	Helo string
 	// Sender
-	MailFrom EmailAddress
+	MailFrom Address
 	// Recipients
-	RcptTo []EmailAddress
+	RcptTo []Address
 	// Data stores the header and message body
 	Data bytes.Buffer
 	// Subject stores the subject of the email, extracted and decoded after calling ParseHeaders()
@@ -62,11 +62,10 @@ type Envelope struct {
 }
 
 func NewEnvelope(remoteAddr string, clientID uint64) *Envelope {
-
 	return &Envelope{
-		RemoteAddress: remoteAddr,
-		Values:        make(map[string]interface{}),
-		QueuedId:      queuedID(clientID),
+		RemoteIP: remoteAddr,
+		Values:   make(map[string]interface{}),
+		QueuedId: queuedID(clientID),
 	}
 }
 
@@ -125,16 +124,16 @@ func (e *Envelope) String() string {
 
 // ResetTransaction is called when the transaction is reset (but save connection)
 func (e *Envelope) ResetTransaction() {
-	e.MailFrom = EmailAddress{}
-	e.RcptTo = []EmailAddress{}
+	e.MailFrom = Address{}
+	e.RcptTo = []Address{}
 	// reset the data buffer, keep it allocated
 	e.Data.Reset()
 }
 
 // Seed is called when used with a new connection, once it's accepted
-func (e *Envelope) Reseed(remoteAddr string, clientID uint64) {
+func (e *Envelope) Reseed(RemoteIP string, clientID uint64) {
 	e.Subject = ""
-	e.RemoteAddress = remoteAddr
+	e.RemoteIP = RemoteIP
 	e.Helo = ""
 	e.Header = nil
 	e.TLS = false
@@ -144,11 +143,13 @@ func (e *Envelope) Reseed(remoteAddr string, clientID uint64) {
 	e.QueuedId = queuedID(clientID)
 }
 
-func (e *Envelope) PushRcpt(addr EmailAddress) {
+// PushRcpt adds a recipient email address to the envelope
+func (e *Envelope) PushRcpt(addr Address) {
 	e.RcptTo = append(e.RcptTo, addr)
 }
 
-func (e *Envelope) PopRcpt() EmailAddress {
+// Pop removes the last email address that was pushed to the envelope
+func (e *Envelope) PopRcpt() Address {
 	ret := e.RcptTo[len(e.RcptTo)-1]
 	e.RcptTo = e.RcptTo[:len(e.RcptTo)-1]
 	return ret

+ 20 - 19
server.go

@@ -13,8 +13,8 @@ import (
 	"time"
 
 	"github.com/flashmob/go-guerrilla/backends"
-	"github.com/flashmob/go-guerrilla/envelope"
 	"github.com/flashmob/go-guerrilla/log"
+	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/response"
 )
 
@@ -284,7 +284,7 @@ func (server *server) isShuttingDown() bool {
 func (server *server) handleClient(client *client) {
 	defer client.closeConn()
 	sc := server.configStore.Load().(ServerConfig)
-	server.log.Infof("Handle client [%s], id: %d", client.RemoteAddress, client.ID)
+	server.log.Infof("Handle client [%s], id: %d", client.RemoteIP, client.ID)
 
 	// Initial greeting
 	greeting := fmt.Sprintf("220 %s SMTP Guerrilla(%s) #%d (%d) %s gr:%d",
@@ -311,7 +311,7 @@ func (server *server) handleClient(client *client) {
 		} else if err := client.upgradeToTLS(tlsConfig); err == nil {
 			advertiseTLS = ""
 		} else {
-			server.log.WithError(err).Warnf("[%s] Failed TLS handshake", client.RemoteAddress)
+			server.log.WithError(err).Warnf("[%s] Failed TLS handshake", client.RemoteIP)
 			// server requires TLS, but can't handshake
 			client.kill()
 		}
@@ -331,17 +331,17 @@ func (server *server) handleClient(client *client) {
 			input, err := server.readCommand(client, sc.MaxSize)
 			server.log.Debugf("Client sent: %s", input)
 			if err == io.EOF {
-				server.log.WithError(err).Warnf("Client closed the connection: %s", client.RemoteAddress)
+				server.log.WithError(err).Warnf("Client closed the connection: %s", client.RemoteIP)
 				return
 			} else if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
-				server.log.WithError(err).Warnf("Timeout: %s", client.RemoteAddress)
+				server.log.WithError(err).Warnf("Timeout: %s", client.RemoteIP)
 				return
 			} else if err == LineLimitExceeded {
 				client.sendResponse(response.Canned.FailLineTooLong)
 				client.kill()
 				break
 			} else if err != nil {
-				server.log.WithError(err).Warnf("Read error: %s", client.RemoteAddress)
+				server.log.WithError(err).Warnf("Read error: %s", client.RemoteIP)
 				client.kill()
 				break
 			}
@@ -381,21 +381,22 @@ func (server *server) handleClient(client *client) {
 					client.sendResponse(response.Canned.FailNestedMailCmd)
 					break
 				}
-				mail := input[10:]
-				from := envelope.EmailAddress{}
-
-				if !(strings.Index(mail, "<>") == 0) &&
-					!(strings.Index(mail, " <>") == 0) {
+				addr := input[10:]
+				if !(strings.Index(addr, "<>") == 0) &&
+					!(strings.Index(addr, " <>") == 0) {
 					// Not Bounce, extract mail.
-					from, err = extractEmail(mail)
-				}
+					if from, err := extractEmail(addr); err != nil {
+						client.sendResponse(err)
+						break
+					} else {
+						client.MailFrom = from
+					}
 
-				if err != nil {
-					client.sendResponse(err)
 				} else {
-					client.MailFrom = from
-					client.sendResponse(response.Canned.SuccessMailCmd)
+					// bounce has empty from address
+					client.MailFrom = mail.Address{}
 				}
+				client.sendResponse(response.Canned.SuccessMailCmd)
 
 			case strings.Index(cmd, "RCPT TO:") == 0:
 				if len(client.RcptTo) > RFC2821LimitRecipients {
@@ -413,7 +414,7 @@ func (server *server) handleClient(client *client) {
 						rcptError := server.backend.ValidateRcpt(client.Envelope)
 						if rcptError != nil {
 							client.PopRcpt()
-							client.sendResponse(response.Canned.FailRcptCmd)
+							client.sendResponse(response.Canned.FailRcptCmd + rcptError.Error())
 						} else {
 							client.sendResponse(response.Canned.SuccessRcptCmd)
 						}
@@ -507,7 +508,7 @@ func (server *server) handleClient(client *client) {
 					advertiseTLS = ""
 					client.resetTransaction()
 				} else {
-					server.log.WithError(err).Warnf("[%s] Failed TLS handshake", client.RemoteAddress)
+					server.log.WithError(err).Warnf("[%s] Failed TLS handshake", client.RemoteIP)
 					// Don't disconnect, let the client decide if it wants to continue
 				}
 			}

+ 1 - 1
tests/guerrilla_test.go

@@ -76,7 +76,6 @@ var configJson = `
     "log_level" : "debug",
     "pid_file" : "/var/run/go-guerrilla.pid",
     "allowed_hosts": ["spam4.me","grr.la"],
-    "backend_name" : "dummy",
     "backend_config" :
         {
             "log_received_mails" : true
@@ -332,6 +331,7 @@ func TestRFC2821LimitRecipients(t *testing.T) {
 			}
 
 			for i := 0; i < 101; i++ {
+				fmt.Println(fmt.Sprintf("RCPT TO:test%[email protected]", i))
 				if _, err := Command(conn, bufin, fmt.Sprintf("RCPT TO:test%[email protected]", i)); err != nil {
 					t.Error("RCPT TO", err.Error())
 					break

+ 3 - 3
util.go

@@ -5,14 +5,14 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/flashmob/go-guerrilla/envelope"
+	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/response"
 )
 
 var extractEmailRegex, _ = regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you're drunk!
 
-func extractEmail(str string) (envelope.EmailAddress, error) {
-	email := envelope.EmailAddress{}
+func extractEmail(str string) (mail.Address, error) {
+	email := mail.Address{}
 	var err error
 	if len(str) > RFC2821LimitPath {
 		return email, errors.New(response.Canned.FailPathTooLong)