1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package autocert
6
7import (
8	"crypto/tls"
9	"log"
10	"net"
11	"os"
12	"path/filepath"
13	"runtime"
14	"time"
15)
16
17// NewListener returns a net.Listener that listens on the standard TLS
18// port (443) on all interfaces and returns *tls.Conn connections with
19// LetsEncrypt certificates for the provided domain or domains.
20//
21// It enables one-line HTTPS servers:
22//
23//     log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
24//
25// NewListener is a convenience function for a common configuration.
26// More complex or custom configurations can use the autocert.Manager
27// type instead.
28//
29// Use of this function implies acceptance of the LetsEncrypt Terms of
30// Service. If domains is not empty, the provided domains are passed
31// to HostWhitelist. If domains is empty, the listener will do
32// LetsEncrypt challenges for any requested domain, which is not
33// recommended.
34//
35// Certificates are cached in a "golang-autocert" directory under an
36// operating system-specific cache or temp directory. This may not
37// be suitable for servers spanning multiple machines.
38//
39// The returned listener uses a *tls.Config that enables HTTP/2, and
40// should only be used with servers that support HTTP/2.
41//
42// The returned Listener also enables TCP keep-alives on the accepted
43// connections. The returned *tls.Conn are returned before their TLS
44// handshake has completed.
45func NewListener(domains ...string) net.Listener {
46	m := &Manager{
47		Prompt: AcceptTOS,
48	}
49	if len(domains) > 0 {
50		m.HostPolicy = HostWhitelist(domains...)
51	}
52	dir := cacheDir()
53	if err := os.MkdirAll(dir, 0700); err != nil {
54		log.Printf("warning: autocert.NewListener not using a cache: %v", err)
55	} else {
56		m.Cache = DirCache(dir)
57	}
58	return m.Listener()
59}
60
61// Listener listens on the standard TLS port (443) on all interfaces
62// and returns a net.Listener returning *tls.Conn connections.
63//
64// The returned listener uses a *tls.Config that enables HTTP/2, and
65// should only be used with servers that support HTTP/2.
66//
67// The returned Listener also enables TCP keep-alives on the accepted
68// connections. The returned *tls.Conn are returned before their TLS
69// handshake has completed.
70//
71// Unlike NewListener, it is the caller's responsibility to initialize
72// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
73func (m *Manager) Listener() net.Listener {
74	ln := &listener{
75		conf: m.TLSConfig(),
76	}
77	ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
78	return ln
79}
80
81type listener struct {
82	conf *tls.Config
83
84	tcpListener  net.Listener
85	tcpListenErr error
86}
87
88func (ln *listener) Accept() (net.Conn, error) {
89	if ln.tcpListenErr != nil {
90		return nil, ln.tcpListenErr
91	}
92	conn, err := ln.tcpListener.Accept()
93	if err != nil {
94		return nil, err
95	}
96	tcpConn := conn.(*net.TCPConn)
97
98	// Because Listener is a convenience function, help out with
99	// this too.  This is not possible for the caller to set once
100	// we return a *tcp.Conn wrapping an inaccessible net.Conn.
101	// If callers don't want this, they can do things the manual
102	// way and tweak as needed. But this is what net/http does
103	// itself, so copy that. If net/http changes, we can change
104	// here too.
105	tcpConn.SetKeepAlive(true)
106	tcpConn.SetKeepAlivePeriod(3 * time.Minute)
107
108	return tls.Server(tcpConn, ln.conf), nil
109}
110
111func (ln *listener) Addr() net.Addr {
112	if ln.tcpListener != nil {
113		return ln.tcpListener.Addr()
114	}
115	// net.Listen failed. Return something non-nil in case callers
116	// call Addr before Accept:
117	return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
118}
119
120func (ln *listener) Close() error {
121	if ln.tcpListenErr != nil {
122		return ln.tcpListenErr
123	}
124	return ln.tcpListener.Close()
125}
126
127func homeDir() string {
128	if runtime.GOOS == "windows" {
129		return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
130	}
131	if h := os.Getenv("HOME"); h != "" {
132		return h
133	}
134	return "/"
135}
136
137func cacheDir() string {
138	const base = "golang-autocert"
139	switch runtime.GOOS {
140	case "darwin":
141		return filepath.Join(homeDir(), "Library", "Caches", base)
142	case "windows":
143		for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
144			if v := os.Getenv(ev); v != "" {
145				return filepath.Join(v, base)
146			}
147		}
148		// Worst case:
149		return filepath.Join(homeDir(), base)
150	}
151	if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
152		return filepath.Join(xdg, base)
153	}
154	return filepath.Join(homeDir(), ".cache", base)
155}
156