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}