main.go (5334B)
1 /*dir-server - simple http or https server for current directory 2 3 Copyright (C) 2019-2020 Brian C. Lane <bcl@brianlane.com> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License along 16 with this program; if not, write to the Free Software Foundation, Inc., 17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 package main 20 21 import ( 22 "crypto/ecdsa" 23 "crypto/elliptic" 24 "crypto/rand" 25 "crypto/x509" 26 "crypto/x509/pkix" 27 "encoding/pem" 28 "flag" 29 "fmt" 30 "log" 31 "math/big" 32 "net/http" 33 "os" 34 "time" 35 ) 36 37 type cmdlineArgs struct { 38 ListenIP string 39 ListenPort int 40 TLS bool 41 Cert string 42 Key string 43 SinglePage bool 44 Path string 45 } 46 47 var cfg = cmdlineArgs{ 48 ListenIP: "0.0.0.0", 49 ListenPort: 8000, 50 TLS: false, 51 Cert: "/var/tmp/cert.pem", 52 Key: "/var/tmp/key.pem", 53 SinglePage: false, 54 Path: "./", 55 } 56 57 func init() { 58 flag.StringVar(&cfg.ListenIP, "ip", cfg.ListenIP, "IP Address to Listen to") 59 flag.IntVar(&cfg.ListenPort, "port", cfg.ListenPort, "Port to listen to") 60 flag.BoolVar(&cfg.TLS, "tls", cfg.TLS, "Use https instead of http") 61 flag.StringVar(&cfg.Cert, "cert", cfg.Cert, "Path to temporary cert.pem") 62 flag.StringVar(&cfg.Key, "key", cfg.Key, "Path to temporary key.pem") 63 flag.BoolVar(&cfg.SinglePage, "single", cfg.SinglePage, "Single page app, direct all unknown routes to index.html") 64 flag.StringVar(&cfg.Path, "path", cfg.Path, "Path to serve files from") 65 66 flag.Parse() 67 } 68 69 func publicKey(priv interface{}) interface{} { 70 switch k := priv.(type) { 71 case *ecdsa.PrivateKey: 72 return &k.PublicKey 73 default: 74 return nil 75 } 76 } 77 78 // This generates a self-signed cert good for 6 hours using ecdsa P256 curve 79 func makeSelfSigned(cert, key string) { 80 hostname, err := os.Hostname() 81 if err != nil { 82 log.Fatalf("Error getting hostname: %s", err) 83 } 84 85 // these are temporary certs, only valid for 6 hours 86 notBefore := time.Now() 87 notAfter := notBefore.Add(6 * time.Hour) 88 89 var priv interface{} 90 priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 91 if err != nil { 92 log.Fatalf("Failed to generate private key: %s", err) 93 } 94 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 95 if err != nil { 96 log.Fatalf("Failed to generate serial number: %s", err) 97 } 98 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 99 100 template := x509.Certificate{ 101 SerialNumber: serialNumber, 102 Subject: pkix.Name{ 103 Organization: []string{"Random Bits UNL"}, 104 }, 105 NotBefore: notBefore, 106 NotAfter: notAfter, 107 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 108 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 109 BasicConstraintsValid: true, 110 DNSNames: []string{hostname}, 111 } 112 derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) 113 if err != nil { 114 log.Fatalf("Failed to create certificate: %s", err) 115 } 116 117 certOut, err := os.Create(cert) 118 if err != nil { 119 log.Fatalf("Failed to open %s for writing: %s", cert, err) 120 } 121 if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 122 log.Fatalf("Failed to write data to cert.pem: %s", err) 123 } 124 if err := certOut.Close(); err != nil { 125 log.Fatalf("Error closing %s: %s", cert, err) 126 } 127 log.Printf("wrote %s\n", cert) 128 129 keyOut, err := os.OpenFile(key, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 130 if err != nil { 131 log.Fatalf("Failed to open %s for writing: %s", key, err) 132 } 133 privBytes, err := x509.MarshalPKCS8PrivateKey(priv) 134 if err != nil { 135 log.Fatalf("Unable to marshal private key: %v", err) 136 } 137 if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { 138 log.Fatalf("Failed to write data to %s: %s", key, err) 139 } 140 if err := keyOut.Close(); err != nil { 141 log.Fatalf("Error closing %s: %s", key, err) 142 } 143 log.Printf("wrote %s\n", key) 144 } 145 146 type singlePageFile struct { 147 http.File 148 } 149 150 // singlePageFileServer is an http.FileSystem that returns index.html for all non-existant paths 151 type singlePageFileServer struct { 152 http.FileSystem 153 } 154 155 func (fs singlePageFileServer) Open(name string) (http.File, error) { 156 file, err := fs.FileSystem.Open(name) 157 if err != nil { 158 file, err = fs.FileSystem.Open("/index.html") 159 if err != nil { 160 return nil, err 161 } 162 } 163 return singlePageFile{file}, nil 164 } 165 166 func main() { 167 if !cfg.SinglePage { 168 http.Handle("/", http.FileServer(http.Dir(cfg.Path))) 169 } else { 170 fs := singlePageFileServer{http.Dir(cfg.Path)} 171 http.Handle("/", http.FileServer(fs)) 172 } 173 listen := fmt.Sprintf("%s:%d", cfg.ListenIP, cfg.ListenPort) 174 175 if !cfg.TLS { 176 if err := http.ListenAndServe(listen, nil); err != nil { 177 panic(err) 178 } 179 } else { 180 makeSelfSigned(cfg.Cert, cfg.Key) 181 if err := http.ListenAndServeTLS(listen, cfg.Cert, cfg.Key, nil); err != nil { 182 panic(err) 183 } 184 185 } 186 }