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