1 package main 2 3 import ( 4 "context" 5 "crypto/tls" 6 "errors" 7 "flag" 8 "fmt" 9 "html/template" 10 "net/http" 11 "net/smtp" 12 "os" 13 "os/signal" 14 "strings" 15 16 "golang.org/x/crypto/acme/autocert" 17 "vimagination.zapto.org/form" 18 "vimagination.zapto.org/httpgzip" 19 "vimagination.zapto.org/reverseproxy/unixconn" 20 ) 21 22 type http2https struct { 23 http.Handler 24 } 25 26 func (hh http2https) ServeHTTP(w http.ResponseWriter, r *http.Request) { 27 if r.TLS == nil { 28 url := "https://" + r.Host + r.URL.Path 29 if len(r.URL.RawQuery) != 0 { 30 url += "?" + r.URL.RawQuery 31 } 32 http.Redirect(w, r, url, http.StatusMovedPermanently) 33 return 34 } 35 hh.Handler.ServeHTTP(w, r) 36 } 37 38 type paths []http.FileSystem 39 40 func (p *paths) String() string { 41 return "" 42 } 43 44 func (p *paths) Set(path string) error { 45 *p = append(*p, http.Dir(path)) 46 return nil 47 } 48 49 type serverNames []string 50 51 func (s *serverNames) String() string { 52 return "" 53 } 54 55 func (s *serverNames) Set(serverName string) error { 56 *s = append(*s, serverName) 57 return nil 58 } 59 60 type contact struct { 61 Template *template.Template 62 From, To string 63 Host string 64 Auth smtp.Auth 65 } 66 67 type values struct { 68 Name string `form:"name,post"` 69 Email string `form:"email,required,post"` 70 Phone string `form:"phone,post"` 71 Subject string `form:"subject,post"` 72 Message string `form:"message,post"` 73 Errors form.ErrorMap 74 Done bool 75 } 76 77 func (c *contact) ServeHTTP(w http.ResponseWriter, r *http.Request) { 78 w.Header().Set("Content-Type", "text/html") 79 var v values 80 if r.Method == http.MethodPost { 81 r.ParseForm() 82 if r.Form.Get("submit") != "" { 83 err := form.Process(r, &v) 84 if err == nil { 85 go smtp.SendMail(c.Host, c.Auth, c.From, []string{c.To}, []byte(fmt.Sprintf("To: %s\r\nFrom: %s\r\nSubject: Message Received\r\n\r\nName: %s\nEmail: %s\nPhone: %s\nSubject: %s\nMessage: %s", c.To, c.From, v.Name, v.Email, v.Phone, v.Subject, v.Message))) 86 v.Done = true 87 } else { 88 v.Errors = err.(form.ErrorMap) 89 } 90 } 91 } 92 c.Template.Execute(w, &v) 93 } 94 95 func main() { 96 if err := run(); err != nil { 97 fmt.Fprintf(os.Stderr, "error: %s", err) 98 } 99 } 100 101 func run() error { 102 var ( 103 contactTmpl string 104 paths paths 105 serverNames serverNames 106 ) 107 flag.StringVar(&contactTmpl, "c", "", "contact form template") 108 flag.Var(&serverNames, "s", "server name(s) for TLS") 109 flag.Var(&paths, "p", "server path") 110 flag.Parse() 111 if len(paths) == 0 { 112 return errors.New("") 113 } 114 server := &http.Server{ 115 Handler: http.DefaultServeMux, 116 } 117 if contactTmpl != "" { 118 from := os.Getenv("contactFrom") 119 os.Unsetenv("contactFrom") 120 to := os.Getenv("contactTo") 121 os.Unsetenv("contactTo") 122 addr := os.Getenv("contactAddr") 123 os.Unsetenv("contactAddr") 124 username := os.Getenv("contactUsername") 125 os.Unsetenv("contactUsername") 126 password := os.Getenv("contactPassword") 127 os.Unsetenv("contactPassword") 128 p := strings.IndexByte(addr, ':') 129 addrNoPort := addr 130 if p > 0 { 131 addrNoPort = addrNoPort[:p] 132 } 133 http.Handle("/contact.html", &contact{ 134 Template: template.Must(template.ParseFiles(contactTmpl)), 135 From: from, 136 To: to, 137 Host: addr, 138 Auth: smtp.PlainAuth("", username, password, addrNoPort), 139 }) 140 } 141 http.Handle("/", httpgzip.FileServer(paths[0], paths[1:]...)) 142 143 l, err := unixconn.Listen("tcp", ":80") 144 if err != nil { 145 return errors.New("unable to open port 80") 146 } 147 if len(serverNames) > 0 { 148 l, err := unixconn.Listen("tcp", ":443") 149 if err != nil { 150 return errors.New("unable to open port 443") 151 } 152 leManager := &autocert.Manager{ 153 Prompt: autocert.AcceptTOS, 154 Cache: autocert.DirCache("./certcache/"), 155 HostPolicy: autocert.HostWhitelist(serverNames...), 156 } 157 server.Handler = leManager.HTTPHandler(http2https{server.Handler}) 158 server.TLSConfig = &tls.Config{ 159 GetCertificate: leManager.GetCertificate, 160 NextProtos: []string{"h2", "http/1.1"}, 161 } 162 go server.ServeTLS(l, "", "") 163 } 164 go func() { 165 if err := server.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) { 166 fmt.Fprintln(os.Stderr, err) 167 } 168 }() 169 170 sc := make(chan os.Signal, 1) 171 signal.Notify(sc, os.Interrupt) 172 <-sc 173 signal.Stop(sc) 174 close(sc) 175 return server.Shutdown(context.Background()) 176 } 177