Bläddra i källkod

merged in the latest master & resolve any conflicts

flashmob 5 år sedan
förälder
incheckning
a99f336b22
31 ändrade filer med 492 tillägg och 299 borttagningar
  1. 10 7
      .travis.yml
  2. 146 0
      Gopkg.lock
  3. 32 0
      Gopkg.toml
  4. 6 9
      Makefile
  5. 8 6
      README.md
  6. 3 3
      api.go
  7. 79 14
      api_test.go
  8. 5 5
      backends/gateway.go
  9. 19 19
      backends/p_compressor.go
  10. 0 9
      backends/p_guerrilla_db_redis.go
  11. 1 1
      backends/p_redis.go
  12. 54 12
      backends/p_sql.go
  13. 6 6
      client.go
  14. 1 1
      cmd/guerrillad/serve.go
  15. 10 11
      cmd/guerrillad/serve_test.go
  16. 31 36
      config.go
  17. 1 2
      config_test.go
  18. 0 70
      glide.lock
  19. 0 20
      glide.yaml
  20. 4 0
      guerrilla.go
  21. 2 1
      guerrilla_unix.go
  22. 8 6
      log/hook.go
  23. 5 5
      log/log.go
  24. 7 7
      mail/envelope.go
  25. 0 2
      mocks/client.go
  26. 1 2
      response/quote.go
  27. 39 32
      server.go
  28. 6 5
      server_test.go
  29. 1 1
      tests/client.go
  30. 3 4
      tests/guerrilla_test.go
  31. 4 3
      version.go

+ 10 - 7
.travis.yml

@@ -1,19 +1,22 @@
 language: go
 sudo: false
 go:
-  - 1.7
-  - 1.8
   - 1.9
   - 1.10.x
+  - 1.11.x
+  - 1.12.x
   - master
 
+cache:
+  directories:
+    - $HOME/.cache/go-build
+    - $HOME/gopath/pkg/mod
+
 install:
-  - export GO15VENDOREXPERIMENT=1
-  - go get github.com/Masterminds/glide
-  - go install github.com/Masterminds/glide
-  - glide up
+  - go get -u github.com/golang/dep/cmd/dep
+  - dep ensure
 
 script:
   - ./.travis.gofmt.sh
   - make guerrillad
-  - make test
+  - make test

+ 146 - 0
Gopkg.lock

@@ -0,0 +1,146 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  digest = "1:0a2a75a7b0d611bf7ecb4e5a0054242815dc27e857b4b9f8ec62225993fd11b7"
+  name = "github.com/asaskevich/EventBus"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "68a521d7cbbb7a859c2608b06342f384b3bd5f5a"
+
+[[projects]]
+  digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65"
+  name = "github.com/go-sql-driver/mysql"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "72cd26f257d44c1114970e19afddcd812016007e"
+  version = "v1.4.1"
+
+[[projects]]
+  digest = "1:38ec74012390146c45af1f92d46e5382b50531247929ff3a685d2b2be65155ac"
+  name = "github.com/gomodule/redigo"
+  packages = [
+    "internal",
+    "redis"
+  ]
+  pruneopts = "UT"
+  revision = "9c11da706d9b7902c6da69c592f75637793fe121"
+  version = "v2.0.0"
+
+[[projects]]
+  digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
+  name = "github.com/inconshreveable/mousetrap"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
+  version = "v1.0"
+
+[[projects]]
+  digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de"
+  name = "github.com/konsorten/go-windows-terminal-sequences"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
+  version = "v1.0.2"
+
+[[projects]]
+  digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976"
+  name = "github.com/sirupsen/logrus"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
+  version = "v1.4.2"
+
+[[projects]]
+  digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
+  name = "github.com/spf13/cobra"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
+  version = "v0.0.3"
+
+[[projects]]
+  digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2"
+  name = "github.com/spf13/pflag"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
+  version = "v1.0.3"
+
+[[projects]]
+  branch = "master"
+  digest = "1:a167b5c532f3245f5a147ade26185f16d6ee8f8d3f6c9846f447e9d8b9705505"
+  name = "golang.org/x/net"
+  packages = [
+    "html",
+    "html/atom",
+    "html/charset"
+  ]
+  pruneopts = "UT"
+  revision = "f4e77d36d62c17c2336347bb2670ddbd02d092b7"
+
+[[projects]]
+  digest = "1:3fe612db5a4468ac2846ae481c22bb3250fa67cf03bccb00c06fa8723a3077a8"
+  name = "golang.org/x/sys"
+  packages = ["unix"]
+  pruneopts = "UT"
+  revision = "7dca6fe1f43775aa6d1334576870ff63f978f539"
+
+[[projects]]
+  digest = "1:8a0baffd5559acaa560f854d7d525c02f4fec2d4f8a214398556fb661a10f6e0"
+  name = "golang.org/x/text"
+  packages = [
+    "encoding",
+    "encoding/charmap",
+    "encoding/htmlindex",
+    "encoding/internal",
+    "encoding/internal/identifier",
+    "encoding/japanese",
+    "encoding/korean",
+    "encoding/simplifiedchinese",
+    "encoding/traditionalchinese",
+    "encoding/unicode",
+    "internal/gen",
+    "internal/language",
+    "internal/language/compact",
+    "internal/tag",
+    "internal/utf8internal",
+    "language",
+    "runes",
+    "transform",
+    "unicode/cldr"
+  ]
+  pruneopts = "UT"
+  revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
+  version = "v0.3.2"
+
+[[projects]]
+  digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3"
+  name = "google.golang.org/appengine"
+  packages = ["cloudsql"]
+  pruneopts = "UT"
+  revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610"
+  version = "v1.5.0"
+
+[[projects]]
+  digest = "1:6a8414c6457caa5db639b9f5c084a95b698f2b3cc011151a4b43224a1d8fe0f5"
+  name = "gopkg.in/iconv.v1"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "16a760eb7e186ae0e3aedda00d4a1daa4d0701d8"
+  version = "v1.1.1"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  input-imports = [
+    "github.com/asaskevich/EventBus",
+    "github.com/go-sql-driver/mysql",
+    "github.com/gomodule/redigo/redis",
+    "github.com/sirupsen/logrus",
+    "github.com/spf13/cobra",
+    "golang.org/x/net/html/charset",
+    "gopkg.in/iconv.v1"
+  ]
+  solver-name = "gps-cdcl"
+  solver-version = 1

+ 32 - 0
Gopkg.toml

@@ -0,0 +1,32 @@
+[[constraint]]
+  name = "github.com/go-sql-driver/mysql"
+  version = "1.3.0"
+
+[[constraint]]
+  name = "github.com/gomodule/redigo"
+  version = "~2.0.0"
+
+[[constraint]]
+  name = "github.com/sirupsen/logrus"
+  version = "~1.4.2"
+
+[[constraint]]
+  branch = "master"
+  name = "golang.org/x/net"
+
+[[constraint]]
+  name = "gopkg.in/iconv.v1"
+  version = "~1.1.1"
+
+[[constraint]]
+  name = "github.com/asaskevich/EventBus"
+  revision = "68a521d7cbbb7a859c2608b06342f384b3bd5f5a"
+
+# The following locks logrus to a particular version of x/sys
+[[override]]
+    name = "golang.org/x/sys"
+    revision = "7dca6fe1f43775aa6d1334576870ff63f978f539"
+
+[prune]
+  go-tests = true
+  unused-packages = true

+ 6 - 9
Makefile

@@ -10,25 +10,22 @@ LD_FLAGS := -X $(ROOT).Version=$(VERSION) -X $(ROOT).Commit=$(COMMIT) -X $(ROOT)
 .PHONY: help clean dependencies test
 help:
 	@echo "Please use \`make <ROOT>' where <ROOT> is one of"
