// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package autocert import ( "crypto/tls" "log" "net" "os" "path/filepath" "runtime" "time" ) // NewListener returns a net.Listener that listens on the standard TLS // port (443) on all interfaces and returns *tls.Conn connections with // LetsEncrypt certificates for the provided domain or domains. // // It enables one-line HTTPS servers: // // log.Fatal(http.Serve(autocert.NewListener("example.com"), handler)) // // NewListener is a convenience function for a common configuration. // More complex or custom configurations can use the autocert.Manager // type instead. // // Use of this function implies acceptance of the LetsEncrypt Terms of // Service. If domains is not empty, the provided domains are passed // to HostWhitelist. If domains is empty, the listener will do // LetsEncrypt challenges for any requested domain, which is not // recommended. // // Certificates are cached in a "golang-autocert" directory under an // operating system-specific cache or temp directory. This may not // be suitable for servers spanning multiple machines. // // The returned listener uses a *tls.Config that enables HTTP/2, and // should only be used with servers that support HTTP/2. // // The returned Listener also enables TCP keep-alives on the accepted // connections. The returned *tls.Conn are returned before their TLS // handshake has completed. func NewListener(domains ...string) net.Listener { m := &Manager{ Prompt: AcceptTOS, } if len(domains) > 0 { m.HostPolicy = HostWhitelist(domains...) } dir := cacheDir() if err := os.MkdirAll(dir, 0700); err != nil { log.Printf("warning: autocert.NewListener not using a cache: %v", err) } else { m.Cache = DirCache(dir) } return m.Listener() } // Listener listens on the standard TLS port (443) on all interfaces // and returns a net.Listener returning *tls.Conn connections. // // The returned listener uses a *tls.Config that enables HTTP/2, and // should only be used with servers that support HTTP/2. // // The returned Listener also enables TCP keep-alives on the accepted // connections. The returned *tls.Conn are returned before their TLS // handshake has completed. // // Unlike NewListener, it is the caller's responsibility to initialize // the Manager m's Prompt, Cache, HostPolicy, and other desired options. func (m *Manager) Listener() net.Listener { ln := &listener{ m: m, conf: m.TLSConfig(), } ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443") return ln } type listener struct { m *Manager conf *tls.Config tcpListener net.Listener tcpListenErr error } func (ln *listener) Accept() (net.Conn, error) { if ln.tcpListenErr != nil { return nil, ln.tcpListenErr } conn, err := ln.tcpListener.Accept() if err != nil { return nil, err } tcpConn := conn.(*net.TCPConn) // Because Listener is a convenience function, help out with // this too. This is not possible for the caller to set once // we return a *tcp.Conn wrapping an inaccessible net.Conn. // If callers don't want this, they can do things the manual // way and tweak as needed. But this is what net/http does // itself, so copy that. If net/http changes, we can change // here too. tcpConn.SetKeepAlive(true) tcpConn.SetKeepAlivePeriod(3 * time.Minute) return tls.Server(tcpConn, ln.conf), nil } func (ln *listener) Addr() net.Addr { if ln.tcpListener != nil { return ln.tcpListener.Addr() } // net.Listen failed. Return something non-nil in case callers // call Addr before Accept: return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443} } func (ln *listener) Close() error { if ln.tcpListenErr != nil { return ln.tcpListenErr } return ln.tcpListener.Close() } func homeDir() string { if runtime.GOOS == "windows" { return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") } if h := os.Getenv("HOME"); h != "" { return h } return "/" } func cacheDir() string { const base = "golang-autocert" switch runtime.GOOS { case "darwin": return filepath.Join(homeDir(), "Library", "Caches", base) case "windows": for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} { if v := os.Getenv(ev); v != "" { return filepath.Join(v, base) } } // Worst case: return filepath.Join(homeDir(), base) } if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" { return filepath.Join(xdg, base) } return filepath.Join(homeDir(), ".cache", base) }