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