소스 검색

Issue #28 - add new TLS configuration options (#110)

* Issue #28 - add new TLS configuration options 

Adds ability to select ciphers, protocols, curves, client auth policy and set a custom certificate authority pool

Note: This change breaks existing TLS configurations, please see docs for the new TLS options https://github.com/flashmob/go-guerrilla/wiki/Configuration
Flashmob 7 년 전
부모
커밋
9cec012189
17개의 변경된 파일587개의 추가작업 그리고 216개의 파일을 삭제
  1. 3 2
      .travis.yml
  2. 2 0
      README.md
  3. 1 1
      api.go
  4. 17 13
      api_test.go
  5. 1 1
      backends/p_guerrilla_db_redis.go
  6. 6 3
      backends/p_sql.go
  7. 1 1
      client.go
  8. 94 51
      cmd/guerrillad/serve_test.go
  9. 137 56
      config.go
  10. 62 50
      config_test.go
  11. 22 10
      goguerrilla.conf.sample
  12. 1 0
      guerrilla.go
  13. 49 6
      server.go
  14. 156 7
      server_test.go
  15. 1 1
      tests/client.go
  16. 17 14
      tests/guerrilla_test.go
  17. 17 0
      tls_go1.8.go

+ 3 - 2
.travis.yml

@@ -1,9 +1,10 @@
 language: go
 language: go
 sudo: false
 sudo: false
 go:
 go:
-  - 1.5
-  - 1.6
   - 1.7
   - 1.7
+  - 1.8
+  - 1.9
+  - 1.10.x
   - master
   - master
 
 
 install:
 install:

+ 2 - 0
README.md

@@ -1,6 +1,8 @@
 
 
 [![Build Status](https://travis-ci.org/flashmob/go-guerrilla.svg?branch=master)](https://travis-ci.org/flashmob/go-guerrilla)
 [![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.
+
 Go-Guerrilla SMTP Daemon
 Go-Guerrilla SMTP Daemon
 ====================
 ====================
 
 

+ 1 - 1
api.go

@@ -93,7 +93,7 @@ func (d *Daemon) LoadConfig(path string) (AppConfig, error) {
 	var ac AppConfig
 	var ac AppConfig
 	data, err := ioutil.ReadFile(path)
 	data, err := ioutil.ReadFile(path)
 	if err != nil {
 	if err != nil {
-		return ac, fmt.Errorf("Could not read config file: %s", err.Error())
+		return ac, fmt.Errorf("could not read config file: %s", err.Error())
 	}
 	}
 	err = ac.Load(data)
 	err = ac.Load(data)
 	if err != nil {
 	if err != nil {

+ 17 - 13
api_test.go

@@ -133,13 +133,15 @@ func TestSMTPLoadFile(t *testing.T) {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":160,
             "timeout":160,
             "listen_interface":"127.0.0.1:2526",
             "listen_interface":"127.0.0.1:2526",
-            "start_tls_on":false,
-            "tls_always_on":false,
-            "max_clients": 2
+            "max_clients": 2,
+			"tls" : {
+				"private_key_file":"config_test.go",
+            	"public_key_file":"config_test.go",
+				"start_tls_on":false,
+            	"tls_always_on":false
+			}
         }
         }
     ]
     ]
 }
 }
@@ -161,13 +163,15 @@ func TestSMTPLoadFile(t *testing.T) {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":160,
             "timeout":160,
             "listen_interface":"127.0.0.1:2526",
             "listen_interface":"127.0.0.1:2526",
-            "start_tls_on":false,
-            "tls_always_on":false,
-            "max_clients": 2
+            "max_clients": 2,
+			"tls" : {
+ 				"private_key_file":"config_test.go",
+				"public_key_file":"config_test.go",
+				"start_tls_on":false,
+            	"tls_always_on":false
+			}
         }
         }
     ]
     ]
 }
 }
@@ -310,9 +314,9 @@ func TestSetConfigError(t *testing.T) {
 
 
 	// lets add a new server with bad TLS
 	// lets add a new server with bad TLS
 	sc.ListenInterface = "127.0.0.1:2527"
 	sc.ListenInterface = "127.0.0.1:2527"
-	sc.StartTLSOn = true
-	sc.PublicKeyFile = "tests/testlog" // totally wrong :->
-	sc.PublicKeyFile = "tests/testlog" // totally wrong :->
+	sc.TLS.StartTLSOn = true
+	sc.TLS.PublicKeyFile = "tests/testlog"  // totally wrong :->
+	sc.TLS.PrivateKeyFile = "tests/testlog" // totally wrong :->
 
 
 	cfg.Servers = append(cfg.Servers, sc)
 	cfg.Servers = append(cfg.Servers, sc)
 
 

+ 1 - 1
backends/p_guerrilla_db_redis.go

@@ -319,7 +319,7 @@ func (g *GuerrillaDBAndRedisBackend) sqlConnect() (*sql.DB, error) {
 		// do we have access?
 		// do we have access?
 		_, err = db.Query("SELECT mail_id FROM " + g.config.Table + " LIMIT 1")
 		_, err = db.Query("SELECT mail_id FROM " + g.config.Table + " LIMIT 1")
 		if err != nil {
 		if err != nil {
-			Log().Error("cannot select table", err)
+			Log().Error("cannot select table:", err)
 			return nil, err
 			return nil, err
 		}
 		}
 		return db, nil
 		return db, nil

+ 6 - 3
backends/p_sql.go

@@ -246,9 +246,12 @@ func SQL() Decorator {
 						recipient,
 						recipient,
 						s.ip2bint(e.RemoteIP).Bytes(),         // ip_addr store as varbinary(16)
 						s.ip2bint(e.RemoteIP).Bytes(),         // ip_addr store as varbinary(16)
 						trimToLimit(e.MailFrom.String(), 255), // return_path
 						trimToLimit(e.MailFrom.String(), 255), // return_path
-						e.TLS,   // is_tls
-						mid,     // message_id
-						replyTo, // reply_to
+						// is_tls
+						e.TLS,
+						// message_id
+						mid,
+						// reply_to
+						replyTo,
 						sender,
 						sender,
 					)
 					)
 
 

+ 1 - 1
client.go

@@ -178,7 +178,7 @@ func (c *client) getID() uint64 {
 // UpgradeToTLS upgrades a client connection to TLS
 // UpgradeToTLS upgrades a client connection to TLS
 func (client *client) upgradeToTLS(tlsConfig *tls.Config) error {
 func (client *client) upgradeToTLS(tlsConfig *tls.Config) error {
 	var tlsConn *tls.Conn
 	var tlsConn *tls.Conn
-	// load the config thread-safely
+	// wrap client.conn in a new TLS server side connection
 	tlsConn = tls.Server(client.conn, tlsConfig)
 	tlsConn = tls.Server(client.conn, tlsConfig)
 	// Call handshake here to get any handshake error before reading starts
 	// Call handshake here to get any handshake error before reading starts
 	err := tlsConn.Handshake()
 	err := tlsConn.Handshake()

+ 94 - 51
cmd/guerrillad/serve_test.go

@@ -43,27 +43,31 @@ var configJsonA = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:3536",
             "listen_interface":"127.0.0.1:3536",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         },
         },
         {
         {
             "is_enabled" : false,
             "is_enabled" : false,
             "host_name":"enable.test.com",
             "host_name":"enable.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:2228",
             "listen_interface":"127.0.0.1:2228",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+				"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         }
         }
     ]
     ]
 }
 }
@@ -92,20 +96,22 @@ var configJsonB = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:3536",
             "listen_interface":"127.0.0.1:3536",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+            	"start_tls_on":true,
