package gemini import ( "context" "crypto/sha256" "crypto/tls" "encoding/base64" "errors" "fmt" "io" "net" "net/url" "reflect" "strings" "time" "github.com/a-h/gemini/log" ) // Handler of Gemini content. type Handler interface { ServeGemini(w ResponseWriter, r *Request) } // HandlerFunc handles a Gemini request and returns a response. type HandlerFunc func(ResponseWriter, *Request) // ServeGemini implements the Handler interface. func (f HandlerFunc) ServeGemini(w ResponseWriter, r *Request) { f(w, r) } // DefaultMIMEType for Gemini responses. const DefaultMIMEType = "text/gemini; charset=utf-8" // Request from the client. A Gemini request contains only the // URL, the Certificates field is populated by the TLS certificates // presented by the client. type Request struct { Context context.Context URL *url.URL Certificate Certificate } // Certificate information provided to the server by the client. type Certificate struct { // ID is the base64-encoded SHA256 hash of the key. ID string // Key is the user public key in PKIX, ASN.1 DER form. Key string // Error is an error message related to any failures in handling the client certificate. Error string } // ResponseWriter used by handlers to send a response to the client. type ResponseWriter interface { io.Writer SetHeader(code Code, meta string) error } // Code returned as part of the Gemini response (see https://gemini.circumlunar.space/docs/specification.html). type Code string const ( CodeInput Code = "10" CodeInputSensitive = "11" CodeSuccess = "20" CodeRedirect = "30" CodeRedirectTemporary = CodeRedirect CodeRedirectPermanent = "31" CodeTemporaryFailure = "40" CodeServerUnavailable = "41" CodeCGIError = "42" CodeProxyError = "43" CodeSlowDown = "44" CodePermanentFailure = "50" CodeNotFound = "51" CodeGone = "52" CodeProxyRequestRefused = "53" CodeBadRequest = "59" CodeClientCertificateRequired = "60" CodeClientCertificateNotAuthorised = "61" CodeClientCertificateNotValid = "62" ) // IsErrorCode returns true if the code is invalid, or starts with 4, 5 or 6. func IsErrorCode(code Code) bool { if !isValidCode(code) || len(code) != 2 { return false } return code[0] == '4' || code[0] == '5' || code[0] == '6' } // NewServer creates a new Gemini server. // addr is in the form ":", e.g. ":1965". If left empty, it will default to ":1965". // domainToHandler is a map of the server name (domain) to the certificate key pair and the Gemini handler used to serve content. func NewServer(ctx context.Context, addr string, domainToHandler map[string]*DomainHandler) *Server { for k, v := range domainToHandler { domainToHandler[strings.ToLower(k)] = v } return &Server{ Context: ctx, Addr: addr, DomainToHandler: domainToHandler, ReadTimeout: time.Second * 5, WriteTimeout: time.Second * 10, HandlerTimeout: time.Second * 30, } } // Server hosts Gemini content. type Server struct { Context context.Context Addr string Insecure bool DomainToHandler map[string]*DomainHandler ReadTimeout time.Duration WriteTimeout time.Duration HandlerTimeout time.Duration } // Set the server listening on the specified port. func (srv *Server) ListenAndServe() error { // Don't start if the server is already closing down. if srv.Context.Err() != nil { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":1965" } log.Info("gemini: starting", log.String("addr", addr)) ln, err := net.Listen("tcp", addr) if err != nil { return err } defer ln.Close() if srv.Insecure { err = srv.serveInsecure(ln) if err != nil { log.Error("gemini: serveInsecure failure", err, log.String("addr", addr)) } } else { err = srv.serveTLS(ln) if err != nil { log.Error("gemini: serveTLS failure", err, log.String("addr", addr)) } } log.Info("gemini: stopped") return err } // ErrServerClosed is returned when a server is attempted to start up when it's already shutting down. var ErrServerClosed = errors.New("gemini: server closed") func (srv *Server) serveInsecure(l net.Listener) (err error) { if len(srv.DomainToHandler) > 1 { return fmt.Errorf("gemini: cannot start insecure mode for more than one domain") } var handler *DomainHandler for _, handler = range srv.DomainToHandler { break } for { if err = srv.Context.Err(); err != nil { log.Error("gemini: context caused shutdown", err) return err } rw, err := l.Accept() if err != nil { log.Error("gemini: insecure listener error", err) continue } go func() { defer rw.Close() srv.handle(handler, Certificate{}, rw) }() } } func (srv *Server) serveTLS(l net.Listener) (err error) { config := &tls.Config{ MinVersion: tls.VersionTLS12, ClientAuth: tls.RequestClientCert, InsecureSkipVerify: true, GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { dh, ok := srv.DomainToHandler[strings.ToLower(hello.ServerName)] if !ok { return nil, fmt.Errorf("gemini: certificate not found for %q", hello.ServerName) } return &dh.KeyPair, nil }, } if err != nil { return err } tlsListener := tls.NewListener(l, config) for { if err = srv.Context.Err(); err != nil { log.Error("gemini: context caused shutdown", err) return err } conn, err := tlsListener.Accept() if err != nil { log.Error("gemini: tls listener error", err) continue } tlsConn, ok := conn.(*tls.Conn) if !ok { panic("gemini: tls.Listener did not return TLS connection") } go srv.handleTLS(tlsConn) } } func (srv *Server) handleTLS(conn *tls.Conn) { defer conn.Close() if err := conn.Handshake(); err != nil { log.Info("gemini: failed TLS handshake", log.String("remote", conn.RemoteAddr().String()), log.String("reason", err.Error())) return } var certificate Certificate peerCerts := conn.ConnectionState().PeerCertificates if len(peerCerts) > 0 { now := time.Now() cert := peerCerts[0] certificate.ID = base64.StdEncoding.EncodeToString(sha256.New().Sum(cert.Raw)) certificate.Key = string(cert.Raw) if now.Before(cert.NotBefore) { certificate.Error = "certificate not yet valid" } if now.After(cert.NotAfter) { certificate.Error = "certificate expired" } } serverName := conn.ConnectionState().ServerName dh, ok := srv.DomainToHandler[strings.ToLower(serverName)] if !ok { log.Warn("gemini: failed to find domain handler", log.String("serverName", serverName)) } srv.handle(dh, certificate, conn) } // while this function could be inlined, exposing it makes it easier to test in isolation. func (srv *Server) handle(dh *DomainHandler, certificate Certificate, conn net.Conn) { start := time.Now() conn.SetReadDeadline(time.Now().Add(srv.ReadTimeout)) r, ok, err := srv.parseRequest(conn) if err != nil { log.Info("gemini: failed to parse request", log.String("reason", err.Error())) return } if !ok { return } r.Certificate = certificate ctx, cancel := context.WithTimeout(srv.Context, srv.HandlerTimeout) defer cancel() r.Context = ctx conn.SetWriteDeadline(time.Now().Add(srv.WriteTimeout)) w := NewWriter(conn) defer func() { if p := recover(); p != nil { log.Error("gemini: server error", nil, log.String("url", r.URL.String()), log.Interface("recover", p)) w.SetHeader(CodeCGIError, "internal error") } }() if certificate.Error != "" { w.SetHeader(CodeClientCertificateNotValid, certificate.Error) return } dh.Handler.ServeGemini(w, r) if w.Code == "" { log.Error("gemini: handler resulted in empty response", nil, log.String("url", r.URL.String()), log.String("handlerType", reflect.TypeOf(dh.Handler).PkgPath())) w.SetHeader(CodeCGIError, "empty response") } duration := time.Now().Sub(start) log.Info("gemini: response", log.String("url", r.URL.String()), log.String("path", r.URL.Path), log.String("code", w.Code), log.String("handlerType", reflect.TypeOf(dh.Handler).PkgPath()), log.Int64("ms", duration.Milliseconds()), log.Int64("us", int64(duration.Microseconds())), log.Int64("lenBody", w.WrittenBody), log.Int("lenHeader", w.WrittenHeader), log.Int64("len", int64(w.WrittenHeader)+w.WrittenBody), ) } func (srv *Server) parseRequest(rw io.ReadWriter) (r *Request, ok bool, err error) { request, ok, err := readUntilCrLf(rw, 1026) if err != nil && err != io.EOF { writeHeaderToWriter(CodeBadRequest, fmt.Sprintf("error reading request: %v", err), rw) return } if !ok { log.Info("gemini: request too long or malformed", log.String("request", string(request))) writeHeaderToWriter(CodeBadRequest, "request too long or malformed", rw) return } ok = false url, err := url.Parse(strings.TrimSpace(string(request))) if err != nil { log.Info("gemini: malformed request", log.String("request", string(request))) writeHeaderToWriter(CodeBadRequest, "request malformed", rw) return } log.Info("gemini: received request", log.String("request", url.String())) r = &Request{ URL: url, } return r, true, err } // Writer passed to Gemini handlers. type Writer struct { Code string Writer io.Writer WrittenHeader int WrittenBody int64 } // NewWriter creates a new Gemini writer. func NewWriter(w io.Writer) *Writer { return &Writer{ Writer: w, } } var ErrCannotWriteBodyWithoutSuccessCode = errors.New("gemini: cannot write body without success code") func (gw *Writer) Write(p []byte) (n int, err error) { if gw.Code == "" { // Section 3.3 gw.SetHeader(CodeSuccess, DefaultMIMEType) gw.Code = CodeSuccess } if !isSuccessCode(Code(gw.Code)) { err = ErrCannotWriteBodyWithoutSuccessCode return } n, err = gw.Writer.Write(p) gw.WrittenBody += int64(n) return } func isSuccessCode(code Code) bool { return len(code) == 2 && code[0] == '2' } // ErrHeaderAlreadyWritten is returned by SetHeader when the Gemini header has already been written to the response. var ErrHeaderAlreadyWritten = errors.New("gemini: header already written") func (gw *Writer) SetHeader(code Code, meta string) (err error) { if gw.Code != "" { return ErrHeaderAlreadyWritten } gw.Code = string(code) var n int n, err = writeHeaderToWriter(code, meta, gw.Writer) gw.WrittenHeader += n return } func writeHeaderToWriter(code Code, meta string, w io.Writer) (n int, err error) { // // Set default meta if required. if meta == "" && isSuccessCode(code) { meta = DefaultMIMEType } if len(meta) > 1024 { meta = meta[:1024] } return w.Write([]byte(string(code) + " " + meta + "\r\n")) } // DomainHandler handles incoming requests for the ServerName using the provided KeyPair certificate // and Handler to process the request. type DomainHandler struct { ServerName string KeyPair tls.Certificate Handler Handler } // NewDomainHandler creates a new handler to listen for Gemini requests using TLS. // The cert can be generated by the github.com/a-h/gemini/cert.Generate package, // or can generated using openssl: // keyFile: // openssl ecparam -genkey -name secp384r1 -out server.key // certFile: // openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 func NewDomainHandler(serverName string, cert tls.Certificate, handler Handler) *DomainHandler { return &DomainHandler{ ServerName: serverName, KeyPair: cert, Handler: handler, } } // NewDomainHandlerFromFiles creates a new handler to listen for Gemini requests using TLS. // certFile / keyFile are links to the X509 keypair. This can be generated using openssl: // keyFile: // openssl ecparam -genkey -name secp384r1 -out server.key // certFile: // openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 func NewDomainHandlerFromFiles(serverName, certFile, keyFile string, handler Handler) (*DomainHandler, error) { keyPair, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, err } return NewDomainHandler(serverName, keyPair, handler), nil } // ListenAndServe starts up a new server to handle multiple domains with a specific certFile, keyFile and handler. func ListenAndServe(ctx context.Context, addr string, domains ...*DomainHandler) (err error) { if len(domains) == 0 { return fmt.Errorf("gemini: no default handler provided") } domainToHandler := make(map[string]*DomainHandler, len(domains)) for i := 0; i < len(domains); i++ { domainToHandler[domains[i].ServerName] = domains[i] } server := NewServer(ctx, addr, domainToHandler) return server.ListenAndServe() }