all repos — flounder @ 3d84831d6976655373cf07b2c74052df69d2b7cb

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