+            	"tls_always_on":false
+			}
         }
         }
     ]
     ]
 }
 }
 `
 `
 
 
-// backend_name changed, is guerrilla-redis-db + added a server
+// added a server
 var configJsonC = `
 var configJsonC = `
 {
 {
     "log_file" : "../../tests/testlog",
     "log_file" : "../../tests/testlog",
@@ -118,46 +124,49 @@ var configJsonC = `
       "guerrillamail.net",
       "guerrillamail.net",
       "guerrillamail.org"
       "guerrillamail.org"
     ],
     ],
-    "backend_name": "guerrilla-redis-db",
     "backend_config" :
     "backend_config" :
         {
         {
             "sql_driver": "mysql",
             "sql_driver": "mysql",
-            "sql_dsn": "root:ok@tcp(127.0.0.1:3306)/gmail_mail?readTimeout=10&writeTimeout=10",
+            "sql_dsn": "root:ok@tcp(127.0.0.1:3306)/gmail_mail?readTimeout=10s&writeTimeout=10s",
             "mail_table":"new_mail",
             "mail_table":"new_mail",
             "redis_interface" : "127.0.0.1:6379",
             "redis_interface" : "127.0.0.1:6379",
             "redis_expire_seconds" : 7200,
             "redis_expire_seconds" : 7200,
             "save_workers_size" : 3,
             "save_workers_size" : 3,
             "primary_mail_host":"sharklasers.com",
             "primary_mail_host":"sharklasers.com",
             "save_workers_size" : 1,
             "save_workers_size" : 1,
-	    "save_process": "HeadersParser|Debugger",
-	    "log_received_mails": true
+	    	"save_process": "HeadersParser|Debugger",
+	    	"log_received_mails": true
         },
         },
     "servers" : [
     "servers" : [
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:25",
             "listen_interface":"127.0.0.1:25",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         },
         },
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size":1000000,
             "max_size":1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:465",
             "listen_interface":"127.0.0.1:465",
-            "start_tls_on":false,
-            "tls_always_on":true,
             "max_clients":500,
             "max_clients":500,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":false,
+            	"tls_always_on":true
+			}
         }
         }
     ]
     ]
 }
 }
@@ -186,27 +195,31 @@ var configJsonD = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:2552",
             "listen_interface":"127.0.0.1:2552",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         },
         },
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"secure.test.com",
             "host_name":"secure.test.com",
             "max_size":1000000,
             "max_size":1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:4655",
             "listen_interface":"127.0.0.1:4655",
-            "start_tls_on":false,
-            "tls_always_on":true,
             "max_clients":500,
             "max_clients":500,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+				"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":false,
+            	"tls_always_on":true
+			}
         }
         }
     ]
     ]
 }
 }
@@ -231,7 +244,7 @@ var configJsonE = `
             "save_process": "GuerrillaRedisDB",
             "save_process": "GuerrillaRedisDB",
             "log_received_mails" : true,
             "log_received_mails" : true,
             "sql_driver": "mysql",
             "sql_driver": "mysql",
-            "sql_dsn": "root:secret@tcp(127.0.0.1:3306)/gmail_mail?readTimeout=10&writeTimeout=10",
+            "sql_dsn": "root:secret@tcp(127.0.0.1:3306)/gmail_mail?readTimeout=10s&writeTimeout=10s",
             "mail_table":"new_mail",
             "mail_table":"new_mail",
             "redis_interface" : "127.0.0.1:6379",
             "redis_interface" : "127.0.0.1:6379",
             "redis_expire_seconds" : 7200,
             "redis_expire_seconds" : 7200,
@@ -243,27 +256,31 @@ var configJsonE = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:2552",
             "listen_interface":"127.0.0.1:2552",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         },
         },
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"secure.test.com",
             "host_name":"secure.test.com",
             "max_size":1000000,
             "max_size":1000000,
-            "private_key_file":"../..//tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:4655",
             "listen_interface":"127.0.0.1:4655",
-            "start_tls_on":false,
-            "tls_always_on":true,
             "max_clients":500,
             "max_clients":500,
-            "log_file" : "../../tests/testlog"
+            "log_file" : "../../tests/testlog",
+			"tls" : {
+				"private_key_file":"../../tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"../../tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":false,
+            	"tls_always_on":true
+			}
         }
         }
     ]
     ]
 }
 }
@@ -667,6 +684,32 @@ func TestServerStopEvent(t *testing.T) {
 
 
 }
 }
 
 
+// just a utility for debugging when using the debugger, skipped by default
+func TestDebug(t *testing.T) {
+
+	t.SkipNow()
+	conf := guerrilla.ServerConfig{ListenInterface: "127.0.0.1:2526"}
+	if conn, buffin, err := test.Connect(conf, 20); err != nil {
+		t.Error("Could not connect to new server", conf.ListenInterface, err)
+	} else {
+		if result, err := test.Command(conn, buffin, "HELO"); err == nil {
+			expect := "250 mai1.guerrillamail.com Hello"
+			if strings.Index(result, expect) != 0 {
+				t.Error("Expected", expect, "but got", result)
+			} else {
+				if result, err = test.Command(conn, buffin, "RCPT TO:[email protected]"); err == nil {
+					expect := "250 2.1.5 OK"
+					if strings.Index(result, expect) != 0 {
+						t.Error("Expected:", expect, "but got:", result)
+					}
+				}
+			}
+		}
+		conn.Close()
+
+	}
+}
+
 // Start with configJsonD.json,
 // Start with configJsonD.json,
 // then connect to 127.0.0.1:4655 & HELO & try RCPT TO with an invalid host [grr.la]
 // then connect to 127.0.0.1:4655 & HELO & try RCPT TO with an invalid host [grr.la]
 // then change the config to enable add new host [grr.la] to allowed_hosts
 // then change the config to enable add new host [grr.la] to allowed_hosts

+ 137 - 56
config.go

