all repos — flounder @ d865a8dbc32bcff967a9cf8733d2eda2f36f4889

A small site builder for the Gemini protocol

gemini.go (view raw)

  1package main
  2
  3import (
  4	"bytes"
  5	"crypto/tls"
  6	"crypto/x509" // todo move into cert file
  7	"encoding/pem"
  8	// "fmt"
  9	"git.sr.ht/~adnano/gmi"
 10	"log"
 11	"os"
 12	// "path"
 13	"text/template"
 14	"time"
 15)
 16
 17func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) {
 18	t, err := template.ParseFiles("templates/index.gmi")
 19	if err != nil {
 20		log.Fatal(err)
 21	}
 22	files, _ := getIndexFiles()
 23	users, _ := getUsers()
 24	data := struct {
 25		Domain string
 26		Files  []*File
 27		Users  []string
 28	}{
 29		Domain: "flounder.online",
 30		Files:  files,
 31		Users:  users,
 32	}
 33	t.Execute(w, data)
 34}
 35
 36func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) {
 37}
 38
 39func runGeminiServer(config *Config) {
 40	var server gmi.Server
 41
 42	if err := server.CertificateStore.Load("./tmpcerts"); err != nil {
 43		log.Fatal(err)
 44	}
 45	server.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate {
 46		cert, err := store.Lookup(hostname)
 47		if err != nil {
 48			switch err {
 49			case gmi.ErrCertificateExpired:
 50				// Generate a new certificate if the current one is expired.
 51				log.Print("Old certificate expired, creating new one")
 52				fallthrough
 53			case gmi.ErrCertificateUnknown:
 54				// Generate a certificate if one does not exist.
 55				cert, err := gmi.NewCertificate(hostname, time.Minute)
 56				if err != nil {
 57					// Failed to generate new certificate, abort
 58					return nil
 59				}
 60				// Store and return the new certificate
 61				err = writeCertificate("./tmpcerts/"+hostname, cert)
 62				if err != nil {
 63					return nil
 64				}
 65				store.Add(hostname, cert)
 66				return &cert
 67			}
 68		}
 69		return cert
 70	}
 71
 72	// replace with wildcard cert
 73	server.HandleFunc("localhost", gmiIndex)
 74
 75	server.ListenAndServe()
 76}
 77
 78// writeCertificate writes the provided certificate and private key
 79// to path.crt and path.key respectively.
 80func writeCertificate(path string, cert tls.Certificate) error {
 81	crt, err := marshalX509Certificate(cert.Leaf.Raw)
 82	if err != nil {
 83		return err
 84	}
 85	key, err := marshalPrivateKey(cert.PrivateKey)
 86	if err != nil {
 87		return err
 88	}
 89
 90	// Write the certificate
 91	crtPath := path + ".crt"
 92	crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
 93	if err != nil {
 94		return err
 95	}
 96	if _, err := crtOut.Write(crt); err != nil {
 97		return err
 98	}
 99
100	// Write the private key
101	keyPath := path + ".key"
102	keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
103	if err != nil {
104		return err
105	}
106	if _, err := keyOut.Write(key); err != nil {
107		return err
108	}
109	return nil
110}
111
112// marshalX509Certificate returns a PEM-encoded version of the given raw certificate.
113func marshalX509Certificate(cert []byte) ([]byte, error) {
114	var b bytes.Buffer
115	if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
116		return nil, err
117	}
118	return b.Bytes(), nil
119}
120
121// marshalPrivateKey returns PEM encoded versions of the given certificate and private key.
122func marshalPrivateKey(priv interface{}) ([]byte, error) {
123	var b bytes.Buffer
124	privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
125	if err != nil {
126		return nil, err
127	}
128	if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
129		return nil, err
130	}
131	return b.Bytes(), nil
132}