author | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-10-01 12:54:09 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-10-09 23:51:04 UTC |
parent | 04dd8b95347050d6a99d3eb52ef8aec6ff24b592 |
chasquid.go | +47 | -41 |
test/t-01-simple_local/msmtprc | +1 | -1 |
test/t-02-exim/msmtprc | +1 | -1 |
test/t-04-aliases/msmtprc | +1 | -1 |
test/t-05-null_address/msmtprc | +1 | -1 |
test/util/generate_cert.go | +4 | -4 |
test/util/lib.sh | +3 | -3 |
diff --git a/chasquid.go b/chasquid.go index 0121984..ddef298 100644 --- a/chasquid.go +++ b/chasquid.go @@ -77,18 +77,31 @@ func main() { s.aliasesR.SuffixSep = conf.SuffixSeparators s.aliasesR.DropChars = conf.DropCharacters - // Load domains. - // They live inside the config directory, so the relative path works. - domainDirs, err := ioutil.ReadDir("domains/") - if err != nil { - glog.Fatalf("Error reading domains/ directory: %v", err) - } - if len(domainDirs) == 0 { - glog.Fatalf("No domains found in config") + // Load certificates from "certs/<directory>/{fullchain,privkey}.pem". + // The structure matches letsencrypt's, to make it easier for that case. + glog.Infof("Loading certificates") + for _, info := range mustReadDir("certs/") { + name := info.Name() + glog.Infof(" %s", name) + + certPath := filepath.Join("certs/", name, "fullchain.pem") + if _, err := os.Stat(certPath); os.IsNotExist(err) { + continue + } + keyPath := filepath.Join("certs/", name, "privkey.pem") + if _, err := os.Stat(keyPath); os.IsNotExist(err) { + continue + } + + err := s.AddCerts(certPath, keyPath) + if err != nil { + glog.Fatalf(" %v", err) + } } + // Load domains from "domains/". glog.Infof("Domain config paths:") - for _, info := range domainDirs { + for _, info := range mustReadDir("domains/") { name := info.Name() dir := filepath.Join("domains", name) loadDomain(name, dir, s) @@ -147,7 +160,6 @@ func loadDomain(name, dir string, s *Server) { glog.Infof(" %s", name) s.AddDomain(name) s.aliasesR.AddDomain(name) - s.AddCerts(dir+"/cert.pem", dir+"/key.pem") if _, err := os.Stat(dir + "/users"); err == nil { glog.Infof(" adding users") @@ -185,6 +197,19 @@ func setupSignalHandling() { }() } +// Read a directory, which must have at least some entries. +func mustReadDir(path string) []os.FileInfo { + dirs, err := ioutil.ReadDir(path) + if err != nil { + glog.Fatalf("Error reading %q directory: %v", path, err) + } + if len(dirs) == 0 { + glog.Fatalf("No entries found in %q", path) + } + + return dirs +} + // Mode for a socket (listening or connection). // We keep them distinct, as policies can differ between them. type SocketMode string @@ -201,16 +226,13 @@ type Server struct { // Maximum data size. MaxDataSize int64 - // Certificate and key pairs. - certs, keys []string - // Addresses. addrs map[SocketMode][]string // Listeners (that came via systemd). listeners map[SocketMode][]net.Listener - // TLS config. + // TLS config (including loaded certificates). tlsConfig *tls.Config // Local domains. @@ -236,6 +258,7 @@ func NewServer() *Server { return &Server{ addrs: map[SocketMode][]string{}, listeners: map[SocketMode][]net.Listener{}, + tlsConfig: &tls.Config{}, connTimeout: 20 * time.Minute, commandTimeout: 1 * time.Minute, localDomains: &set.String{}, @@ -244,9 +267,13 @@ func NewServer() *Server { } } -func (s *Server) AddCerts(cert, key string) { - s.certs = append(s.certs, cert) - s.keys = append(s.keys, key) +func (s *Server) AddCerts(certPath, keyPath string) error { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return err + } + s.tlsConfig.Certificates = append(s.tlsConfig.Certificates, cert) + return nil } func (s *Server) AddAddr(a string, m SocketMode) { @@ -292,31 +319,10 @@ func (s *Server) periodicallyReload() { } } -func (s *Server) getTLSConfig() (*tls.Config, error) { - var err error - conf := &tls.Config{} - - conf.Certificates = make([]tls.Certificate, len(s.certs)) - for i := 0; i < len(s.certs); i++ { - conf.Certificates[i], err = tls.LoadX509KeyPair(s.certs[i], s.keys[i]) - if err != nil { - return nil, err - } - } - - conf.BuildNameToCertificate() - - return conf, nil -} - func (s *Server) ListenAndServe() { - var err error - - // Configure TLS. - s.tlsConfig, err = s.getTLSConfig() - if err != nil { - glog.Fatalf("Error loading TLS config: %v", err) - } + // At this point the TLS config should be done, build the + // name->certificate map (used by the TLS library for SNI). + s.tlsConfig.BuildNameToCertificate() for m, addrs := range s.addrs { for _, addr := range addrs { diff --git a/test/t-01-simple_local/msmtprc b/test/t-01-simple_local/msmtprc index 949e099..acb28d1 100644 --- a/test/t-01-simple_local/msmtprc +++ b/test/t-01-simple_local/msmtprc @@ -4,7 +4,7 @@ host testserver port 1587 tls on -tls_trust_file config/domains/testserver/cert.pem +tls_trust_file config/certs/testserver/fullchain.pem from user@testserver diff --git a/test/t-02-exim/msmtprc b/test/t-02-exim/msmtprc index f24e87c..09c75b2 100644 --- a/test/t-02-exim/msmtprc +++ b/test/t-02-exim/msmtprc @@ -4,7 +4,7 @@ host srv-chasquid port 1587 tls on -tls_trust_file config/domains/srv-chasquid/cert.pem +tls_trust_file config/certs/srv-chasquid/fullchain.pem from user@srv-chasquid diff --git a/test/t-04-aliases/msmtprc b/test/t-04-aliases/msmtprc index 1679764..8d191e1 100644 --- a/test/t-04-aliases/msmtprc +++ b/test/t-04-aliases/msmtprc @@ -4,7 +4,7 @@ host testserver port 1587 tls on -tls_trust_file config/domains/testserver/cert.pem +tls_trust_file config/certs/testserver/fullchain.pem from user@testserver diff --git a/test/t-05-null_address/msmtprc b/test/t-05-null_address/msmtprc index 91fab60..9322c92 100644 --- a/test/t-05-null_address/msmtprc +++ b/test/t-05-null_address/msmtprc @@ -4,7 +4,7 @@ host testserver port 1587 tls on -tls_trust_file config/domains/testserver/cert.pem +tls_trust_file config/certs/testserver/fullchain.pem from user@testserver diff --git a/test/util/generate_cert.go b/test/util/generate_cert.go index e488792..daaff1b 100644 --- a/test/util/generate_cert.go +++ b/test/util/generate_cert.go @@ -142,16 +142,16 @@ func main() { log.Fatalf("Failed to create certificate: %s", err) } - certOut, err := os.Create("cert.pem") + certOut, err := os.Create("fullchain.pem") if err != nil { - log.Fatalf("failed to open cert.pem for writing: %s", err) + log.Fatalf("failed to open fullchain.pem for writing: %s", err) } pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close() - keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + keyOut, err := os.OpenFile("privkey.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { - log.Fatalf("failed to open key.pem for writing:", err) + log.Fatalf("failed to open privkey.pem for writing:", err) return } pem.Encode(keyOut, pemBlockForKey(priv)) diff --git a/test/util/lib.sh b/test/util/lib.sh index 40b16cb..8dedbf2 100644 --- a/test/util/lib.sh +++ b/test/util/lib.sh @@ -80,11 +80,11 @@ function wait_for_file() { done } -# Generate certs for the given domain. +# Generate certs for the given hostname. function generate_certs_for() { - mkdir -p config/domains/${1} + mkdir -p config/certs/${1}/ ( - cd config/domains/${1} + cd config/certs/${1} generate_cert -ca -duration=1h -host=${1} ) }