@@ -41,20 +41,13 @@ type ServerConfig struct {
 	// MaxSize is the maximum size of an email that will be accepted for delivery.
 	// MaxSize is the maximum size of an email that will be accepted for delivery.
 	// Defaults to 10 Mebibytes
 	// Defaults to 10 Mebibytes
 	MaxSize int64 `json:"max_size"`
 	MaxSize int64 `json:"max_size"`
-	// PrivateKeyFile path to cert private key in PEM format. Will be ignored if blank
-	PrivateKeyFile string `json:"private_key_file"`
-	// PublicKeyFile path to cert (public key) chain in PEM format.
-	// Will be ignored if blank
-	PublicKeyFile string `json:"public_key_file"`
+	// TLS Configuration
+	TLS ServerTLSConfig `json:"tls,omitempty"`
 	// Timeout specifies the connection timeout in seconds. Defaults to 30
 	// Timeout specifies the connection timeout in seconds. Defaults to 30
 	Timeout int `json:"timeout"`
 	Timeout int `json:"timeout"`
 	// Listen interface specified in <ip>:<port> - defaults to 127.0.0.1:2525
 	// Listen interface specified in <ip>:<port> - defaults to 127.0.0.1:2525
 	ListenInterface string `json:"listen_interface"`
 	ListenInterface string `json:"listen_interface"`
-	// StartTLSOn should we offer STARTTLS command. Cert must be valid.
-	// False by default
-	StartTLSOn bool `json:"start_tls_on,omitempty"`
-	// TLSAlwaysOn run this server as a pure TLS server, i.e. SMTPS
-	TLSAlwaysOn bool `json:"tls_always_on,omitempty"`
+
 	// MaxClients controls how many maxiumum clients we can handle at once.
 	// MaxClients controls how many maxiumum clients we can handle at once.
 	// Defaults to 100
 	// Defaults to 100
 	MaxClients int `json:"max_clients"`
 	MaxClients int `json:"max_clients"`
@@ -64,10 +57,95 @@ type ServerConfig struct {
 	// XClientOn when using a proxy such as Nginx, XCLIENT command is used to pass the
 	// XClientOn when using a proxy such as Nginx, XCLIENT command is used to pass the
 	// original client's IP address & client's HELO
 	// original client's IP address & client's HELO
 	XClientOn bool `json:"xclient_on,omitempty"`
 	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"`
+	// TLS Ciphers to use.
+	// Use Go's default if empty
+	Ciphers []string `json:"ciphers,omitempty"`
+	// TLS Curves to use.
+	// Use Go's default if empty
+	Curves []string `json:"curves,omitempty"`
+	// 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 ciphersuite
+	PreferServerCipherSuites bool `json:"prefer_server_cipher_suites,omitempty"`
 
 
 	// The following used to watch certificate changes so that the TLS can be reloaded
 	// The following used to watch certificate changes so that the TLS can be reloaded
-	_privateKeyFile_mtime int
-	_publicKeyFile_mtime  int
+	_privateKeyFile_mtime int64
+	_publicKeyFile_mtime  int64
+}
+
+// https://golang.org/pkg/crypto/tls/#pkg-constants
+// Ciphers introduced before Go 1.7 are listed here,
+// ciphers since Go 1.8, see tls_go1.8.go
+var TLSCiphers = map[string]uint16{
+
+	// // Note: Generally avoid using CBC unless for compatibility
+	"TLS_RSA_WITH_3DES_EDE_CBC_SHA":        tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+	"TLS_RSA_WITH_AES_128_CBC_SHA":         tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+	"TLS_RSA_WITH_AES_256_CBC_SHA":         tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+	"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+	"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":  tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":   tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+	"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":   tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+
+	"TLS_RSA_WITH_RC4_128_SHA":        tls.TLS_RSA_WITH_RC4_128_SHA,
+	"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
+	"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+
+	"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":        tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+	"TLS_ECDHE_RSA_WITH_RC4_128_SHA":          tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":   tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+
+	// Include to prevent downgrade attacks
+	"TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV,
+}
+
+// https://golang.org/pkg/crypto/tls/#pkg-constants
+var TLSProtocols = map[string]uint16{
+	"ssl3.0": tls.VersionSSL30,
+	"tls1.0": tls.VersionTLS10,
+	"tls1.1": tls.VersionTLS11,
+	"tls1.2": tls.VersionTLS12,
+}
+
+// https://golang.org/pkg/crypto/tls/#CurveID
+var TLSCurves = map[string]tls.CurveID{
+	"P256": tls.CurveP256,
+	"P384": tls.CurveP384,
+	"P521": tls.CurveP521,
+}
+
+// https://golang.org/pkg/crypto/tls/#ClientAuthType
+var TLSClientAuthTypes = map[string]tls.ClientAuthType{
+	"NoClientCert":               tls.NoClientCert,
+	"RequestClientCert":          tls.RequestClientCert,
+	"RequireAnyClientCert":       tls.RequireAnyClientCert,
+	"VerifyClientCertIfGiven":    tls.VerifyClientCertIfGiven,
+	"RequireAndVerifyClientCert": tls.RequireAndVerifyClientCert,
 }
 }
 
 
 // Unmarshalls json data into AppConfig struct and any other initialization of the struct
 // Unmarshalls json data into AppConfig struct and any other initialization of the struct
@@ -274,11 +352,16 @@ func (c *AppConfig) setBackendDefaults() error {
 // All events are fired and run synchronously
 // All events are fired and run synchronously
 func (sc *ServerConfig) emitChangeEvents(oldServer *ServerConfig, app Guerrilla) {
 func (sc *ServerConfig) emitChangeEvents(oldServer *ServerConfig, app Guerrilla) {
 	// get a list of changes
 	// get a list of changes
-	changes := getDiff(
+	changes := getChanges(
 		*oldServer,
 		*oldServer,
 		*sc,
 		*sc,
 	)
 	)
-	if len(changes) > 0 {
+	tlsChanges := getChanges(
+		(*oldServer).TLS,
+		(*sc).TLS,
+	)
+
+	if len(changes) > 0 || len(tlsChanges) > 0 {
 		// something changed in the server config
 		// something changed in the server config
 		app.Publish(EventConfigServerConfig, sc)
 		app.Publish(EventConfigServerConfig, sc)
 	}
 	}
@@ -309,22 +392,7 @@ func (sc *ServerConfig) emitChangeEvents(oldServer *ServerConfig, app Guerrilla)
 		app.Publish(EventConfigServerMaxClients, sc)
 		app.Publish(EventConfigServerMaxClients, sc)
 	}
 	}
 
 
-	// tls changed
-	if ok := func() bool {
-		if _, ok := changes["PrivateKeyFile"]; ok {
-			return true
-		}
-		if _, ok := changes["PublicKeyFile"]; ok {
-			return true
-		}
-		if _, ok := changes["StartTLSOn"]; ok {
-			return true
-		}
-		if _, ok := changes["TLSAlwaysOn"]; ok {
-			return true
-		}
-		return false
-	}(); ok {
+	if len(tlsChanges) > 0 {
 		app.Publish(EventConfigServerTLSConfig, sc)
 		app.Publish(EventConfigServerTLSConfig, sc)
 	}
 	}
 }
 }
@@ -338,37 +406,31 @@ func (sc *ServerConfig) loadTlsKeyTimestamps() error {
 				iface,
 				iface,
 				err.Error()))
 				err.Error()))
 	}
 	}
-	if info, err := os.Stat(sc.PrivateKeyFile); err == nil {
-		sc._privateKeyFile_mtime = info.ModTime().Second()
+	if info, err := os.Stat(sc.TLS.PrivateKeyFile); err == nil {
+		sc.TLS._privateKeyFile_mtime = info.ModTime().Unix()
 	} else {
 	} else {
 		return statErr(sc.ListenInterface, err)
 		return statErr(sc.ListenInterface, err)
 	}
 	}
