diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go new file mode 100644 index 000000000..1f7439c0c --- /dev/null +++ b/cmd/hello/hello.go @@ -0,0 +1,116 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The hello binary runs hello.ipn.dev. +package main // import "tailscale.com/cmd/hello" + +import ( + "encoding/json" + "flag" + "fmt" + "html/template" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + "strings" + + "tailscale.com/tailcfg" +) + +var ( + httpAddr = flag.String("http", ":80", "address to run an HTTP server on, or empty for none") + httpsAddr = flag.String("https", ":443", "address to run an HTTPS server on, or empty for none") +) + +func main() { + flag.Parse() + + http.HandleFunc("/", root) + log.Printf("Starting hello server.") + + errc := make(chan error, 1) + if *httpAddr != "" { + log.Printf("running HTTP server on %s", *httpAddr) + go func() { + errc <- http.ListenAndServe(*httpAddr, nil) + }() + } + if *httpsAddr != "" { + log.Printf("running HTTPS server on %s", *httpsAddr) + go func() { + errc <- http.ListenAndServeTLS(*httpsAddr, + "/etc/hello/hello.ipn.dev.crt", + "/etc/hello/hello.ipn.dev.key", + nil, + ) + }() + } + log.Fatal(<-errc) +} + +func slurpHTML() string { + slurp, err := ioutil.ReadFile("hello.tmpl.html") + if err != nil { + log.Fatal(err) + } + return string(slurp) +} + +var tmpl = template.Must(template.New("home").Parse(slurpHTML())) + +type tmplData struct { + DisplayName string // "Foo Barberson" + LoginName string // "foo@bar.com" + MachineName string // "imac5k" + IP string // "100.2.3.4" +} + +func root(w http.ResponseWriter, r *http.Request) { + if r.TLS == nil && *httpsAddr != "" { + host := r.Host + if strings.Contains(r.Host, "100.101.102.103") { + host = "hello.ipn.dev" + } + http.Redirect(w, r, "https://"+host, http.StatusFound) + return + } + if r.RequestURI != "/" { + http.Redirect(w, r, "/", http.StatusFound) + return + } + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + http.Error(w, "no remote addr", 500) + return + } + who, err := whoIs(ip) + if err != nil { + log.Printf("whois(%q) error: %v", ip, err) + http.Error(w, "Your Tailscale works, but we failed to look you up.", 500) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + tmpl.Execute(w, tmplData{ + DisplayName: who.UserProfile.DisplayName, + LoginName: who.UserProfile.LoginName, + MachineName: who.Node.ComputedName, + IP: ip, + }) +} + +func whoIs(ip string) (*tailcfg.WhoIsResponse, error) { + res, err := http.Get("http://127.0.0.1:4242/whois?ip=" + url.QueryEscape(ip)) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != 200 { + slurp, _ := ioutil.ReadAll(res.Body) + return nil, fmt.Errorf("HTTP %s: %s", res.Status, slurp) + } + r := new(tailcfg.WhoIsResponse) + return r, json.NewDecoder(res.Body).Decode(r) +} diff --git a/cmd/hello/hello.tmpl.html b/cmd/hello/hello.tmpl.html new file mode 100644 index 000000000..bf43b65ac --- /dev/null +++ b/cmd/hello/hello.tmpl.html @@ -0,0 +1,17 @@ + + + Hello from Tailscale + + +

Hello!

+

+ Hello {{.DisplayName}} ({{.LoginName}}) from {{.MachineName}} ({{.IP}}). +

+

+ Your Tailscale is working! +

+

+ Welcome to Tailscale. +

+ +