-	@echo "  dependencies to go install the dependencies"
 	@echo "  guerrillad   to build the main binary for current platform"
 	@echo "  test         to run unittests"
 
 clean:
 	rm -f guerrillad
 
-dependencies:
-	$(GO_VARS) $(GO) list -f='{{ join .Deps "\n" }}' $(ROOT)/cmd/guerrillad | grep -v $(ROOT) | tr '\n' ' ' | $(GO_VARS) xargs $(GO) get -u -v
-	$(GO_VARS) $(GO) list -f='{{ join .Deps "\n" }}' $(ROOT)/cmd/guerrillad | grep -v $(ROOT) | tr '\n' ' ' | $(GO_VARS) xargs $(GO) install -v
+vendor:
+	dep ensure
 
-guerrillad: *.go */*.go */*/*.go
+guerrillad:
 	$(GO_VARS) $(GO) build -o="guerrillad" -ldflags="$(LD_FLAGS)" $(ROOT)/cmd/guerrillad
 
-guerrilladrace: *.go */*.go */*/*.go
+guerrilladrace:
 	$(GO_VARS) $(GO) build -o="guerrillad" -race -ldflags="$(LD_FLAGS)" $(ROOT)/cmd/guerrillad
 
-
-test: *.go */*.go */*/*.go
+test:
 	$(GO_VARS) $(GO) test -v .
 	$(GO_VARS) $(GO) test -v ./tests
 	$(GO_VARS) $(GO) test -v ./cmd/guerrillad
@@ -41,7 +38,7 @@ test: *.go */*.go */*/*.go
 	$(GO_VARS) $(GO) test -v ./mail/smtp
 	$(GO_VARS) $(GO) test -v ./chunk
 
-testrace: *.go */*.go */*/*.go
+testrace:
 	$(GO_VARS) $(GO) test -v . -race
 	$(GO_VARS) $(GO) test -v ./tests -race
 	$(GO_VARS) $(GO) test -v ./cmd/guerrillad -race

+ 8 - 6
README.md

@@ -1,7 +1,7 @@
 
 [![Build Status](https://travis-ci.org/flashmob/go-guerrilla.svg?branch=master)](https://travis-ci.org/flashmob/go-guerrilla)
 
-Breaking change: The structure of the config has recently changed to accommodate more advanced TLS settings.
+Note: Latest release, v1.6.0, has moved from Glide to Dep dependency management tool. 
 
 Go-Guerrilla SMTP Daemon
 ====================
@@ -81,10 +81,10 @@ Getting started
 
 #### Dependencies
 
-Go-Guerrilla uses [Glide](https://github.com/Masterminds/glide) to manage 
-dependencies. If you have glide installed, just run `glide install` as usual.
+Go-Guerrilla uses [Dep](https://golang.github.io/dep/) to manage 
+dependencies. If you have dep installed, just run `dep ensure` as usual.
  
-You can also run `$ go get ./..` if you don't want to use glide, and then run `$ make test`
+You can also run `$ go get ./..` if you don't want to use dep, and then run `$ make test`
 to ensure all is good.
 
 To build the binary run:
@@ -141,8 +141,10 @@ import (
 
 ```
 
-You may use ``$ go get ./...`` to get all dependencies, also Go-Guerrilla uses 
-[glide](https://github.com/Masterminds/glide) for dependency management.
+You should use the `dep ensure` command to get all dependencies, as Go-Guerrilla uses 
+[dep](https://golang.github.io/dep/) for dependency management. 
+
+Otherise, ``$ go get ./...`` should work if you're in a hurry.
 
 #### 2. Start a server
 

+ 3 - 3
api.go

@@ -127,10 +127,10 @@ func (d *Daemon) ReloadConfig(c AppConfig) error {
 	if err != nil {
 		d.Log().WithError(err).Error("Error while reloading config")
 		return err
-	} else {
-		d.Log().Infof("Configuration was reloaded at %s", d.configLoadTime)
-		d.Config.EmitChangeEvents(&oldConfig, d.g)
 	}
+	d.Log().Infof("Configuration was reloaded at %s", d.configLoadTime)
+	d.Config.EmitChangeEvents(&oldConfig, d.g)
+
 	return nil
 }
 

+ 79 - 14
api_test.go

@@ -230,6 +230,7 @@ func TestSMTPLoadFile(t *testing.T) {
 	}
 }
 
+// test re-opening the main log
 func TestReopenLog(t *testing.T) {
 	if err := os.Truncate("tests/testlog", 0); err != nil {
 		t.Error(err)
@@ -259,14 +260,80 @@ func TestReopenLog(t *testing.T) {
 		t.Error("could not read logfile")
 		return
 	}
-	if strings.Index(string(b), "re-opened log file") < 0 {
+	if !strings.Contains(string(b), "re-opened log file") {
 		t.Error("Server log did not re-opened, expecting \"re-opened log file\"")
 	}
-	if strings.Index(string(b), "re-opened main log file") < 0 {
+	if !strings.Contains(string(b), "re-opened main log file") {
 		t.Error("Main log did not re-opened, expecting \"re-opened main log file\"")
 	}
 }
 
+const testServerLog = "tests/testlog-server.log"
+
+// test re-opening the individual server log
+func TestReopenServerLog(t *testing.T) {
+	if err := os.Truncate("tests/testlog", 0); err != nil {
+		t.Error(err)
+	}
+
+	defer func() {
+		if _, err := os.Stat(testServerLog); err == nil {
+			if err = os.Remove(testServerLog); err != nil {
+				t.Error(err)
+			}
+		}
+	}()
+
+	cfg := &AppConfig{LogFile: "tests/testlog", LogLevel: log.DebugLevel.String(), AllowedHosts: []string{"grr.la"}}
+	sc := ServerConfig{
+		ListenInterface: "127.0.0.1:2526",
+		IsEnabled:       true,
+		LogFile:         testServerLog,
+	}
+	cfg.Servers = append(cfg.Servers, sc)
+	d := Daemon{Config: cfg}
+
+	err := d.Start()
+	if err != nil {
+		t.Error("start error", err)
+	} else {
+		if err := talkToServer("127.0.0.1:2526"); err != nil {
+			t.Error(err)
+		}
+		if err = d.ReopenLogs(); err != nil {
+			t.Error(err)
+		}
+		time.Sleep(time.Second * 2)
+		if err := talkToServer("127.0.0.1:2526"); err != nil {
+			t.Error(err)
+		}
+		d.Shutdown()
+	}
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	if !strings.Contains(string(b), "re-opened log file") {
+		t.Error("Server log did not re-opened, expecting \"re-opened log file\"")
+	}
+	if !strings.Contains(string(b), "re-opened main log file") {
+		t.Error("Main log did not re-opened, expecting \"re-opened main log file\"")
+	}
+
+	b, err = ioutil.ReadFile(testServerLog)
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+
+	if !strings.Contains(string(b), "Handle client") {
+		t.Error("server log does not contain \"handle client\"")
+	}
+
+}
+
 func TestSetConfig(t *testing.T) {
 
 	if err := os.Truncate("tests/testlog", 0); err != nil {
@@ -307,7 +374,7 @@ func TestSetConfig(t *testing.T) {
 	}
 	//fmt.Println(string(b))
 	// has 127.0.0.1:2527 started?
-	if strings.Index(string(b), "127.0.0.1:2527") < 0 {
+	if !strings.Contains(string(b), "127.0.0.1:2527") {
 		t.Error("expecting 127.0.0.1:2527 to start")
 	}
 
@@ -411,18 +478,18 @@ func TestSetAddProcessor(t *testing.T) {
 		return
 	}
 	// lets check for fingerprints
-	if strings.Index(string(b), "another funky recipient") < 0 {
+	if !strings.Contains(string(b), "another funky recipient") {
 		t.Error("did not log: another funky recipient")
 	}
 
-	if strings.Index(string(b), "Another funky email!") < 0 {
+	if !strings.Contains(string(b), "Another funky email!") {
 		t.Error("Did not log: Another funky email!")
 	}
 
-	if strings.Index(string(b), "Funky logger is up & down to funk") < 0 {
+	if !strings.Contains(string(b), "Funky logger is up & down to funk") {
 		t.Error("Did not log: Funky logger is up & down to funk")
 	}
-	if strings.Index(string(b), "The funk has been stopped!") < 0 {
+	if !strings.Contains(string(b), "The funk has been stopped!") {
 		t.Error("Did not log:The funk has been stopped!")
 	}
 
@@ -455,9 +522,6 @@ func talkToServer(address string, body string) (err error) {
 	if err != nil {
 		return err
 	}
-	if err != nil {
-		return err
-	}
 	_, err = fmt.Fprint(conn, "RCPT TO:<[email protected]>\r\n")
 	if err != nil {
 		return err
@@ -518,6 +582,7 @@ func talkToServer(address string, body string) (err error) {
 
 // Test hot config reload
 // Here we forgot to add FunkyLogger so backend will fail to init
+// it will log to stderr at the beginning, but then change to tests/testlog
 
 func TestReloadConfig(t *testing.T) {
 	if err := os.Truncate("tests/testlog", 0); err != nil {
@@ -596,7 +661,7 @@ func TestPubSubAPI(t *testing.T) {
 		return
 	}
 	// lets interrogate the log
-	if strings.Index(string(b), "number1") < 0 {
+	if !strings.Contains(string(b), "number1") {
 		t.Error("it lools like d.ReloadConfig(cfg) did not fire EventConfigPidFile, pidEvHandler not called")
 	}
 
@@ -629,7 +694,7 @@ func TestAPILog(t *testing.T) {
 		return
 	}
 	// lets interrogate the log
-	if strings.Index(string(b), "logtest1") < 0 {
+	if !strings.Contains(string(b), "logtest1") {
 		t.Error("hai was not found in the log, it should have been in tests/testlog")
 	}
 }
@@ -720,11 +785,11 @@ func TestCustomBackendResult(t *testing.T) {
 		return
 	}
 	// lets check for fingerprints
-	if strings.Index(string(b), "451 4.3.0 Error") < 0 {
+	if !strings.Contains(string(b), "451 4.3.0 Error") {
 		t.Error("did not log: 451 4.3.0 Error")
 	}
 
-	if strings.Index(string(b), "system shock") < 0 {
+	if !strings.Contains(string(b), "system shock") {
 		t.Error("did not log: system shock")
 	}
 

+ 5 - 5
backends/gateway.go

@@ -8,11 +8,12 @@ import (
 	"sync"
 	"time"
 
+	"runtime/debug"
+	"strings"
+
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/response"
-	"runtime/debug"
-	"strings"
 )
 
 var ErrProcessorNotFound error
@@ -379,7 +380,7 @@ func (gw *BackendGateway) newStack(stackConfig string) (Processor, error) {
 		if makeFunc, ok := processors[name]; ok {
 			decorators = append(decorators, makeFunc())
 		} else {
-			ErrProcessorNotFound = errors.New(fmt.Sprintf("processor [%s] not found", name))
+			ErrProcessorNotFound = fmt.Errorf("processor [%s] not found", name)
 			return nil, ErrProcessorNotFound
 		}
 	}
@@ -522,7 +523,7 @@ func (gw *BackendGateway) Start() error {
 		gw.State = BackendStateRunning
 		return nil
 	} else {
-		return errors.New(fmt.Sprintf("cannot start backend because it's in %s state", gw.State))
+		return fmt.Errorf("cannot start backend because it's in %s state", gw.State)
 	}
 }
 
@@ -594,7 +595,6 @@ func (gw *BackendGateway) workDispatcher(
 			return
 		}
 		// state is dispatcherStateStopped if it reached here
-		return
 
 	}()
 	state = dispatcherStateIdle

+ 19 - 19
backends/p_compressor.go

@@ -31,15 +31,15 @@ func init() {
 }
 
 // compressedData struct will be compressed using zlib when printed via fmt
-type compressor struct {
-	extraHeaders []byte
-	data         *bytes.Buffer
+type DataCompressor struct {
+	ExtraHeaders []byte
+	Data         *bytes.Buffer
 	// the pool is used to recycle buffers to ease up on the garbage collector
-	pool *sync.Pool
+	Pool *sync.Pool
 }
 
 // newCompressedData returns a new CompressedData
-func newCompressor() *compressor {
+func newCompressor() *DataCompressor {
 	// grab it from the pool
 	var p = sync.Pool{
 		// if not available, then create a new one
@@ -48,45 +48,45 @@ func newCompressor() *compressor {
 			return &b
 		},
 	}
-	return &compressor{
-		pool: &p,
+	return &DataCompressor{
+		Pool: &p,
 	}
 }
 
 // Set the extraheaders and buffer of data to compress
-func (c *compressor) set(b []byte, d *bytes.Buffer) {
-	c.extraHeaders = b
-	c.data = d
+func (c *DataCompressor) set(b []byte, d *bytes.Buffer) {
+	c.ExtraHeaders = b
+	c.Data = d
 }
 
 // String implements the Stringer interface.
 // Can only be called once!
 // This is because the compression buffer will be reset and compressor will be returned to the pool
-func (c *compressor) String() string {
-	if c.data == nil {
+func (c *DataCompressor) String() string {
+	if c.Data == nil {
 		return ""
 	}
 	//borrow a buffer form the pool
-	b := c.pool.Get().(*bytes.Buffer)
+	b := c.Pool.Get().(*bytes.Buffer)
 	// put back in the pool
 	defer func() {
 		b.Reset()
-		c.pool.Put(b)
+		c.Pool.Put(b)
 	}()
 
 	var r *bytes.Reader
 	w, _ := zlib.NewWriterLevel(b, zlib.BestSpeed)
-	r = bytes.NewReader(c.extraHeaders)
+	r = bytes.NewReader(c.ExtraHeaders)
 	_, _ = io.Copy(w, r)
-	_, _ = io.Copy(w, c.data)
+	_, _ = io.Copy(w, c.Data)
 	_ = w.Close()
 	return b.String()
 }
 
 // clear it, without clearing the pool
-func (c *compressor) clear() {
-	c.extraHeaders = []byte{}
-	c.data = nil
+func (c *DataCompressor) clear() {
+	c.ExtraHeaders = []byte{}
+	c.Data = nil
 }
 
 func Compressor() Decorator {

+ 0 - 9
backends/p_guerrilla_db_redis.go

@@ -318,15 +318,6 @@ func trimToLimit(str string, limit int) string {
 }
 
 func (g *GuerrillaDBAndRedisBackend) sqlConnect() (*sql.DB, error) {
-	tOut := GuerrillaDBAndRedisBatchTimeout
-	if g.config.BatchTimeout > 0 {
-		tOut = time.Duration(g.config.BatchTimeout)
-	}
-	tOut += 10
-	// don't go to 30 sec or more
-	if tOut >= 30 {
-		tOut = 29
-	}
 	if db, err := sql.Open(g.config.Driver, g.config.DSN); err != nil {
 		Log().Error("cannot open database", err, "]")
 		return nil, err

+ 1 - 1
backends/p_redis.go

@@ -93,7 +93,7 @@ func Redis() Decorator {
 					var stringer fmt.Stringer
 					// a compressor was set
 					if c, ok := e.Values["zlib-compressor"]; ok {
-						stringer = c.(*compressor)
+						stringer = c.(*DataCompressor)
 					} else {
 						stringer = e
 					}

+ 54 - 12
backends/p_sql.go

@@ -4,6 +4,7 @@ import (
 	"database/sql"
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/flashmob/go-guerrilla/mail"
 
@@ -25,6 +26,12 @@ import (
 //               : sql_driver string - database driver name, eg. mysql
 //               : sql_dsn string - driver-specific data source name
 //               : primary_mail_host string - primary host name
+//               : sql_max_open_conns - sets the maximum number of open connections
+//               : to the database. The default is 0 (unlimited)
+//               : sql_max_idle_conns - sets the maximum number of connections in the
+//               : idle connection pool. The default is 2
+//               : sql_max_conn_lifetime - sets the maximum amount of time
+//               : a connection may be reused
 // --------------:-------------------------------------------------------------------
 // Input         : e.Data
 //               : e.DeliveryHeader generated by ParseHeader() processor
@@ -40,10 +47,15 @@ func init() {
 }
 
 type SQLProcessorConfig struct {
-	Table       string `json:"mail_table"`
-	Driver      string `json:"sql_driver"`
-	DSN         string `json:"sql_dsn"`
-	PrimaryHost string `json:"primary_mail_host"`
+	Table           string `json:"mail_table"`
+	Driver          string `json:"sql_driver"`
+	DSN             string `json:"sql_dsn"`
+	SQLInsert       string `json:"sql_insert,omitempty"`
+	SQLValues       string `json:"sql_values,omitempty"`
+	PrimaryHost     string `json:"primary_mail_host"`
+	MaxConnLifetime string `json:"sql_max_conn_lifetime,omitempty"`
+	MaxOpenConns    int    `json:"sql_max_open_conns,omitempty"`
+	MaxIdleConns    int    `json:"sql_max_idle_conns,omitempty"`
 }
 
 type SQLProcessor struct {
@@ -58,6 +70,21 @@ func (s *SQLProcessor) connect() (*sql.DB, error) {
 		Log().Error("cannot open database: ", err)
 		return nil, err
 	}
+
+	if s.config.MaxOpenConns != 0 {
+		db.SetMaxOpenConns(s.config.MaxOpenConns)
+	}
+	if s.config.MaxIdleConns != 0 {
+		db.SetMaxIdleConns(s.config.MaxIdleConns)
+	}
+	if s.config.MaxConnLifetime != "" {
+		t, err := time.ParseDuration(s.config.MaxConnLifetime)
+		if err != nil {
+			return nil, err
+		}
+		db.SetConnMaxLifetime(t)
+	}
+
 	// do we have permission to access the table?
 	_, err = db.Query("SELECT mail_id FROM " + s.config.Table + " LIMIT 1")
 	if err != nil {
@@ -68,18 +95,33 @@ func (s *SQLProcessor) connect() (*sql.DB, error) {
 
 // prepares the sql query with the number of rows that can be batched with it
 func (s *SQLProcessor) prepareInsertQuery(rows int, db *sql.DB) *sql.Stmt {
+	var sqlstr, values string
 	if rows == 0 {
 		panic("rows argument cannot be 0")
 	}
 	if s.cache[rows-1] != nil {
 		return s.cache[rows-1]
 	}
-	sqlstr := "INSERT INTO " + s.config.Table + " "
-	sqlstr += "(`date`, `to`, `from`, `subject`, `body`,  `mail`, `spam_score`, "
-	sqlstr += "`hash`, `content_type`, `recipient`, `has_attach`, `ip_addr`, "
-	sqlstr += "`return_path`, `is_tls`, `message_id`, `reply_to`, `sender`)"
-	sqlstr += " VALUES "
-	values := "(NOW(), ?, ?, ?, ? , ?, 0, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)"
+	if s.config.SQLInsert != "" {
+		sqlstr = s.config.SQLInsert
+		if !strings.HasSuffix(sqlstr, " ") {
+			// Add a trailing space so we can concatinate our values string
+			// without causing a syntax error
+			sqlstr = sqlstr + " "
+		}
+	} else {
+		// Default to MySQL SQL
+		sqlstr = "INSERT INTO " + s.config.Table + " "
+		sqlstr += "(`date`, `to`, `from`, `subject`, `body`,  `mail`, `spam_score`, "
+		sqlstr += "`hash`, `content_type`, `recipient`, `has_attach`, `ip_addr`, "
+		sqlstr += "`return_path`, `is_tls`, `message_id`, `reply_to`, `sender`)"
+		sqlstr += " VALUES "
+	}
+	if s.config.SQLValues != "" {
+		values = s.config.SQLValues
+	} else {
+		values = "(NOW(), ?, ?, ?, ? , ?, 0, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)"
+	}
 	// add more rows
 	comma := ""
 	for i := 0; i < rows; i++ {
@@ -185,11 +227,11 @@ func SQL() Decorator {
 					e.QueuedId = e.Hashes[0]
 				}
 
-				var co *compressor
+				var co *DataCompressor
 				// a compressor was set by the Compress processor
 				if c, ok := e.Values["zlib-compressor"]; ok {
 					body = "gzip"
-					co = c.(*compressor)
+					co = c.(*DataCompressor)
 				}
 				// was saved in redis by the Redis processor
 				if _, ok := e.Values["redis"]; ok {

+ 6 - 6
client.go

@@ -6,13 +6,14 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
+	"net"
+	"sync"
+	"time"
+
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mail/smtp"
 	"github.com/flashmob/go-guerrilla/response"
-	"net"
-	"sync"
-	"time"
 )
 
 // ClientState indicates which part of the SMTP transaction a given client is in.
@@ -180,9 +181,8 @@ func (c *client) getID() uint64 {
 
 // UpgradeToTLS upgrades a client connection to TLS
 func (c *client) upgradeToTLS(tlsConfig *tls.Config) error {
-	var tlsConn *tls.Conn
 	// wrap c.conn in a new TLS server side connection
-	tlsConn = tls.Server(c.conn, tlsConfig)
+	tlsConn := tls.Server(c.conn, tlsConfig)
 	// Call handshake here to get any handshake error before reading starts
 	err := tlsConn.Handshake()
 	if err != nil {
@@ -234,6 +234,6 @@ func (c *client) parsePath(in []byte, p pathParser) (mail.Address, error) {
 	return address, err
 }
 
-func (s *server) rcptTo(in []byte) (address mail.Address, err error) {
+func (s *server) rcptTo() (address mail.Address, err error) {
 	return address, err
 }

+ 1 - 1
cmd/guerrillad/serve.go

@@ -46,7 +46,7 @@ func init() {
 	// log to stderr on startup
 	var err error
 	mainlog, err = log.GetLogger(log.OutputStderr.String(), log.InfoLevel.String())
-	if err != nil {
+	if err != nil && mainlog != nil {
 		mainlog.WithError(err).Errorf("Failed creating a logger to %s", log.OutputStderr)
 	}
 	cfgFile := "goguerrilla.conf" // deprecated default name

+ 10 - 11
cmd/guerrillad/serve_test.go

@@ -20,7 +20,7 @@ import (
 	"github.com/flashmob/go-guerrilla"
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/log"
-	"github.com/flashmob/go-guerrilla/tests"
+	test "github.com/flashmob/go-guerrilla/tests"
 	"github.com/flashmob/go-guerrilla/tests/testcert"
 	"github.com/spf13/cobra"
 )
@@ -370,7 +370,7 @@ func grepTestlog(match string, lineNumber int) (found int, err error) {
 				}
 			}
 		}
-		if err != nil && err != io.EOF {
+		if err != io.EOF {
 			return found, err
 		}
 
@@ -549,7 +549,6 @@ func TestCmdConfigChangeEvents(t *testing.T) {
 		if val == false {
 			t.Error("Did not fire config change event:", event)
 			t.FailNow()
-			break
 		}
 	}
 
@@ -672,7 +671,7 @@ func TestServerAddEvent(t *testing.T) {
 	newConf := conf                                      // copy the cmdConfg
 	newConf.Servers = append(newConf.Servers, newServer) // add the new server
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
-		if err := ioutil.WriteFile("configJsonA.json", []byte(jsonbytes), 0644); err != nil {
+		if err := ioutil.WriteFile("configJsonA.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	}
@@ -749,7 +748,7 @@ func TestServerStartEvent(t *testing.T) {
 	newConf.Servers[1].IsEnabled = true
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
 		//fmt.Println(string(jsonbytes))
-		if err = ioutil.WriteFile("configJsonA.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonA.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {
@@ -830,7 +829,7 @@ func TestServerStopEvent(t *testing.T) {
 	newConf.Servers[1].IsEnabled = true // enable 2nd server
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
 		//fmt.Println(string(jsonbytes))
-		if err = ioutil.WriteFile("configJsonA.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonA.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {
@@ -863,7 +862,7 @@ func TestServerStopEvent(t *testing.T) {
 	newerConf.Servers[1].IsEnabled = false
 	if jsonbytes, err := json.Marshal(newerConf); err == nil {
 		//fmt.Println(string(jsonbytes))
-		if err = ioutil.WriteFile("configJsonA.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonA.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {
@@ -980,7 +979,7 @@ func TestAllowedHostsEvent(t *testing.T) {
 	newConf := conf
 	newConf.AllowedHosts = append(newConf.AllowedHosts, "grr.la")
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
-		if err = ioutil.WriteFile("configJsonD.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonD.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {
@@ -1277,7 +1276,7 @@ func TestBadTLSReload(t *testing.T) {
 	newConf := conf // copy the cmdConfg
 
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
-		if err = ioutil.WriteFile("configJsonD.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonD.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {
@@ -1353,7 +1352,7 @@ func TestSetTimeoutEvent(t *testing.T) {
 	newConf := conf // copy the cmdConfg
 	newConf.Servers[0].Timeout = 1
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
-		if err = ioutil.WriteFile("configJsonD.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonD.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {
@@ -1456,7 +1455,7 @@ func TestDebugLevelChange(t *testing.T) {
 	newConf := conf // copy the cmdConfg
 	newConf.LogLevel = log.InfoLevel.String()
 	if jsonbytes, err := json.Marshal(newConf); err == nil {
-		if err = ioutil.WriteFile("configJsonD.json", []byte(jsonbytes), 0644); err != nil {
+		if err = ioutil.WriteFile("configJsonD.json", jsonbytes, 0644); err != nil {
 			t.Error(err)
 		}
 	} else {

+ 31 - 36
config.go

@@ -5,12 +5,13 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/flashmob/go-guerrilla/backends"
-	"github.com/flashmob/go-guerrilla/log"
 	"os"
 	"reflect"
 	"strings"
 	"time"
+
+	"github.com/flashmob/go-guerrilla/backends"
+	"github.com/flashmob/go-guerrilla/log"
 )
 
 // AppConfig is the holder of the configuration of the app
@@ -34,44 +35,32 @@ type AppConfig struct {
 
 // ServerConfig specifies config options for a single server
 type ServerConfig struct {
-	// IsEnabled set to true to start the server, false will ignore it
-	IsEnabled bool `json:"is_enabled"`
+	// TLS Configuration
+	TLS ServerTLSConfig `json:"tls,omitempty"`
+	// LogFile is where the logs go. Use path to file, or "stderr", "stdout" or "off".
+	// defaults to AppConfig.Log file setting
+	LogFile string `json:"log_file,omitempty"`
 	// Hostname will be used in the server's reply to HELO/EHLO. If TLS enabled
 	// make sure that the Hostname matches the cert. Defaults to os.Hostname()
 	Hostname string `json:"host_name"`
+	// Listen interface specified in <ip>:<port> - defaults to 127.0.0.1:2525
+	ListenInterface string `json:"listen_interface"`
 	// MaxSize is the maximum size of an email that will be accepted for delivery.
 	// Defaults to 10 Mebibytes
 	MaxSize int64 `json:"max_size"`
-	// TLS Configuration
-	TLS ServerTLSConfig `json:"tls,omitempty"`
 	// Timeout specifies the connection timeout in seconds. Defaults to 30
 	Timeout int `json:"timeout"`
-	// Listen interface specified in <ip>:<port> - defaults to 127.0.0.1:2525
-	ListenInterface string `json:"listen_interface"`
-
 	// MaxClients controls how many maximum clients we can handle at once.
 	// Defaults to defaultMaxClients
 	MaxClients int `json:"max_clients"`
-	// LogFile is where the logs go. Use path to file, or "stderr", "stdout" or "off".
-	// defaults to AppConfig.Log file setting
-	LogFile string `json:"log_file,omitempty"`
+	// IsEnabled set to true to start the server, false will ignore it
+	IsEnabled bool `json:"is_enabled"`
 	// XClientOn when using a proxy such as Nginx, XCLIENT command is used to pass the
 	// original client's IP address & client's HELO
 	XClientOn bool `json:"xclient_on,omitempty"`
 }
 
 type ServerTLSConfig struct {
-
-	// StartTLSOn should we offer STARTTLS command. Cert must be valid.
-	// False by default
-	StartTLSOn bool `json:"start_tls_on,omitempty"`
-	// AlwaysOn run this server as a pure TLS server, i.e. SMTPS
-	AlwaysOn bool `json:"tls_always_on,omitempty"`
-	// PrivateKeyFile path to cert private key in PEM format.
-	PrivateKeyFile string `json:"private_key_file"`
-	// PublicKeyFile path to cert (public key) chain in PEM format.
-	PublicKeyFile string `json:"public_key_file"`
-
 	// TLS Protocols to use. [0] = min, [1]max
 	// Use Go's default if empty
 	Protocols []string `json:"protocols,omitempty"`
@@ -81,19 +70,27 @@ type ServerTLSConfig struct {
 	// TLS Curves to use.
 	// Use Go's default if empty
 	Curves []string `json:"curves,omitempty"`
+	// PrivateKeyFile path to cert private key in PEM format.
+	PrivateKeyFile string `json:"private_key_file"`
+	// PublicKeyFile path to cert (public key) chain in PEM format.
+	PublicKeyFile string `json:"public_key_file"`
 	// TLS Root cert authorities to use. "A PEM encoded CA's certificate file.
 	// Defaults to system's root CA file if empty
 	RootCAs string `json:"root_cas_file,omitempty"`
 	// declares the policy the server will follow for TLS Client Authentication.
 	// Use Go's default if empty
 	ClientAuthType string `json:"client_auth_type,omitempty"`
-	// controls whether the server selects the
-	// client's most preferred cipher suite
-	PreferServerCipherSuites bool `json:"prefer_server_cipher_suites,omitempty"`
-
 	// The following used to watch certificate changes so that the TLS can be reloaded
 	_privateKeyFileMtime int64
 	_publicKeyFileMtime  int64
+	// controls whether the server selects the
+	// client's most preferred cipher suite
+	PreferServerCipherSuites bool `json:"prefer_server_cipher_suites,omitempty"`
+	// StartTLSOn should we offer STARTTLS command. Cert must be valid.
+	// False by default
+	StartTLSOn bool `json:"start_tls_on,omitempty"`
+	// AlwaysOn run this server as a pure TLS server, i.e. SMTPS
+	AlwaysOn bool `json:"tls_always_on,omitempty"`
 }
 
 // https://golang.org/pkg/crypto/tls/#pkg-constants
@@ -303,7 +300,7 @@ func (c *AppConfig) setDefaults() error {
 				c.Servers[i].MaxSize = defaultMaxSize // 10 Mebibytes
 			}
 			if c.Servers[i].ListenInterface == "" {
-				return errors.New(fmt.Sprintf("Listen interface not specified for server at index %d", i))
+				return fmt.Errorf("listen interface not specified for server at index %d", i)
 			}
 			if c.Servers[i].LogFile == "" {
 				c.Servers[i].LogFile = c.LogFile
@@ -408,11 +405,10 @@ func (sc *ServerConfig) emitChangeEvents(oldServer *ServerConfig, app Guerrilla)
 // Loads in timestamps for the ssl keys
 func (sc *ServerConfig) loadTlsKeyTimestamps() error {
 	var statErr = func(iface string, err error) error {
-		return errors.New(
-			fmt.Sprintf(
-				"could not stat key for server [%s], %s",
-				iface,
-				err.Error()))
+		return fmt.Errorf(
+			"could not stat key for server [%s], %s",
+			iface,
+			err.Error())
 	}
 	if sc.TLS.PrivateKeyFile == "" {
 		sc.TLS._privateKeyFileMtime = time.Now().Unix()
@@ -447,8 +443,7 @@ func (sc *ServerConfig) Validate() error {
 			errs = append(errs, errors.New("PrivateKeyFile is empty"))
 		}
 		if _, err := tls.LoadX509KeyPair(sc.TLS.PublicKeyFile, sc.TLS.PrivateKeyFile); err != nil {
-			errs = append(errs,
-				errors.New(fmt.Sprintf("cannot use TLS config for [%s], %v", sc.ListenInterface, err)))
+			errs = append(errs, fmt.Errorf("cannot use TLS config for [%s], %v", sc.ListenInterface, err))
 		}
 	}
 	if len(errs) > 0 {
@@ -504,7 +499,7 @@ func getChanges(a interface{}, b interface{}) map[string]interface{} {
 // only able to convert int, bool, slice-of-strings and string; not recursive
 // slices are marshal'd to json for convenient comparison later
 func structtomap(obj interface{}) map[string]interface{} {
-	ret := make(map[string]interface{}, 0)
+	ret := make(map[string]interface{})
 	v := reflect.ValueOf(obj)
 	t := v.Type()
 	for index := 0; index < v.NumField(); index++ {

+ 1 - 2
config_test.go

@@ -276,7 +276,7 @@ func TestConfigChangeEvents(t *testing.T) {
 	for event := range expectedEvents {
 		// Put in anon func since range is overwriting event
 		func(e Event) {
-			if strings.Index(e.String(), "config_change") != -1 {
+			if strings.Contains(e.String(), "config_change") {
 				f := func(c *AppConfig) {
 					expectedEvents[e] = true
 				}
@@ -307,7 +307,6 @@ func TestConfigChangeEvents(t *testing.T) {
 		if val == false {
 			t.Error("Did not fire config change event:", event)
 			t.FailNow()
-			break
 		}
 	}
 

+ 0 - 70
glide.lock

@@ -1,70 +0,0 @@
-hash: 9d467015838b5163cc5f1ebb92fa68fd4721445b2dbf236a03d002c92b4ba52a
-updated: 2018-12-24T15:20:01.207901397Z
-imports:
-- name: github.com/asaskevich/EventBus
-  version: 68a521d7cbbb7a859c2608b06342f384b3bd5f5a
-- name: github.com/garyburd/redigo
-  version: 8873b2f1995f59d4bcdd2b0dc9858e2cb9bf0c13
-  subpackages:
-  - internal
-  - redis
-- name: github.com/go-sql-driver/mysql
-  version: 72cd26f257d44c1114970e19afddcd812016007e
-- name: github.com/gomodule/redigo
-  version: 8873b2f1995f59d4bcdd2b0dc9858e2cb9bf0c13
-  subpackages:
-  - redis
-- name: github.com/inconshreveable/mousetrap
-  version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
-- name: github.com/rakyll/statik
-  version: 79258177a57a85a8ab2eca7ce0936aad80307f4e
-  subpackages:
-  - fs
-- name: github.com/sirupsen/logrus
-  version: 3e01752db0189b9157070a0e1668a620f9a85da2
-- name: github.com/spf13/cobra
-  version: d2d81d9a96e23f0255397222bb0b4e3165e492dc
-- name: github.com/spf13/pflag
-  version: 24fa6976df40757dce6aea913e7b81ade90530e1
-- name: golang.org/x/crypto
-  version: 505ab145d0a99da450461ae2c1a9f6cd10d1f447
-  subpackages:
-  - ssh/terminal
-- name: golang.org/x/net
-  version: 927f97764cc334a6575f4b7a1584a147864d5723
-  subpackages:
-  - html
-  - html/atom
-  - html/charset
-- name: golang.org/x/sys
-  version: 7dca6fe1f43775aa6d1334576870ff63f978f539
-  subpackages:
-  - unix
-  - windows
-- name: golang.org/x/text
-  version: 17bcc049122f272a32787ba38073ee47433023e9
-  subpackages:
-  - encoding
-  - encoding/charmap
-  - encoding/htmlindex
-  - encoding/internal
-  - encoding/internal/identifier
-  - encoding/japanese
-  - encoding/korean
-  - encoding/simplifiedchinese
-  - encoding/traditionalchinese
-  - encoding/unicode
-  - internal/language
-  - internal/language/compact
-  - internal/tag
-  - internal/utf8internal
-  - language
-  - runes
-  - transform
-- name: google.golang.org/appengine
-  version: e9657d882bb81064595ca3b56cbe2546bbabf7b1
-  subpackages:
-  - cloudsql
-- name: gopkg.in/iconv.v1
-  version: 16a760eb7e186ae0e3aedda00d4a1daa4d0701d8
-testImports: []

+ 0 - 20
glide.yaml

@@ -1,20 +0,0 @@
-package: github.com/flashmob/go-guerrilla
-import:
-- package: github.com/sirupsen/logrus
-  version: ~1.0.4
-- package: github.com/gomodule/redigo
-  version: ~2.0.0
-  subpackages:
-  - redis
-- package: github.com/spf13/cobra
-- package: gopkg.in/iconv.v1
-  version: ~1.1.1
-- package: github.com/asaskevich/EventBus
-  version: 68a521d7cbbb7a859c2608b06342f384b3bd5f5a
-- package: github.com/go-sql-driver/mysql
-  version: ^1.3.0
-- package: golang.org/x/sys
-  version: 7dca6fe1f43775aa6d1334576870ff63f978f539
-- package: golang.org/x/net
-  subpackages:
-  - html/charset

+ 4 - 0
guerrilla.go

@@ -102,6 +102,9 @@ func New(ac *AppConfig, b backends.Backend, l log.Logger) (Guerrilla, error) {
 
 	g.state = daemonStateNew
 	err := g.makeServers()
+	if err != nil {
+		return g, err
+	}
 
 	// start backend for processing email
 	err = g.backend().Start()
@@ -129,6 +132,7 @@ func (g *guerrilla) makeServers() error {
 			errs = append(errs, err)
 			continue
 		} else {
+			sc := sc // pin!
 			server, err := newServer(&sc, g.backend(), g.mainlog())
 			if err != nil {
 				g.mainlog().WithError(err).Errorf("Failed to create server [%s]", sc.ListenInterface)

+ 2 - 1
guerrilla_unix.go

@@ -11,5 +11,6 @@ func getFileLimit() (uint64, error) {
 	if err != nil {
 		return 0, err
 	}
-	return rLimit.Max, nil
+	//unnecessary type conversions to uint64 is needed for FreeBSD
+	return uint64(rLimit.Max), nil
 }

+ 8 - 6
log/hook.go

@@ -143,7 +143,10 @@ func (hook *LogrusHook) openCreate(dest string) (err error) {
 func (hook *LogrusHook) Fire(entry *log.Entry) error {
 	hookMu.Lock()
 	defer hookMu.Unlock()
-	if line, err := entry.String(); err == nil {
+
+	line, err := entry.String()
+
+	if err == nil {
 		r := strings.NewReader(line)
 		if _, err = io.Copy(hook.w, r); err != nil {
 			return err
@@ -157,9 +160,8 @@ func (hook *LogrusHook) Fire(entry *log.Entry) error {
 			}
 		}
 		return err
-	} else {
-		return err
 	}
+	return err
 }
 
 // Levels implements the logrus Hook interface
@@ -177,12 +179,12 @@ func (hook *LogrusHook) Reopen() error {
 			return err
 		}
 		// The file could have been re-named by an external program such as logrotate(8)
-		if _, err := os.Stat(hook.fname); err != nil {
+		_, err := os.Stat(hook.fname)
+		if err != nil {
 			// The file doesn't exist, create a new one.
 			return hook.openCreate(hook.fname)
-		} else {
-			return hook.openAppend(hook.fname)
 		}
+		return hook.openAppend(hook.fname)
 	}
 	return err
 

+ 5 - 5
log/log.go

@@ -130,17 +130,17 @@ func GetLogger(dest string, level string) (Logger, error) {
 	// we'll use the hook to output instead
 	logrus.Out = ioutil.Discard
 	// setup the hook
-	if h, err := NewLogrusHook(dest); err != nil {
+	h, err := NewLogrusHook(dest)
+	if err != nil {
 		// revert back to stderr
 		logrus.Out = os.Stderr
 		return l, err
-	} else {
-		logrus.Hooks.Add(h)
-		l.h = h
 	}
 
-	return l, nil
+	logrus.Hooks.Add(h)
+	l.h = h
 
+	return l, nil
 }
 
 func newLogrus(o OutputOption, level string) (*log.Logger, error) {

+ 7 - 7
mail/envelope.go

@@ -72,7 +72,7 @@ func NewAddress(str string) (Address, error) {
 	return Address{}, errors.New("invalid address")
 }
 
-// Email represents a single SMTP message.
+// Envelope of Email represents a single SMTP message.
 type Envelope struct {
 	// Remote IP address
 	RemoteIP string
@@ -139,7 +139,7 @@ func (e *Envelope) Len() int {
 	return len(e.DeliveryHeader) + e.Data.Len()
 }
 
-// Returns a new reader for reading the email contents, including the delivery headers
+// NewReader returns a new reader for reading the email contents, including the delivery headers
 func (e *Envelope) NewReader() io.Reader {
 	return io.MultiReader(
 		strings.NewReader(e.DeliveryHeader),
@@ -174,9 +174,9 @@ func (e *Envelope) ResetTransaction() {
 	e.Values = make(map[string]interface{})
 }
 
-// Seed is called when used with a new connection, once it's accepted
-func (e *Envelope) Reseed(RemoteIP string, clientID uint64) {
-	e.RemoteIP = RemoteIP
+// Reseed is called when used with a new connection, once it's accepted
+func (e *Envelope) Reseed(remoteIP string, clientID uint64) {
+	e.RemoteIP = remoteIP
 	e.QueuedId = queuedID(clientID)
 	e.Helo = ""
 	e.TLS = false
@@ -187,14 +187,14 @@ func (e *Envelope) PushRcpt(addr Address) {
 	e.RcptTo = append(e.RcptTo, addr)
 }
 
-// Pop removes the last email address that was pushed to the envelope
+// PopRcpt 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
 }
 
-// Converts 7 bit encoded mime header strings to UTF-8
+// MimeHeaderDecode converts 7 bit encoded mime header strings to UTF-8
 func MimeHeaderDecode(str string) string {
 	state := 0
 	var buf bytes.Buffer

+ 0 - 2
mocks/client.go

@@ -11,8 +11,6 @@ const (
 
 func lastWords(message string, err error) {
 	fmt.Println(message, err.Error())
-	return
-	// panic(err)
 }
 
 func sendMail(i int) {

+ 1 - 2
response/quote.go

@@ -1,7 +1,6 @@
 package response
 
 import (
-	"fmt"
 	"math/rand"
 	"time"
 )
@@ -156,5 +155,5 @@ var quotes = struct {
 // GetQuote returns a random quote from The big Lebowski
 func GetQuote() string {
 	rand.Seed(time.Now().UnixNano())
-	return fmt.Sprintf("%s", quotes.m[rand.Intn(len(quotes.m))])
+	return quotes.m[rand.Intn(len(quotes.m))]
 }

+ 39 - 32
server.go

@@ -86,8 +86,8 @@ func (c command) match(in []byte) bool {
 	return bytes.Index(in, []byte(c)) == 0
 }
 
-// Creates and returns a new ready-to-run Server from a configuration
-func newServer(sc *ServerConfig, b backends.Backend, l log.Logger) (*server, error) {
+// Creates and returns a new ready-to-run Server from a ServerConfig configuration
+func newServer(sc *ServerConfig, b backends.Backend, mainlog log.Logger) (*server, error) {
 	server := &server{
 		clientPool:      NewPool(sc.MaxClients),
 		closedListener:  make(chan bool, 1),
@@ -95,20 +95,21 @@ func newServer(sc *ServerConfig, b backends.Backend, l log.Logger) (*server, err
 		state:           ServerStateNew,
 		envelopePool:    mail.NewPool(sc.MaxClients),
 	}
-	server.logStore.Store(l)
-	server.backendStore.Store(b)
-	logFile := sc.LogFile
-	if logFile == "" {
-		// none set, use the same log file as mainlog
-		logFile = server.mainlog().GetLogDest()
-	}
-	// set level to same level as mainlog level
-	mainlog, logOpenError := log.GetLogger(logFile, server.mainlog().GetLevel())
 	server.mainlogStore.Store(mainlog)
-	if logOpenError != nil {
-		server.log().WithError(logOpenError).Errorf("Failed creating a logger for server [%s]", sc.ListenInterface)
+	server.backendStore.Store(b)
+	if sc.LogFile == "" {
+		// none set, use the mainlog for the server log
+		server.logStore.Store(mainlog)
+		server.log().Info("server [" + sc.ListenInterface + "] did not configure a separate log file, so using the main log")
+	} else {
+		// set level to same level as mainlog level
+		if l, logOpenError := log.GetLogger(sc.LogFile, server.mainlog().GetLevel()); logOpenError != nil {
+			server.log().WithError(logOpenError).Errorf("Failed creating a logger for server [%s]", sc.ListenInterface)
+			return server, logOpenError
+		} else {
+			server.logStore.Store(l)
+		}
 	}
-
 	server.setConfig(sc)
 	server.setTimeout(sc.Timeout)
 	if err := server.configureSSL(); err != nil {
@@ -214,7 +215,7 @@ func (s *server) setAllowedHosts(allowedHosts []string) {
 	s.hosts.table = make(map[string]bool, len(allowedHosts))
 	s.hosts.wildcards = nil
 	for _, h := range allowedHosts {
-		if strings.Index(h, "*") != -1 {
+		if strings.Contains(h, "*") {
 			s.hosts.wildcards = append(s.hosts.wildcards, strings.ToLower(h))
 		} else {
 			s.hosts.table[strings.ToLower(h)] = true
@@ -454,14 +455,14 @@ func (s *server) handleClient(client *client) {
 				if toks := bytes.Split(input[8:], []byte{' '}); len(toks) > 0 {
 					for i := range toks {
 						if vals := bytes.Split(toks[i], []byte{'='}); len(vals) == 2 {
-							if bytes.Compare(vals[1], []byte("[UNAVAILABLE]")) == 0 {
+							if bytes.Equal(vals[1], []byte("[UNAVAILABLE]")) {
 								// skip
 								continue
 							}
-							if bytes.Compare(vals[0], []byte("ADDR")) == 0 {
+							if bytes.Equal(vals[0], []byte("ADDR")) {
 								client.RemoteIP = string(vals[1])
 							}
-							if bytes.Compare(vals[0], []byte("HELO")) == 0 {
+							if bytes.Equal(vals[0], []byte("HELO")) {
 								client.Helo = string(vals[1])
 							}
 						}
@@ -473,7 +474,7 @@ func (s *server) handleClient(client *client) {
 					client.sendResponse(r.FailNestedMailCmd)
 					break
 				}
-				client.MailFrom, err = client.parsePath([]byte(input[10:]), client.parser.MailFrom)
+				client.MailFrom, err = client.parsePath(input[10:], client.parser.MailFrom)
 				if err != nil {
 					s.log().WithError(err).Error("MAIL parse error", "["+string(input[10:])+"]")
 					client.sendResponse(err)
@@ -499,7 +500,7 @@ func (s *server) handleClient(client *client) {
 					client.sendResponse(r.ErrorTooManyRecipients)
 					break
 				}
-				to, err := client.parsePath([]byte(input[8:]), client.parser.RcptTo)
+				to, err := client.parsePath(input[8:], client.parser.RcptTo)
 				if err != nil {
 					s.log().WithError(err).Error("RCPT parse error", "["+string(input[8:])+"]")
 					client.sendResponse(err.Error())
@@ -558,7 +559,7 @@ func (s *server) handleClient(client *client) {
 
 			// intentionally placed the limit 1MB above so that reading does not return with an error
 			// if the client goes a little over. Anything above will err
-			client.bufin.setLimit(int64(sc.MaxSize) + 1024000) // This a hard limit.
+			client.bufin.setLimit(sc.MaxSize + 1024000) // This a hard limit.
 			be := s.backend()
 			var (
 				n   int64
@@ -652,23 +653,29 @@ func (s *server) handleClient(client *client) {
 }
 
 func (s *server) log() log.Logger {
-	if l, ok := s.logStore.Load().(log.Logger); ok {
-		return l
-	}
-	l, err := log.GetLogger(log.OutputStderr.String(), log.InfoLevel.String())
-	if err == nil {
-		s.logStore.Store(l)
-	}
-	return l
+	return s.loadLog(&s.logStore)
 }
 
 func (s *server) mainlog() log.Logger {
-	if l, ok := s.mainlogStore.Load().(log.Logger); ok {
+	return s.loadLog(&s.mainlogStore)
+}
+
+func (s *server) loadLog(value *atomic.Value) log.Logger {
+	if l, ok := value.Load().(log.Logger); ok {
 		return l
 	}
-	l, err := log.GetLogger(log.OutputStderr.String(), log.InfoLevel.String())
+	out := log.OutputStderr.String()
+	level := log.InfoLevel.String()
+	if value == &s.logStore {
+		if sc, ok := s.configStore.Load().(ServerConfig); ok && sc.LogFile != "" {
+			out = sc.LogFile
+		}
+		level = s.mainlog().GetLevel()
+	}
+
+	l, err := log.GetLogger(out, level)
 	if err == nil {
-		s.mainlogStore.Store(l)
+		value.Store(l)
 	}
 	return l
 }

+ 6 - 5
server_test.go

@@ -11,12 +11,13 @@ import (
 
 	"crypto/tls"
 	"fmt"
+	"io/ioutil"
+	"net"
+
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mocks"
-	"io/ioutil"
-	"net"
 )
 
 // getMockServerConfig gets a mock ServerConfig struct used for creating a new server
@@ -232,7 +233,7 @@ func TestTLSConfig(t *testing.T) {
 	} else if c.CurvePreferences[0] != tls.CurveP521 && c.CurvePreferences[1] != tls.CurveP384 {
 		t.Error("c.CurvePreferences curves not setup")
 	}
-	if strings.Index(string(c.RootCAs.Subjects()[0]), "Mountain View") == -1 {
+	if !strings.Contains(string(c.RootCAs.Subjects()[0]), "Mountain View") {
 		t.Error("c.RootCAs not correctly set")
 	}
 	if c.ClientAuth != tls.NoClientCert {
@@ -440,7 +441,7 @@ func TestGatewayTimeout(t *testing.T) {
 			expect := "transaction timeout"
 			if err != nil {
 				t.Error(err)
-			} else if strings.Index(str, expect) == -1 {
+			} else if !strings.Contains(str, expect) {
 				t.Error("Expected the reply to have'", expect, "'but got", str)
 			}
 		}
@@ -530,7 +531,7 @@ func TestGatewayPanic(t *testing.T) {
 				t.Error(err)
 			} else {
 				expect := "storage failed"
-				if strings.Index(str, expect) == -1 {
+				if !strings.Contains(str, expect) {
 					t.Error("Expected the reply to have'", expect, "'but got", str)
 				}
 			}

+ 1 - 1
tests/client.go

@@ -32,7 +32,7 @@ func Connect(serverConfig guerrilla.ServerConfig, deadline time.Duration) (net.C
 	bufin = bufio.NewReader(conn)
 
 	// should be ample time to complete the test
-	if err = conn.SetDeadline(time.Now().Add(time.Duration(time.Second * deadline))); err != nil {
+	if err = conn.SetDeadline(time.Now().Add(time.Second * deadline)); err != nil {
 		return conn, bufin, err
 	}
 	// read greeting, ignore it

+ 3 - 4
tests/guerrilla_test.go

@@ -64,7 +64,7 @@ func init() {
 	} else {
 		logger, _ = log.GetLogger(config.LogFile, "debug")
 		initErr = setupCerts(config)
-		if err != nil {
+		if initErr != nil {
 			return
 		}
 		backend, _ := getBackend(config.BackendConfig, logger)
@@ -196,7 +196,6 @@ func TestStart(t *testing.T) {
 	app.Shutdown()
 	if read, err := ioutil.ReadFile("./testlog"); err == nil {
 		logOutput := string(read)
-		//fmt.Println(logOutput)
 		if i := strings.Index(logOutput, "Listening on TCP 127.0.0.1:4654"); i < 0 {
 			t.Error("Server did not listen on 127.0.0.1:4654")
 		}
@@ -249,7 +248,7 @@ func TestGreeting(t *testing.T) {
 			// handle error
 			t.Error("Cannot dial server", config.Servers[0].ListenInterface)
 		}
-		if err := conn.SetReadDeadline(time.Now().Add(time.Duration(time.Millisecond * 500))); err != nil {
+		if err := conn.SetReadDeadline(time.Now().Add(time.Millisecond * 500)); err != nil {
 			t.Error(err)
 		}
 		greeting, err := bufio.NewReader(conn).ReadString('\n')
@@ -276,7 +275,7 @@ func TestGreeting(t *testing.T) {
 			t.Error(err, "Cannot dial server (TLS)", config.Servers[1].ListenInterface)
 			t.FailNow()
 		}
-		if err := conn.SetReadDeadline(time.Now().Add(time.Duration(time.Millisecond * 500))); err != nil {
+		if err := conn.SetReadDeadline(time.Now().Add(time.Millisecond * 500)); err != nil {
 			t.Error(err)
 		}
 		greeting, err = bufio.NewReader(conn).ReadString('\n')

+ 4 - 3
version.go

@@ -13,14 +13,15 @@ var (
 
 func init() {
 	// If version, commit, or build time are not set, make that clear.
+	const unknown = "unknown"
 	if Version == "" {
-		Version = "unknown"
+		Version = unknown
 	}
 	if Commit == "" {
-		Commit = "unknown"
+		Commit = unknown
 	}
 	if BuildTime == "" {
-		BuildTime = "unknown"
+		BuildTime = unknown
 	}
 
 	StartTime = time.Now()