all repos — flounder @ 4b85797053e9433b25d6961b53092975a66c6a45

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	"strings"
  9	// "fmt"
 10	"git.sr.ht/~adnano/gmi"
 11	"io/ioutil"
 12	"log"
 13	"os"
 14	"path"
 15	"path/filepath"
 16	"text/template"
 17	"time"
 18)
 19
 20func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) {
 21	t, err := template.ParseFiles("templates/index.gmi")
 22	if err != nil {
 23		log.Fatal(err)
 24	}
 25	files, _ := getIndexFiles()
 26	users, _ := getUsers()
 27	data := struct {
 28		Domain    string
 29		SiteTitle string
 30		Files     []*File
 31		Users     []string
 32	}{
 33		Domain:    c.RootDomain,
 34		SiteTitle: c.SiteTitle,
 35		Files:     files,
 36		Users:     users,
 37	}
 38	t.Execute(w, data)
 39}
 40
 41func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) {
 42	userName := strings.Split(r.URL.Host, ".")[0]
 43	fileName := path.Join(c.FilesDirectory, userName, filepath.Clean(r.URL.Path))
 44	data, err := ioutil.ReadFile(fileName)
 45	// serve file?
 46	// TODO write mimetype
 47	if err != nil {
 48		// TODO return 404 equivalent
 49		log.Fatal(err)
 50	}
 51	_, err = w.Write(data)
 52	if err != nil {
 53		// return internal server error
 54		log.Fatal(err)
 55	}
 56}
 57
 58func runGeminiServer() {
 59	log.Println("Starting gemini server")
 60	var server gmi.Server
 61
 62	if err := server.CertificateStore.Load("./tmpcerts"); err != nil {
 63		log.Fatal(err)
 64	}
 65	server.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate {
 66		cert, err := store.Lookup(hostname)
 67		if err != nil {
 68			switch err {
 69			case gmi.ErrCertificateExpired:
 70				// Generate a new certificate if the current one is expired.
 71				log.Print("Old certificate expired, creating new one")
 72				fallthrough
 73			case gmi.ErrCertificateUnknown:
 74				// Generate a certificate if one does not exist.
 75				cert, err := gmi.NewCertificate(hostname, time.Minute)
 76				if err != nil {
 77					// Failed to generate new certificate, abort
 78					return nil
 79				}
 80				// Store and return the new certificate
 81				err = writeCertificate("./tmpcerts/"+hostname, cert)
 82				if err != nil {
 83					return nil
 84				}
 85				store.Add(hostname, cert)
 86				return &cert
 87			}
 88		}
 89		return cert
 90	}
 91
 92	// replace with wildcard cert
 93	server.HandleFunc(c.RootDomain, gmiIndex)
 94	server.HandleFunc("*."+c.RootDomain, gmiPage)
 95
 96	server.ListenAndServe()
 97}
 98
 99// TODO log request
100
101// writeCertificate writes the provided certificate and private key
102// to path.crt and path.key respectively.
103func writeCertificate(path string, cert tls.Certificate) error {
104	crt, err := marshalX509Certificate(cert.Leaf.Raw)
105	if err != nil {
106		return err
107	}
108	key, err := marshalPrivateKey(cert.PrivateKey)
109	if err != nil {
110		return err
111	}
112
113	// Write the certificate
114	crtPath := path + ".crt"
115	crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
116	if err != nil {
117		return err
118	}
119	if _, err := crtOut.Write(crt); err != nil {
120		return err
121	}
122
123	// Write the private key
124	keyPath := path + ".key"
125	keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
126	if err != nil {
127		return err
128	}
129	if _, err := keyOut.Write(key); err != nil {
130		return err
131	}
132	return nil
133}
134
135// marshalX509Certificate returns a PEM-encoded version of the given raw certificate.
136func marshalX509Certificate(cert []byte) ([]byte, error) {
137	var b bytes.Buffer
138	if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
139		return nil, err
140	}
141	return b.Bytes(), nil
142}
143
144// marshalPrivateKey returns PEM encoded versions of the given certificate and private key.
145func marshalPrivateKey(priv interface{}) ([]byte, error) {
146	var b bytes.Buffer
147	privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
148	if err != nil {
149		return nil, err
150	}
151	if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
152		return nil, err
153	}
154	return b.Bytes(), nil
155}