aboutsummaryrefslogtreecommitdiff
path: root/server.go
diff options
context:
space:
mode:
authorAdnan Maolood <[email protected]>2020-10-28 14:59:45 -0400
committerAdnan Maolood <[email protected]>2020-10-28 15:03:54 -0400
commit7f0b1fa8a14d495edf6522fe9cb26cf14b69cb9b (patch)
tree99a2342cc3e29ffc0edbb8cdadc0c511075b70f3 /server.go
parentFix examples/cert.go (diff)
downloadgo-gemini-7f0b1fa8a14d495edf6522fe9cb26cf14b69cb9b.tar.xz
go-gemini-7f0b1fa8a14d495edf6522fe9cb26cf14b69cb9b.zip
Refactor server certificates
Diffstat (limited to 'server.go')
-rw-r--r--server.go250
1 files changed, 28 insertions, 222 deletions
diff --git a/server.go b/server.go
index 65e62b5..f991f08 100644
--- a/server.go
+++ b/server.go
@@ -7,11 +7,8 @@ import (
"log"
"net"
"net/url"
- "path"
- "sort"
"strconv"
"strings"
- "sync"
"time"
)
@@ -21,13 +18,12 @@ type Server struct {
// If Addr is empty, the server will listen on the address ":1965".
Addr string
- // CertificateStore contains the certificates used by the server.
- CertificateStore CertificateStore
+ // Certificates contains the certificates used by the server.
+ Certificates CertificateStore
- // GetCertificate, if not nil, will be called to retrieve the certificate
- // to use for a given hostname.
- // If the certificate is nil, the connection will be aborted.
- GetCertificate func(hostname string, store *CertificateStore) *tls.Certificate
+ // CreateCertificate, if not nil, will be called to create a new certificate
+ // if the current one is expired or missing.
+ CreateCertificate func(hostname string) (tls.Certificate, error)
// registered responders
responders map[responderKey]Responder
@@ -69,6 +65,9 @@ func (s *Server) Register(pattern string, responder Responder) {
key.wildcard = true
}
+ if _, ok := s.responders[key]; ok {
+ panic("gemini: multiple registrations for " + pattern)
+ }
s.responders[key] = responder
}
@@ -90,18 +89,11 @@ func (s *Server) ListenAndServe() error {
}
defer ln.Close()
- config := &tls.Config{
- ClientAuth: tls.RequestClientCert,
- MinVersion: tls.VersionTLS12,
- GetCertificate: func(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
- if s.GetCertificate != nil {
- return s.GetCertificate(h.ServerName, &s.CertificateStore), nil
- }
- return s.CertificateStore.Lookup(h.ServerName)
- },
- }
- tlsListener := tls.NewListener(ln, config)
- return s.Serve(tlsListener)
+ return s.Serve(tls.NewListener(ln, &tls.Config{
+ ClientAuth: tls.RequestClientCert,
+ MinVersion: tls.VersionTLS12,
+ GetCertificate: s.getCertificate,
+ }))
}
// Serve listens for requests on the provided listener.
@@ -135,6 +127,21 @@ func (s *Server) Serve(l net.Listener) error {
}
}
+func (s *Server) getCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ cert, err := s.Certificates.Lookup(h.ServerName)
+ switch err {
+ case ErrCertificateExpired, ErrCertificateUnknown:
+ if s.CreateCertificate != nil {
+ cert, err := s.CreateCertificate(h.ServerName)
+ if err == nil {
+ s.Certificates.Add(h.ServerName, cert)
+ }
+ return &cert, err
+ }
+ }
+ return cert, err
+}
+
// respond responds to a connection.
func (s *Server) respond(conn net.Conn) {
r := bufio.NewReader(conn)
@@ -317,204 +324,3 @@ type ResponderFunc func(*ResponseWriter, *Request)
func (f ResponderFunc) Respond(w *ResponseWriter, r *Request) {
f(w, r)
}
-
-// The following code is modified from the net/http package.
-
-// Copyright 2009 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.
-
-// ServeMux is a Gemini request multiplexer.
-// It matches the URL of each incoming request against a list of registered
-// patterns and calls the handler for the pattern that
-// most closely matches the URL.
-//
-// Patterns name fixed, rooted paths, like "/favicon.ico",
-// or rooted subtrees, like "/images/" (note the trailing slash).
-// Longer patterns take precedence over shorter ones, so that
-// if there are handlers registered for both "/images/"
-// and "/images/thumbnails/", the latter handler will be
-// called for paths beginning "/images/thumbnails/" and the
-// former will receive requests for any other paths in the
-// "/images/" subtree.
-//
-// Note that since a pattern ending in a slash names a rooted subtree,
-// the pattern "/" matches all paths not matched by other registered
-// patterns, not just the URL with Path == "/".
-//
-// If a subtree has been registered and a request is received naming the
-// subtree root without its trailing slash, ServeMux redirects that
-// request to the subtree root (adding the trailing slash). This behavior can
-// be overridden with a separate registration for the path without
-// the trailing slash. For example, registering "/images/" causes ServeMux
-// to redirect a request for "/images" to "/images/", unless "/images" has
-// been registered separately.
-//
-// ServeMux also takes care of sanitizing the URL request path and
-// redirecting any request containing . or .. elements or repeated slashes
-// to an equivalent, cleaner URL.
-type ServeMux struct {
- mu sync.RWMutex
- m map[string]muxEntry
- es []muxEntry // slice of entries sorted from longest to shortest.
-}
-
-type muxEntry struct {
- r Responder
- pattern string
-}
-
-// cleanPath returns the canonical path for p, eliminating . and .. elements.
-func cleanPath(p string) string {
- if p == "" {
- return "/"
- }
- if p[0] != '/' {
- p = "/" + p
- }
- np := path.Clean(p)
- // path.Clean removes trailing slash except for root;
- // put the trailing slash back if necessary.
- if p[len(p)-1] == '/' && np != "/" {
- // Fast path for common case of p being the string we want:
- if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
- np = p
- } else {
- np += "/"
- }
- }
- return np
-}
-
-// Find a handler on a handler map given a path string.
-// Most-specific (longest) pattern wins.
-func (mux *ServeMux) match(path string) Responder {
- // Check for exact match first.
- v, ok := mux.m[path]
- if ok {
- return v.r
- }
-
- // Check for longest valid match. mux.es contains all patterns
- // that end in / sorted from longest to shortest.
- for _, e := range mux.es {
- if strings.HasPrefix(path, e.pattern) {
- return e.r
- }
- }
- return nil
-}
-
-// redirectToPathSlash determines if the given path needs appending "/" to it.
-// This occurs when a handler for path + "/" was already registered, but
-// not for path itself. If the path needs appending to, it creates a new
-// URL, setting the path to u.Path + "/" and returning true to indicate so.
-func (mux *ServeMux) redirectToPathSlash(path string, u *url.URL) (*url.URL, bool) {
- mux.mu.RLock()
- shouldRedirect := mux.shouldRedirectRLocked(path)
- mux.mu.RUnlock()
- if !shouldRedirect {
- return u, false
- }
- path = path + "/"
- u = &url.URL{Path: path, RawQuery: u.RawQuery}
- return u, true
-}
-
-// shouldRedirectRLocked reports whether the given path and host should be redirected to
-// path+"/". This should happen if a handler is registered for path+"/" but
-// not path -- see comments at ServeMux.
-func (mux *ServeMux) shouldRedirectRLocked(path string) bool {
- if _, exist := mux.m[path]; exist {
- return false
- }
-
- n := len(path)
- if n == 0 {
- return false
- }
- if _, exist := mux.m[path+"/"]; exist {
- return path[n-1] != '/'
- }
-
- return false
-}
-
-// Respond dispatches the request to the responder whose
-// pattern most closely matches the request URL.
-func (mux *ServeMux) Respond(w *ResponseWriter, r *Request) {
- path := cleanPath(r.URL.Path)
-
- // If the given path is /tree and its handler is not registered,
- // redirect for /tree/.
- if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
- Redirect(w, u.String())
- return
- }
-
- if path != r.URL.Path {
- u := *r.URL
- u.Path = path
- Redirect(w, u.String())
- return
- }
-
- mux.mu.RLock()
- defer mux.mu.RUnlock()
-
- resp := mux.match(path)
- if resp == nil {
- w.WriteStatus(StatusNotFound)
- return
- }
- resp.Respond(w, r)
-}
-
-// Handle registers the responder for the given pattern.
-// If a responder already exists for pattern, Handle panics.
-func (mux *ServeMux) Handle(pattern string, responder Responder) {
- mux.mu.Lock()
- defer mux.mu.Unlock()
-
- if pattern == "" {
- panic("gemini: invalid pattern")
- }
- if responder == nil {
- panic("gemini: nil responder")
- }
- if _, exist := mux.m[pattern]; exist {
- panic("gemini: multiple registrations for " + pattern)
- }
-
- if mux.m == nil {
- mux.m = make(map[string]muxEntry)
- }
- e := muxEntry{responder, pattern}
- mux.m[pattern] = e
- if pattern[len(pattern)-1] == '/' {
- mux.es = appendSorted(mux.es, e)
- }
-}
-
-func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
- n := len(es)
- i := sort.Search(n, func(i int) bool {
- return len(es[i].pattern) < len(e.pattern)
- })
- if i == n {
- return append(es, e)
- }
- // we now know that i points at where we want to insert
- es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
- copy(es[i+1:], es[i:]) // move shorter entries down
- es[i] = e
- return es
-}
-
-// HandleFunc registers the responder function for the given pattern.
-func (mux *ServeMux) HandleFunc(pattern string, responder func(*ResponseWriter, *Request)) {
- if responder == nil {
- panic("gemini: nil responder")
- }
- mux.Handle(pattern, ResponderFunc(responder))
-}