-	if info, err := os.Stat(sc.PublicKeyFile); err == nil {
-		sc._publicKeyFile_mtime = info.ModTime().Second()
+	if info, err := os.Stat(sc.TLS.PublicKeyFile); err == nil {
+		sc.TLS._publicKeyFile_mtime = info.ModTime().Unix()
 	} else {
 	} else {
 		return statErr(sc.ListenInterface, err)
 		return statErr(sc.ListenInterface, err)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-// Gets the timestamp of the TLS certificates. Returns a unix time of when they were last modified
-// when the config was read. We use this info to determine if TLS needs to be re-loaded.
-func (sc *ServerConfig) getTlsKeyTimestamps() (int, int) {
-	return sc._privateKeyFile_mtime, sc._publicKeyFile_mtime
-}
-
 // Validate validates the server's configuration.
 // Validate validates the server's configuration.
 func (sc *ServerConfig) Validate() error {
 func (sc *ServerConfig) Validate() error {
 	var errs Errors
 	var errs Errors
 
 
-	if sc.StartTLSOn || sc.TLSAlwaysOn {
-		if sc.PublicKeyFile == "" {
+	if sc.TLS.StartTLSOn || sc.TLS.AlwaysOn {
+		if sc.TLS.PublicKeyFile == "" {
 			errs = append(errs, errors.New("PublicKeyFile is empty"))
 			errs = append(errs, errors.New("PublicKeyFile is empty"))
 		}
 		}
-		if sc.PrivateKeyFile == "" {
+		if sc.TLS.PrivateKeyFile == "" {
 			errs = append(errs, errors.New("PrivateKeyFile is empty"))
 			errs = append(errs, errors.New("PrivateKeyFile is empty"))
 		}
 		}
-		if _, err := tls.LoadX509KeyPair(sc.PublicKeyFile, sc.PrivateKeyFile); err != nil {
+		if _, err := tls.LoadX509KeyPair(sc.TLS.PublicKeyFile, sc.TLS.PrivateKeyFile); err != nil {
 			errs = append(errs,
 			errs = append(errs,
 				errors.New(fmt.Sprintf("cannot use TLS config for [%s], %v", sc.ListenInterface, err)))
 				errors.New(fmt.Sprintf("cannot use TLS config for [%s], %v", sc.ListenInterface, err)))
 		}
 		}
@@ -380,28 +442,42 @@ func (sc *ServerConfig) Validate() error {
 	return nil
 	return nil
 }
 }
 
 
-// Returns a diff between struct a & struct b.
+// Gets the timestamp of the TLS certificates. Returns a unix time of when they were last modified
+// when the config was read. We use this info to determine if TLS needs to be re-loaded.
+func (stc *ServerTLSConfig) getTlsKeyTimestamps() (int64, int64) {
+	return stc._privateKeyFile_mtime, stc._publicKeyFile_mtime
+}
+
+// Returns value changes between struct a & struct b.
 // Results are returned in a map, where each key is the name of the field that was different.
 // Results are returned in a map, where each key is the name of the field that was different.
 // a and b are struct values, must not be pointer
 // a and b are struct values, must not be pointer
 // and of the same struct type
 // and of the same struct type
-func getDiff(a interface{}, b interface{}) map[string]interface{} {
+func getChanges(a interface{}, b interface{}) map[string]interface{} {
 	ret := make(map[string]interface{}, 5)
 	ret := make(map[string]interface{}, 5)
 	compareWith := structtomap(b)
 	compareWith := structtomap(b)
 	for key, val := range structtomap(a) {
 	for key, val := range structtomap(a) {
+		if sliceOfStr, ok := val.([]string); ok {
+			val, _ = json.Marshal(sliceOfStr)
+			val = string(val.([]uint8))
+		}
+		if sliceOfStr, ok := compareWith[key].([]string); ok {
+			compareWith[key], _ = json.Marshal(sliceOfStr)
+			compareWith[key] = string(compareWith[key].([]uint8))
+		}
 		if val != compareWith[key] {
 		if val != compareWith[key] {
 			ret[key] = compareWith[key]
 			ret[key] = compareWith[key]
 		}
 		}
 	}
 	}
-	// detect tls changes (have the key files been modified?)
-	if oldServer, ok := a.(ServerConfig); ok {
-		t1, t2 := oldServer.getTlsKeyTimestamps()
-		if newServer, ok := b.(ServerConfig); ok {
-			t3, t4 := newServer.getTlsKeyTimestamps()
+	// detect changes to TLS keys (have the key files been modified?)
+	if oldTLS, ok := a.(ServerTLSConfig); ok {
+		t1, t2 := oldTLS.getTlsKeyTimestamps()
+		if newTLS, ok := b.(ServerTLSConfig); ok {
+			t3, t4 := newTLS.getTlsKeyTimestamps()
 			if t1 != t3 {
 			if t1 != t3 {
-				ret["PrivateKeyFile"] = newServer.PrivateKeyFile
+				ret["PrivateKeyFile"] = newTLS.PrivateKeyFile
 			}
 			}
 			if t2 != t4 {
 			if t2 != t4 {
-				ret["PublicKeyFile"] = newServer.PublicKeyFile
+				ret["PublicKeyFile"] = newTLS.PublicKeyFile
 			}
 			}
 		}
 		}
 	}
 	}
@@ -409,7 +485,8 @@ func getDiff(a interface{}, b interface{}) map[string]interface{} {
 }
 }
 
 
 // Convert fields of a struct to a map
 // Convert fields of a struct to a map
-// only able to convert int, bool and string; not recursive
+// 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{} {
 func structtomap(obj interface{}) map[string]interface{} {
 	ret := make(map[string]interface{}, 0)
 	ret := make(map[string]interface{}, 0)
 	v := reflect.ValueOf(obj)
 	v := reflect.ValueOf(obj)
@@ -417,9 +494,11 @@ func structtomap(obj interface{}) map[string]interface{} {
 	for index := 0; index < v.NumField(); index++ {
 	for index := 0; index < v.NumField(); index++ {
 		vField := v.Field(index)
 		vField := v.Field(index)
 		fName := t.Field(index).Name
 		fName := t.Field(index).Name
-
-		switch vField.Kind() {
+		k := vField.Kind()
+		switch k {
 		case reflect.Int:
 		case reflect.Int:
+			fallthrough
+		case reflect.Int64:
 			value := vField.Int()
 			value := vField.Int()
 			ret[fName] = value
 			ret[fName] = value
 		case reflect.String:
 		case reflect.String:
@@ -428,6 +507,8 @@ func structtomap(obj interface{}) map[string]interface{} {
 		case reflect.Bool:
 		case reflect.Bool:
 			value := vField.Bool()
 			value := vField.Bool()
 			ret[fName] = value
 			ret[fName] = value
+		case reflect.Slice:
+			ret[fName] = vField.Interface().([]string)
 		}
 		}
 	}
 	}
 	return ret
 	return ret

+ 62 - 50
config_test.go

@@ -33,55 +33,59 @@ var configJsonA = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":160,
             "timeout":160,
             "listen_interface":"127.0.0.1:2526",
             "listen_interface":"127.0.0.1:2526",
-            "start_tls_on":false,
-            "tls_always_on":false,
-            "max_clients": 2
+            "max_clients": 2,
+			"tls" : {
+				"start_tls_on":false,
+            	"tls_always_on":false,
+				"private_key_file":"config_test.go",
+            	"public_key_file":"config_test.go"
+			}
         },
         },
-
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail2.guerrillamail.com",
             "host_name":"mail2.guerrillamail.com",
             "max_size":1000001,
             "max_size":1000001,
-            "private_key_file":"./tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file":"./tests/mail2.guerrillamail.com.cert.pem",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:2527",
             "listen_interface":"127.0.0.1:2527",
-            "start_tls_on":true,
-            "tls_always_on":false,
-            "max_clients":1
+			"max_clients":1,
+			"tls" : {
+ 				"private_key_file":"./tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file":"./tests/mail2.guerrillamail.com.cert.pem",
+				"tls_always_on":false,
+            	"start_tls_on":true
+			}
         },
         },
 
 
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.stopme.com",
             "host_name":"mail.stopme.com",
-            "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
+            "max_size": 100017, 
             "timeout":160,
             "timeout":160,
-            "listen_interface":"127.0.0.1:9999",
-            "start_tls_on":false,
-            "tls_always_on":false,
-            "max_clients": 2
+            "listen_interface":"127.0.0.1:9999", 
+            "max_clients": 2,
+			"tls" : {
+				"private_key_file":"config_test.go",
+            	"public_key_file":"config_test.go",
+				"start_tls_on":false,
+            	"tls_always_on":false
+			}
         },
         },
-
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.disableme.com",
             "host_name":"mail.disableme.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":160,
             "timeout":160,
             "listen_interface":"127.0.0.1:3333",
             "listen_interface":"127.0.0.1:3333",
-            "start_tls_on":false,
-            "tls_always_on":false,
-            "max_clients": 2
+            "max_clients": 2,
+			"tls" : { 
+				"private_key_file":"config_test.go",
+            	"public_key_file":"config_test.go",
+				"start_tls_on":false,
+				"tls_always_on":false
+			}
         }
         }
-
-
     ]
     ]
 }
 }
 `
 `
