dir-server

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

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 }