2018-08-21 19:26:50 +05:30
|
|
|
// 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{
|
2019-03-27 16:45:23 +05:30
|
|
|
m: m,
|
|
|
|
conf: m.TLSConfig(),
|
2018-08-21 19:26:50 +05:30
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|