@@ -106,52 +110,60 @@ var configJsonB = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":161,
             "timeout":161,
             "listen_interface":"127.0.0.1:2526",
             "listen_interface":"127.0.0.1:2526",
-            "start_tls_on":false,
-            "tls_always_on":true,
-            "max_clients": 3
+            "max_clients": 3,
+			"tls" : {
+ 				"private_key_file":"./config_test.go",
+            	"public_key_file":"./config_test.go",
+				"start_tls_on":false,
+            	"tls_always_on":true
+			}
         },
         },
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail2.guerrillamail.com",
             "host_name":"mail2.guerrillamail.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"./tests/mail2.guerrillamail.com.key.pem",
-            "public_key_file": "./tests/mail2.guerrillamail.com.cert.pem",
             "timeout":160,
             "timeout":160,
             "listen_interface":"127.0.0.1:2527",
             "listen_interface":"127.0.0.1:2527",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "log_file" : "./tests/testlog",
             "log_file" : "./tests/testlog",
-            "max_clients": 2
+            "max_clients": 2,
+			"tls" : {
+				"private_key_file":"./tests/mail2.guerrillamail.com.key.pem",
+            	"public_key_file": "./tests/mail2.guerrillamail.com.cert.pem",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         },
         },
 
 
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size":1000001,
             "max_size":1000001,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:4654",
             "listen_interface":"127.0.0.1:4654",
-            "start_tls_on":false,
-            "tls_always_on":false,
-            "max_clients":1
+            "max_clients":1,
+			"tls" : {
+				"private_key_file":"config_test.go",
+				"public_key_file":"config_test.go",
+				"start_tls_on":false,
+            	"tls_always_on":false
+			}
         },
         },
 
 
         {
         {
             "is_enabled" : false,
             "is_enabled" : false,
             "host_name":"mail.disbaleme.com",
             "host_name":"mail.disbaleme.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"config_test.go",
-            "public_key_file":"config_test.go",
             "timeout":160,
             "timeout":160,
             "listen_interface":"127.0.0.1:3333",
             "listen_interface":"127.0.0.1:3333",
-            "start_tls_on":true,
-            "tls_always_on":false,
-            "max_clients": 2
+            "max_clients": 2,
+			"tls" : {
+				"private_key_file":"config_test.go",
+            	"public_key_file":"config_test.go",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         }
         }
     ]
     ]
 }
 }
@@ -169,8 +181,8 @@ func TestConfigLoad(t *testing.T) {
 		t.SkipNow()
 		t.SkipNow()
 	}
 	}
 	// did we got the timestamps?
 	// did we got the timestamps?
-	if ac.Servers[0]._privateKeyFile_mtime <= 0 {
-		t.Error("failed to read timestamp for _privateKeyFile_mtime, got", ac.Servers[0]._privateKeyFile_mtime)
+	if ac.Servers[0].TLS._privateKeyFile_mtime <= 0 {
+		t.Error("failed to read timestamp for _privateKeyFile_mtime, got", ac.Servers[0].TLS._privateKeyFile_mtime)
 	}
 	}
 }
 }
 
 
@@ -209,8 +221,8 @@ func TestConfigChangeEvents(t *testing.T) {
 	// simulate timestamp change
 	// simulate timestamp change
 
 
 	time.Sleep(time.Second + time.Millisecond*500)
 	time.Sleep(time.Second + time.Millisecond*500)
-	os.Chtimes(oldconf.Servers[1].PrivateKeyFile, time.Now(), time.Now())
-	os.Chtimes(oldconf.Servers[1].PublicKeyFile, time.Now(), time.Now())
+	os.Chtimes(oldconf.Servers[1].TLS.PrivateKeyFile, time.Now(), time.Now())
+	os.Chtimes(oldconf.Servers[1].TLS.PublicKeyFile, time.Now(), time.Now())
 	newconf := &AppConfig{}
 	newconf := &AppConfig{}
 	newconf.Load([]byte(configJsonB))
 	newconf.Load([]byte(configJsonB))
 	newconf.Servers[0].LogFile = log.OutputOff.String() // test for log file change
 	newconf.Servers[0].LogFile = log.OutputOff.String() // test for log file change

+ 22 - 10
goguerrilla.conf.sample

@@ -22,27 +22,39 @@
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size": 1000000,
             "max_size": 1000000,
-            "private_key_file":"/path/to/pem/file/test.com.key",
-            "public_key_file":"/path/to/pem/file/test.com.crt",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:25",
             "listen_interface":"127.0.0.1:25",
-            "start_tls_on":true,
-            "tls_always_on":false,
             "max_clients": 1000,
             "max_clients": 1000,
-            "log_file" : "stderr"
+            "log_file" : "stderr",
+            "tls" : {
+                "start_tls_on":true,
+                "tls_always_on":false,
+                "private_key_file":"/path/to/pem/file/test.com.key",
+                "public_key_file":"/path/to/pem/file/test.com.crt",
+                "protocols" : ["ssl3.0", "tls1.2"],
+                "ciphers" : ["TLS_FALLBACK_SCSV", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"],
+                "curves" : ["P256", "P384", "P521", "X25519"],
+                "client_auth_type" : "NoClientCert"
+            }
         },
         },
         {
         {
             "is_enabled" : false,
             "is_enabled" : false,
             "host_name":"mail.test.com",
             "host_name":"mail.test.com",
             "max_size":1000000,
             "max_size":1000000,
-            "private_key_file":"/path/to/pem/file/test.com.key",
-            "public_key_file":"/path/to/pem/file/test.com.crt",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:465",
             "listen_interface":"127.0.0.1:465",
-            "start_tls_on":false,
-            "tls_always_on":true,
             "max_clients":500,
             "max_clients":500,
-            "log_file" : "stderr"
+            "log_file" : "stderr",
+            "tls" : {
+                "private_key_file":"/path/to/pem/file/test.com.key",
+                "public_key_file":"/path/to/pem/file/test.com.crt",
+                 "start_tls_on":false,
+                 "tls_always_on":true,
+                 "protocols" : ["ssl3.0", "tls1.2"],
+                 "ciphers" : ["TLS_FALLBACK_SCSV", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"],
+                 "curves" : ["P256", "P384", "P521", "X25519"],
+                 "client_auth_type" : "NoClientCert"
+            }
         }
         }
     ]
     ]
 }
 }

+ 1 - 0
guerrilla.go

