dir-server

Simple http(s) server
git clone https://www.brianlane.com/git/dir-server
Log | Files | Refs | README

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 }