@@ -247,6 +247,7 @@ func (g *guerrilla) subscribeEvents() {
 	// server config was updated
 	// server config was updated
 	g.Subscribe(EventConfigServerConfig, func(sc *ServerConfig) {
 	g.Subscribe(EventConfigServerConfig, func(sc *ServerConfig) {
 		g.setServerConfig(sc)
 		g.setServerConfig(sc)
+		g.mainlog().Infof("server %s config change event, a new config has been saved", sc.ListenInterface)
 	})
 	})
 
 
 	// add a new server to the config & start
 	// add a new server to the config & start

+ 49 - 6
server.go

@@ -11,10 +11,12 @@ import (
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
 
 
+	"crypto/x509"
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/response"
 	"github.com/flashmob/go-guerrilla/response"
+	"io/ioutil"
 )
 )
 
 
 const (
 const (
@@ -100,8 +102,8 @@ func newServer(sc *ServerConfig, b backends.Backend, l log.Logger) (*server, err
 
 
 func (s *server) configureSSL() error {
 func (s *server) configureSSL() error {
 	sConfig := s.configStore.Load().(ServerConfig)
 	sConfig := s.configStore.Load().(ServerConfig)
-	if sConfig.TLSAlwaysOn || sConfig.StartTLSOn {
-		cert, err := tls.LoadX509KeyPair(sConfig.PublicKeyFile, sConfig.PrivateKeyFile)
+	if sConfig.TLS.AlwaysOn || sConfig.TLS.StartTLSOn {
+		cert, err := tls.LoadX509KeyPair(sConfig.TLS.PublicKeyFile, sConfig.TLS.PrivateKeyFile)
 		if err != nil {
 		if err != nil {
 			return fmt.Errorf("error while loading the certificate: %s", err)
 			return fmt.Errorf("error while loading the certificate: %s", err)
 		}
 		}
@@ -110,6 +112,47 @@ func (s *server) configureSSL() error {
 			ClientAuth:   tls.VerifyClientCertIfGiven,
 			ClientAuth:   tls.VerifyClientCertIfGiven,
 			ServerName:   sConfig.Hostname,
 			ServerName:   sConfig.Hostname,
 		}
 		}
+		if len(sConfig.TLS.Protocols) > 0 {
+			if min, ok := TLSProtocols[sConfig.TLS.Protocols[0]]; ok {
+				tlsConfig.MinVersion = min
+			}
+		}
+		if len(sConfig.TLS.Protocols) > 1 {
+			if max, ok := TLSProtocols[sConfig.TLS.Protocols[1]]; ok {
+				tlsConfig.MaxVersion = max
+			}
+		}
+		if len(sConfig.TLS.Ciphers) > 0 {
+			for _, val := range sConfig.TLS.Ciphers {
+				if c, ok := TLSCiphers[val]; ok {
+					tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, c)
+				}
+			}
+		}
+		if len(sConfig.TLS.Curves) > 0 {
+			for _, val := range sConfig.TLS.Curves {
+				if c, ok := TLSCurves[val]; ok {
+					tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, c)
+				}
+			}
+		}
+		if len(sConfig.TLS.RootCAs) > 0 {
+			caCert, err := ioutil.ReadFile(sConfig.TLS.RootCAs)
+			if err != nil {
+				s.log().WithError(err).Errorf("failed opening TLSRootCAs file [%s]", sConfig.TLS.RootCAs)
+			} else {
+				caCertPool := x509.NewCertPool()
+				caCertPool.AppendCertsFromPEM(caCert)
+				tlsConfig.RootCAs = caCertPool
+			}
+
+		}
+		if len(sConfig.TLS.ClientAuthType) > 0 {
+			if ca, ok := TLSClientAuthTypes[sConfig.TLS.ClientAuthType]; ok {
+				tlsConfig.ClientAuth = ca
+			}
+		}
+		tlsConfig.PreferServerCipherSuites = sConfig.TLS.PreferServerCipherSuites
 		tlsConfig.Rand = rand.Reader
 		tlsConfig.Rand = rand.Reader
 		s.tlsConfigStore.Store(tlsConfig)
 		s.tlsConfigStore.Store(tlsConfig)
 	}
 	}
@@ -303,7 +346,7 @@ func (server *server) handleClient(client *client) {
 	// Also, Last line has no dash -
 	// Also, Last line has no dash -
 	help := "250 HELP"
 	help := "250 HELP"
 
 
-	if sc.TLSAlwaysOn {
+	if sc.TLS.AlwaysOn {
 		tlsConfig, ok := server.tlsConfigStore.Load().(*tls.Config)
 		tlsConfig, ok := server.tlsConfigStore.Load().(*tls.Config)
 		if !ok {
 		if !ok {
 			server.mainlog().Error("Failed to load *tls.Config")
 			server.mainlog().Error("Failed to load *tls.Config")
@@ -315,7 +358,7 @@ func (server *server) handleClient(client *client) {
 			client.kill()
 			client.kill()
 		}
 		}
 	}
 	}
-	if !sc.StartTLSOn {
+	if !sc.TLS.StartTLSOn {
 		// STARTTLS turned off, don't advertise it
 		// STARTTLS turned off, don't advertise it
 		advertiseTLS = ""
 		advertiseTLS = ""
 	}
 	}
@@ -460,7 +503,7 @@ func (server *server) handleClient(client *client) {
 				client.sendResponse(response.Canned.SuccessDataCmd)
 				client.sendResponse(response.Canned.SuccessDataCmd)
 				client.state = ClientData
 				client.state = ClientData
 
 
-			case sc.StartTLSOn && strings.Index(cmd, "STARTTLS") == 0:
+			case sc.TLS.StartTLSOn && strings.Index(cmd, "STARTTLS") == 0:
 
 
 				client.sendResponse(response.Canned.SuccessStartTLSCmd)
 				client.sendResponse(response.Canned.SuccessStartTLSCmd)
 				client.state = ClientStartTLS
 				client.state = ClientStartTLS
@@ -512,7 +555,7 @@ func (server *server) handleClient(client *client) {
 			client.resetTransaction()
 			client.resetTransaction()
 
 
 		case ClientStartTLS:
 		case ClientStartTLS:
-			if !client.TLS && sc.StartTLSOn {
+			if !client.TLS && sc.TLS.StartTLSOn {
 				tlsConfig, ok := server.tlsConfigStore.Load().(*tls.Config)
 				tlsConfig, ok := server.tlsConfigStore.Load().(*tls.Config)
 				if !ok {
 				if !ok {
 					server.mainlog().Error("Failed to load *tls.Config")
 					server.mainlog().Error("Failed to load *tls.Config")

+ 156 - 7
server_test.go

@@ -8,26 +8,31 @@ import (
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
+	"crypto/tls"
 	"fmt"
 	"fmt"
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/log"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mail"
 	"github.com/flashmob/go-guerrilla/mocks"
 	"github.com/flashmob/go-guerrilla/mocks"
+	"io/ioutil"
 	"net"
 	"net"
+	"os"
 )
 )
 
 
 // getMockServerConfig gets a mock ServerConfig struct used for creating a new server
 // getMockServerConfig gets a mock ServerConfig struct used for creating a new server
 func getMockServerConfig() *ServerConfig {
 func getMockServerConfig() *ServerConfig {
 	sc := &ServerConfig{
 	sc := &ServerConfig{
-		IsEnabled:       true, // not tested here
-		Hostname:        "saggydimes.test.com",
-		MaxSize:         1024, // smtp message max size
-		PrivateKeyFile:  "./tests/mail.guerrillamail.com.key.pem",
-		PublicKeyFile:   "./tests/mail.guerrillamail.com.cert.pem",
+		IsEnabled: true, // not tested here
+		Hostname:  "saggydimes.test.com",
+		MaxSize:   1024, // smtp message max size
+		TLS: ServerTLSConfig{
+			PrivateKeyFile: "./tests/mail.guerrillamail.com.key.pem",
+			PublicKeyFile:  "./tests/mail.guerrillamail.com.cert.pem",
+			StartTLSOn:     true,
+			AlwaysOn:       false,
+		},
 		Timeout:         5,
 		Timeout:         5,
 		ListenInterface: "127.0.0.1:2529",
 		ListenInterface: "127.0.0.1:2529",
-		StartTLSOn:      true,
-		TLSAlwaysOn:     false,
 		MaxClients:      30, // not tested here
 		MaxClients:      30, // not tested here
 		LogFile:         "./tests/testlog",
 		LogFile:         "./tests/testlog",
 	}
 	}
@@ -60,6 +65,150 @@ func getMockServerConn(sc *ServerConfig, t *testing.T) (*mocks.Conn, *server) {
 	return conn, server
 	return conn, server
 }
 }
 
 
+// test the RootCAs tls config setting
+var rootCAPK = `-----BEGIN CERTIFICATE-----
+MIIDqjCCApKgAwIBAgIJALh2TrsBR5MiMA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEhMB8G
+A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv
+c3QwIBcNMTgwNTE4MDYzOTU2WhgPMjExODA0MjQwNjM5NTZaMGkxCzAJBgNVBAYT
+AlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEhMB8GA1UE
+CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCcb0ulYT1o5ysor5UtWYW
+q/ZY3PyK3/4YBZq5JoX4xk7GNQQ+3p/Km7QPoBXfgjFLZXEV2R0bE5hHMXfLa5Xb
+64acb9VqCqDvPFXcaNP4rEdBKDVN2p0PEi917tcKBSrZn5Yl+iOhtcBpQDvhHgn/
+9MdmIAKB3+yK+4l9YhT40XfDXCQqzfg4XcNaEgTzZHcDJz+KjWJuJChprcx27MTI
+Ndxs9nmFA2rK16rjgjtwjZ4t9dXsljdOcx59s6dIQ0GnEM8qdKxi/vEx4+M/hbGf
+v7H75LsuKRrVJINAmfy9fmc6VAXjFU0ZVxGK5eVnzsh/hY08TSSrlCCKAJpksjJz
+AgMBAAGjUzBRMB0GA1UdDgQWBBSZsYWs+8FYe4z4c6LLmFB4TeeV/jAfBgNVHSME
+GDAWgBSZsYWs+8FYe4z4c6LLmFB4TeeV/jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBCwUAA4IBAQAcXt/FaILkOCMj8bTUx42vi2N9ZTiEuRbYi24IyGOokbDR
+pSsIxiz+HDPUuX6/X/mHidl24aS9wdv5JTXMr44/BeGK1WC7gMueZBxAqONpaG1Q
+VU0e3q1YwXKcupKQ7kVWl0fuY3licv0+s4zBcTLKkmWAYqsb/n0KtCMyqewi+Rqa
+Zj5Z3OcWOq9Ad9fZWKcG8k/sgeTk9z0X1mZcEyWWxqsUmxvN+SdWLoug1xJVVbMN
+CipZ0vBIi9KOhQgzuIFhoTcd6myUtov52/EFqlX6UuFpY2gEWw/f/yu+SI08v4w9
+KwxgAKBkhx2JYZKtu1EsPIMDyS0aahcDnHqnrGAi
+-----END CERTIFICATE-----`
+
+var clientPrvKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA5ZLmMBdKkVyVmN0VhDSFGvgKp24ejHPCv+wfuf3vlU9cwKfH
+R3vejleZAVRcidscfA0Jsub/Glsr0XwecagtpvTI+Fp1ik6sICOz+VW3958qaAi8
+TjbUMjcDHJeSLcjr725CH5uIvhRzR+daYaJQhAcL2MEt8M9WIF6AjtDZEH9R6oM8
+t5FkO0amImlnipYXNBFghmzkZzfGXXRQLw2A+u6keLcjCrn9h2BaofGIjQfYcu/3
+fH4cIFR4z/soGKameqnCUz7dWmbf4tAI+8QR0VXXBKhiHDm98tPSeH994hC52Uul
+rjEVcM5Uox5hazS2PK06oSc1YuFZONqeeGqj6wIDAQABAoIBADERzRHKaK3ZVEBw
+QQEZGLpC+kP/TZhHxgCvv7hJhsQrSnADbJzi5RcXsiSOm5j7tILvZntO1IgVpLAK
+D5fLkrZ069/pteXyGuhjuTw6DjBnXPEPrPAq2ABDse6SlzQiFgv/TTLkU74NMPbV
+hIQJ5ZvSxb12zRMDviz9Bg2ApmTX6k2iPjQBnEHgKzb64IdMcEb5HE1qNt0v0lRA
+sGBMZZKQWbt2m0pSbAbnB3S9GcpJkRgFFMdTaUScIWO6ICT2hBP2pw2/4M2Zrmlt
+bsyWu9uswBzhvu+/pg2E66V6mji0EzDMlXqjlO5jro6t7P33t1zkd/i/ykKmtDLp
+IpR94UECgYEA9Y4EIjOyaBWJ6TRQ6a/tehGPbwIOgvEiTYXRJqdU49qn/i4YZjSm
+F4iibJz+JeOIQXSwa9F7gRlaspIuHgIJoer7BrITMuhr+afqMLkxK0pijul/qAbm
+HdpFn8IxjpNu4/GoAENbEVy50SMST9yWh5ulEkHHftd4/NJKoJQ2PZ8CgYEA71bb
+lFVh1MFclxRKECmpyoqUAzwGlMoHJy/jaBYuWG4X7rzxqDRrgPH3as6gXpRiSZ+K
+5fC+wcU7dKnHtJOkBDk6J5ev2+hbwg+yq3w4+l3bPDvf2TJyXjXjRDZo12pxFD58
+ybCOF6ItbIDXqT5pvo3PMjgMwu1Ycie+h6hA3jUCgYEAsq93XpQT/R2/T44cWxEE
+VFG2+GacvLhP5+26ttAJPA1/Nb3BT458Vp+84iCT6GpcWpVZU/wKTXVvxIYPPRLq
+g4MEzGiFBASRngiMqIv6ta/ZbHmJxXHPvmV5SLn9aezrQsA1KovZFxdMuF03FBpH
+B8NBKbnoO+r8Ra2ZVKTFm60CgYAZw8Dpi/N3IsWj4eRDLyj/C8H5Qyn2NHVmq4oQ
+d2rPzDI5Wg+tqs7z15hp4Ap1hAW8pTcfn7X5SBEpculzr/0VE1AGWRbuVmoiTuxN
+95ZupVHnfw6O5BZZu/VWL4FDx0qbAksOrznso2G+b3RH3NcnUz69yjjddw1xZIPn
+OJ6bDQKBgDUcWYu/2amU18D5vJpppUgRq2084WPUeXsaniTbmWfOC8NAn8CKLY0N
+V4yGSu98apDuqEVqL0VFQEgqK+5KTvRdXXYi36XYRbbVUgV13xveq2YTvjNbPM60
+QWG9YmgH7hVYGusuh5nQeS0qiIpwyws2H5mBVrGXrQ1Xb0MLWj8/
+-----END RSA PRIVATE KEY-----`
+
+// signed using the Root (rootCAPK)
+var clientPubKey = `-----BEGIN CERTIFICATE-----
+MIIDWDCCAkACCQCHoh4OvUySOzANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MCAX
+DTE4MDUxODA2NDQ0NVoYDzMwMTcwOTE4MDY0NDQ1WjBxMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEhMB8G
+A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv
+c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlkuYwF0qRXJWY3RWE
+NIUa+Aqnbh6Mc8K/7B+5/e+VT1zAp8dHe96OV5kBVFyJ2xx8DQmy5v8aWyvRfB5x
+qC2m9Mj4WnWKTqwgI7P5Vbf3nypoCLxONtQyNwMcl5ItyOvvbkIfm4i+FHNH51ph
+olCEBwvYwS3wz1YgXoCO0NkQf1Hqgzy3kWQ7RqYiaWeKlhc0EWCGbORnN8ZddFAv
+DYD67qR4tyMKuf2HYFqh8YiNB9hy7/d8fhwgVHjP+ygYpqZ6qcJTPt1aZt/i0Aj7
+xBHRVdcEqGIcOb3y09J4f33iELnZS6WuMRVwzlSjHmFrNLY8rTqhJzVi4Vk42p54
+aqPrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIQmlo8iCpyYggkbpfDmThBPHfy1
+cZcCi/tRFoFe1ccwn2ezLMIKmW38ZebiroawwqrZgU6AP+dMxVKLMjpyLPSrpFKa
+3o/LbVF7qMfH8/y2q8t7javd6rxoENH9uxLyHhauzI1iWy0whoDWBNiZrPBTBCjq
+jDGZARZqGyrPeXi+RNe1cMvZCxAFy7gqEtWFLWWrp0gYNPvxkHhhQBrUcF+8T/Nf
+9G4hKZSN/KAgC0CNBVuNrdyNc3l8H66BfwwL5X0+pesBYZM+MEfmBZOo+p7OWx2r
+ug8tR8eSL1vGleONtFRBUVG7NbtjhBf9FhvPZcSRR10od/vWHku9E01i4xg=
+-----END CERTIFICATE-----`
+
+func TestTLSConfig(t *testing.T) {
+
+	if err := ioutil.WriteFile("rootca.test.pem", []byte(rootCAPK), 0644); err != nil {
+		t.Fatal("couldn't create rootca.test.pem file.", err)
+		return
+	}
+	if err := ioutil.WriteFile("client.test.key", []byte(clientPrvKey), 0644); err != nil {
+		t.Fatal("couldn't create client.test.key file.", err)
+		return
+	}
+	if err := ioutil.WriteFile("client.test.pem", []byte(clientPubKey), 0644); err != nil {
+		t.Fatal("couldn't create client.test.pem file.", err)
+		return
+	}
+
+	s := server{}
+	s.setConfig(&ServerConfig{
+		TLS: ServerTLSConfig{
+			StartTLSOn:     true,
+			PrivateKeyFile: "client.test.key",
+			PublicKeyFile:  "client.test.pem",
+			RootCAs:        "rootca.test.pem",
+			ClientAuthType: "NoClientCert",
+			Curves:         []string{"P521", "P384"},
+			Ciphers:        []string{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"},
+			Protocols:      []string{"tls1.0", "tls1.2"},
+		},
+	})
+	s.configureSSL()
+
+	c := s.tlsConfigStore.Load().(*tls.Config)
+
+	if len(c.CurvePreferences) != 2 {
+		t.Error("c.CurvePreferences should have two elements")
+	} 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 {
+		t.Error("c.RootCAs not correctly set")
+	}
+	if c.ClientAuth != tls.NoClientCert {
+		t.Error("c.ClientAuth should be tls.NoClientCert")
+	}
+
+	if len(c.CipherSuites) != 2 {
+		t.Error("c.CipherSuites length should be 2")
+	}
+
+	if c.CipherSuites[0] != tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 && c.CipherSuites[1] != tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA {
+		t.Error("c.CipherSuites not correctly set ")
+	}
+
+	if c.MinVersion != tls.VersionTLS10 {
+		t.Error("c.MinVersion should be tls.VersionTLS10")
+	}
+
+	if c.MaxVersion != tls.VersionTLS12 {
+		t.Error("c.MinVersion should be tls.VersionTLS10")
+	}
+
+	if c.PreferServerCipherSuites != false {
+		t.Error("PreferServerCipherSuites should be false")
+	}
+
+	os.Remove("rootca.test.pem")
+	os.Remove("client.test.key")
+	os.Remove("client.test.pem")
+
+}
+
 func TestHandleClient(t *testing.T) {
 func TestHandleClient(t *testing.T) {
 	var mainlog log.Logger
 	var mainlog log.Logger
 	var logOpenError error
 	var logOpenError error

+ 1 - 1
tests/client.go

@@ -14,7 +14,7 @@ func Connect(serverConfig guerrilla.ServerConfig, deadline time.Duration) (net.C
 	var bufin *bufio.Reader
 	var bufin *bufio.Reader
 	var conn net.Conn
 	var conn net.Conn
 	var err error
 	var err error
-	if serverConfig.TLSAlwaysOn {
+	if serverConfig.TLS.AlwaysOn {
 		// start tls automatically
 		// start tls automatically
 		conn, err = tls.Dial("tcp", serverConfig.ListenInterface, &tls.Config{
 		conn, err = tls.Dial("tcp", serverConfig.ListenInterface, &tls.Config{
 			InsecureSkipVerify: true,
 			InsecureSkipVerify: true,

+ 17 - 14
tests/guerrilla_test.go

@@ -40,7 +40,6 @@ import (
 
 
 type TestConfig struct {
 type TestConfig struct {
 	guerrilla.AppConfig
 	guerrilla.AppConfig
-	BackendName   string                 `json:"backend_name"`
 	BackendConfig map[string]interface{} `json:"backend_config"`
 	BackendConfig map[string]interface{} `json:"backend_config"`
 }
 }
 
 
@@ -86,28 +85,32 @@ var configJson = `
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size": 100017,
             "max_size": 100017,
-            "private_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.key",
-            "public_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.crt",
             "timeout":160,
             "timeout":160,
-            "listen_interface":"127.0.0.1:2526",
-            "start_tls_on":true,
-            "tls_always_on":false,
+            "listen_interface":"127.0.0.1:2526", 
             "max_clients": 2,
             "max_clients": 2,
-            "log_file" : ""
+            "log_file" : "",
+			"tls" : {
+				"private_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.key",
+            	"public_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.crt",
+				"start_tls_on":true,
+            	"tls_always_on":false
+			}
         },
         },
 
 
         {
         {
             "is_enabled" : true,
             "is_enabled" : true,
             "host_name":"mail.guerrillamail.com",
             "host_name":"mail.guerrillamail.com",
             "max_size":1000001,
             "max_size":1000001,
-            "private_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.key",
-            "public_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.crt",
             "timeout":180,
             "timeout":180,
             "listen_interface":"127.0.0.1:4654",
             "listen_interface":"127.0.0.1:4654",
-            "start_tls_on":false,
-            "tls_always_on":true,
             "max_clients":1,
             "max_clients":1,
-            "log_file" : ""
+            "log_file" : "",
+			"tls" : {
+				"private_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.key",
+            	"public_key_file":"/vagrant/projects/htdocs/guerrilla/config/ssl/guerrillamail.com.crt",
+				"start_tls_on":false,
+            	"tls_always_on":true
+			}
         }
         }
     ]
     ]
 }
 }
@@ -125,8 +128,8 @@ func getBackend(backendConfig map[string]interface{}, l log.Logger) (backends.Ba
 func setupCerts(c *TestConfig) {
 func setupCerts(c *TestConfig) {
 	for i := range c.Servers {
 	for i := range c.Servers {
 		testcert.GenerateCert(c.Servers[i].Hostname, "", 365*24*time.Hour, false, 2048, "P256", "./")
 		testcert.GenerateCert(c.Servers[i].Hostname, "", 365*24*time.Hour, false, 2048, "P256", "./")
-		c.Servers[i].PrivateKeyFile = c.Servers[i].Hostname + ".key.pem"
-		c.Servers[i].PublicKeyFile = c.Servers[i].Hostname + ".cert.pem"
+		c.Servers[i].TLS.PrivateKeyFile = c.Servers[i].Hostname + ".key.pem"
+		c.Servers[i].TLS.PublicKeyFile = c.Servers[i].Hostname + ".cert.pem"
 	}
 	}
 }
 }
 
 

+ 17 - 0
tls_go1.8.go

@@ -0,0 +1,17 @@
+// +build go1.8
+
+package guerrilla
+
+import "crypto/tls"
+
+// add ciphers introduced since Go 1.8
+func init() {
+	TLSCiphers["TLS_RSA_WITH_AES_128_CBC_SHA256"] = tls.TLS_RSA_WITH_AES_128_CBC_SHA256
+	TLSCiphers["TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"] = tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
+	TLSCiphers["TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"] = tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
+	TLSCiphers["TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"] = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+	TLSCiphers["TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"] = tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
+	TLSCiphers["TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"] = tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
+
+	TLSCurves["X25519"] = tls.X25519
+}