From e5bd230fb0221fc2791ac9ab0efd03fbdb283410 Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@zerotier.com>
Date: Tue, 24 Sep 2019 16:44:29 -0700
Subject: [PATCH] .

---
 go/cmd/zerotier/cli/addroot.go    |  2 +-
 go/cmd/zerotier/cli/join.go       |  2 +-
 go/cmd/zerotier/cli/leave.go      |  2 +-
 go/cmd/zerotier/cli/networks.go   |  2 +-
 go/cmd/zerotier/cli/peers.go      |  2 +-
 go/cmd/zerotier/cli/removeroot.go |  2 +-
 go/cmd/zerotier/cli/roots.go      |  2 +-
 go/cmd/zerotier/cli/service.go    | 15 +++++-
 go/cmd/zerotier/cli/set.go        |  2 +-
 go/cmd/zerotier/cli/show.go       |  2 +-
 go/cmd/zerotier/cli/status.go     | 33 +++++++++++-
 go/cmd/zerotier/zerotier.go       | 84 +++++++++++++++++++++++--------
 go/pkg/zerotier/api.go            | 69 ++++++++++++++++++++++---
 go/pkg/zerotier/misc.go           |  3 ++
 go/pkg/zerotier/osdep-posix.go    | 17 +++++++
 15 files changed, 197 insertions(+), 42 deletions(-)

diff --git a/go/cmd/zerotier/cli/addroot.go b/go/cmd/zerotier/cli/addroot.go
index 2c9b74223..317c2693a 100644
--- a/go/cmd/zerotier/cli/addroot.go
+++ b/go/cmd/zerotier/cli/addroot.go
@@ -14,5 +14,5 @@
 package cli
 
 // AddRoot CLI command
-func AddRoot(args []string) {
+func AddRoot(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/join.go b/go/cmd/zerotier/cli/join.go
index dfa946894..211d2161c 100644
--- a/go/cmd/zerotier/cli/join.go
+++ b/go/cmd/zerotier/cli/join.go
@@ -14,5 +14,5 @@
 package cli
 
 // Join CLI command
-func Join(args []string) {
+func Join(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/leave.go b/go/cmd/zerotier/cli/leave.go
index cba0d19ff..c2dd46e30 100644
--- a/go/cmd/zerotier/cli/leave.go
+++ b/go/cmd/zerotier/cli/leave.go
@@ -14,5 +14,5 @@
 package cli
 
 // Leave CLI command
-func Leave(args []string) {
+func Leave(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/networks.go b/go/cmd/zerotier/cli/networks.go
index a7d80208f..e0b371890 100644
--- a/go/cmd/zerotier/cli/networks.go
+++ b/go/cmd/zerotier/cli/networks.go
@@ -14,5 +14,5 @@
 package cli
 
 // Networks CLI command
-func Networks(args []string) {
+func Networks(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/peers.go b/go/cmd/zerotier/cli/peers.go
index df21b543d..6770492e4 100644
--- a/go/cmd/zerotier/cli/peers.go
+++ b/go/cmd/zerotier/cli/peers.go
@@ -14,5 +14,5 @@
 package cli
 
 // Peers CLI command
-func Peers(args []string) {
+func Peers(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/removeroot.go b/go/cmd/zerotier/cli/removeroot.go
index 60bbe1076..69428c299 100644
--- a/go/cmd/zerotier/cli/removeroot.go
+++ b/go/cmd/zerotier/cli/removeroot.go
@@ -14,5 +14,5 @@
 package cli
 
 // RemoveRoot CLI command
-func RemoveRoot(args []string) {
+func RemoveRoot(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/roots.go b/go/cmd/zerotier/cli/roots.go
index 56eb54b22..c20b2fa0b 100644
--- a/go/cmd/zerotier/cli/roots.go
+++ b/go/cmd/zerotier/cli/roots.go
@@ -14,5 +14,5 @@
 package cli
 
 // Roots CLI command
-func Roots(args []string) {
+func Roots(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/service.go b/go/cmd/zerotier/cli/service.go
index ce1eefb1d..00412c373 100644
--- a/go/cmd/zerotier/cli/service.go
+++ b/go/cmd/zerotier/cli/service.go
@@ -13,6 +13,17 @@
 
 package cli
 
-// Service is "zerotier service ..."
-func Service(args []string) {
+/*
+func nodeStart() {
+	osSignalChannel := make(chan os.Signal, 2)
+	signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGBUS)
+	signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2)
+	go func() {
+		<-osSignalChannel
+	}()
+}
+*/
+
+// Service is "zerotier service ..."
+func Service(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/set.go b/go/cmd/zerotier/cli/set.go
index 14cf855eb..0388e1227 100644
--- a/go/cmd/zerotier/cli/set.go
+++ b/go/cmd/zerotier/cli/set.go
@@ -14,5 +14,5 @@
 package cli
 
 // Set CLI command
-func Set(args []string) {
+func Set(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/show.go b/go/cmd/zerotier/cli/show.go
index 865c13da2..d0cc32ba0 100644
--- a/go/cmd/zerotier/cli/show.go
+++ b/go/cmd/zerotier/cli/show.go
@@ -14,5 +14,5 @@
 package cli
 
 // Show CLI command
-func Show(args []string) {
+func Show(basePath, authToken string, args []string) {
 }
diff --git a/go/cmd/zerotier/cli/status.go b/go/cmd/zerotier/cli/status.go
index 61578200f..faf55028a 100644
--- a/go/cmd/zerotier/cli/status.go
+++ b/go/cmd/zerotier/cli/status.go
@@ -13,6 +13,37 @@
 
 package cli
 
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"os"
+	"zerotier/pkg/zerotier"
+)
+
 // Status shows service status info
-func Status(args []string) {
+func Status(basePath, authToken string, args []string, jsonOutput bool) {
+	var status zerotier.APIStatus
+	statusCode, err := zerotier.APIGet(basePath, zerotier.APISocketName, authToken, "/status", &status)
+	if err != nil {
+		fmt.Printf("FATAL: API response code %d: %s\n", statusCode, err.Error())
+		os.Exit(1)
+		return
+	}
+	if statusCode != http.StatusOK {
+		if statusCode == http.StatusUnauthorized {
+			fmt.Printf("FATAL: API response code %d: unauthorized (authorization token incorrect)\n", statusCode)
+		}
+		fmt.Printf("FATAL: API response code %d\n", statusCode)
+		os.Exit(1)
+		return
+	}
+
+	if jsonOutput {
+		j, _ := json.MarshalIndent(&status, "", "  ")
+		fmt.Println(string(j))
+	} else {
+	}
+
+	os.Exit(0)
 }
diff --git a/go/cmd/zerotier/zerotier.go b/go/cmd/zerotier/zerotier.go
index 39643f529..ee1bbe742 100644
--- a/go/cmd/zerotier/zerotier.go
+++ b/go/cmd/zerotier/zerotier.go
@@ -16,15 +16,18 @@ package main
 import (
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"os"
+	"path"
+	"runtime"
+	"strings"
 	"zerotier/cmd/zerotier/cli"
 	"zerotier/pkg/zerotier"
 )
 
 var copyrightText = fmt.Sprintf(`ZeroTier Network Virtualization Service Version %d.%d.%d
 (c)2019 ZeroTier, Inc.
-Licensed under the ZeroTier BSL (see LICENSE.txt)`,
-	zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
+Licensed under the ZeroTier BSL (see LICENSE.txt)`, zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
 
 func printHelp() {
 	fmt.Println(copyrightText + `
@@ -78,20 +81,41 @@ used to explicitly specify a location.
 `)
 }
 
-/*
-func nodeStart() {
-	osSignalChannel := make(chan os.Signal, 2)
-	signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGBUS)
-	signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2)
-	go func() {
-		<-osSignalChannel
-	}()
+func readAuthToken(basePath string) string {
+	data, _ := ioutil.ReadFile(path.Join(basePath, "authtoken.secret"))
+	if len(data) > 0 {
+		return string(data)
+	}
+	userHome, _ := os.UserHomeDir()
+	if len(userHome) > 0 {
+		if runtime.GOOS == "darwin" {
+			data, _ = ioutil.ReadFile(userHome + "/Library/Application Support/ZeroTier/authtoken.secret")
+			if len(data) > 0 {
+				return string(data)
+			}
+			data, _ = ioutil.ReadFile(userHome + "/Library/Application Support/ZeroTier/One/authtoken.secret")
+			if len(data) > 0 {
+				return string(data)
+			}
+		}
+		data, _ = ioutil.ReadFile(path.Join(userHome, ".zerotierauth"))
+		if len(data) > 0 {
+			return string(data)
+		}
+		data, _ = ioutil.ReadFile(path.Join(userHome, ".zeroTierOneAuthToken"))
+		if len(data) > 0 {
+			return string(data)
+		}
+	}
+	return ""
 }
-*/
 
 func main() {
 	globalOpts := flag.NewFlagSet("global", flag.ContinueOnError)
 	hflag := globalOpts.Bool("h", false, "") // support -h to be canonical with other Unix utilities
+	jflag := globalOpts.Bool("j", false, "")
+	pflag := globalOpts.String("p", "", "")
+	tflag := globalOpts.String("t", "", "")
 	err := globalOpts.Parse(os.Args[1:])
 	if err != nil {
 		printHelp()
@@ -109,6 +133,22 @@ func main() {
 		cmdArgs = args[1:]
 	}
 
+	basePath := zerotier.PlatformDefaultHomePath
+	if len(*pflag) > 0 {
+		basePath = *pflag
+	}
+	var authToken string
+	if len(*tflag) > 0 {
+		authToken = *tflag
+	} else {
+		authToken = readAuthToken(basePath)
+	}
+	if len(authToken) == 0 {
+		fmt.Println("FATAL: unable to read API authorization token from service path or user home ('sudo' may be needed)")
+		os.Exit(1)
+	}
+	authToken = strings.TrimSpace(authToken)
+
 	switch args[0] {
 	case "help":
 		printHelp()
@@ -117,27 +157,27 @@ func main() {
 		fmt.Printf("%d.%d.%d\n", zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
 		os.Exit(0)
 	case "service":
-		cli.Service(cmdArgs)
+		cli.Service(basePath, authToken, cmdArgs)
 	case "status":
-		cli.Status(cmdArgs)
+		cli.Status(basePath, authToken, cmdArgs, *jflag)
 	case "peers":
-		cli.Peers(cmdArgs)
+		cli.Peers(basePath, authToken, cmdArgs)
 	case "roots":
-		cli.Roots(cmdArgs)
+		cli.Roots(basePath, authToken, cmdArgs)
 	case "addroot":
-		cli.AddRoot(cmdArgs)
+		cli.AddRoot(basePath, authToken, cmdArgs)
 	case "removeroot":
-		cli.RemoveRoot(cmdArgs)
+		cli.RemoveRoot(basePath, authToken, cmdArgs)
 	case "networks":
-		cli.Networks(cmdArgs)
+		cli.Networks(basePath, authToken, cmdArgs)
 	case "join":
-		cli.Join(cmdArgs)
+		cli.Join(basePath, authToken, cmdArgs)
 	case "leave":
-		cli.Leave(cmdArgs)
+		cli.Leave(basePath, authToken, cmdArgs)
 	case "show":
-		cli.Show(cmdArgs)
+		cli.Show(basePath, authToken, cmdArgs)
 	case "set":
-		cli.Set(cmdArgs)
+		cli.Set(basePath, authToken, cmdArgs)
 	}
 
 	printHelp()
diff --git a/go/pkg/zerotier/api.go b/go/pkg/zerotier/api.go
index 342b7e238..be1d6b786 100644
--- a/go/pkg/zerotier/api.go
+++ b/go/pkg/zerotier/api.go
@@ -14,6 +14,7 @@
 package zerotier
 
 import (
+	"bytes"
 	secrand "crypto/rand"
 	"encoding/json"
 	"fmt"
@@ -27,7 +28,58 @@ import (
 	acl "github.com/hectane/go-acl"
 )
 
-type apiStatus struct {
+// APISocketName is the default socket name for accessing the API
+const APISocketName = "apisocket"
+
+// APIGet makes a query to the API via a Unix domain or windows pipe socket
+func APIGet(basePath, socketName, authToken, queryPath string, obj interface{}) (int, error) {
+	client, err := createNamedSocketHTTPClient(basePath, socketName)
+	if err != nil {
+		return http.StatusTeapot, err
+	}
+	req, err := http.NewRequest("GET", "http://socket"+queryPath, nil)
+	if err != nil {
+		return http.StatusTeapot, err
+	}
+	req.Header.Add("Authorization", "bearer "+authToken)
+	resp, err := client.Do(req)
+	if err != nil {
+		return http.StatusTeapot, err
+	}
+	err = json.NewDecoder(resp.Body).Decode(obj)
+	return resp.StatusCode, err
+}
+
+// APIPost posts a JSON object to the API via a Unix domain or windows pipe socket and reads a response
+func APIPost(basePath, socketName, authToken, queryPath string, post, result interface{}) (int, error) {
+	client, err := createNamedSocketHTTPClient(basePath, socketName)
+	if err != nil {
+		return http.StatusTeapot, err
+	}
+	var data []byte
+	if post != nil {
+		data, err = json.Marshal(post)
+		if err != nil {
+			return http.StatusTeapot, err
+		}
+	} else {
+		data = []byte("null")
+	}
+	req, err := http.NewRequest("POST", "http://socket"+queryPath, bytes.NewReader(data))
+	if err != nil {
+		return http.StatusTeapot, err
+	}
+	req.Header.Add("Authorization", "bearer "+authToken)
+	resp, err := client.Do(req)
+	if err != nil {
+		return http.StatusTeapot, err
+	}
+	err = json.NewDecoder(resp.Body).Decode(result)
+	return resp.StatusCode, err
+}
+
+// APIStatus is the object returned by API status inquiries
+type APIStatus struct {
 	Address                 Address
 	Clock                   int64
 	Config                  LocalConfig
@@ -42,7 +94,8 @@ type apiStatus struct {
 	VersionBuild            int
 }
 
-type apiNetwork struct {
+// APINetwork is the object returned by API network inquiries
+type APINetwork struct {
 	Config                 *NetworkConfig
 	Settings               *NetworkLocalSettings
 	MulticastSubscriptions []*MulticastGroup
@@ -89,12 +142,12 @@ func apiReadObj(out http.ResponseWriter, req *http.Request, dest interface{}) (e
 }
 
 func apiCheckAuth(out http.ResponseWriter, req *http.Request, token string) bool {
-	ah := req.Header.Get("X-ZT1-Auth")
-	if len(ah) > 0 && strings.TrimSpace(ah) == token {
+	ah := req.Header.Get("Authorization")
+	if len(ah) > 0 && strings.TrimSpace(ah) == ("bearer "+token) {
 		return true
 	}
-	ah = req.Header.Get("Authorization")
-	if len(ah) > 0 && strings.TrimSpace(ah) == ("bearer "+token) {
+	ah = req.Header.Get("X-ZT1-Auth")
+	if len(ah) > 0 && strings.TrimSpace(ah) == token {
 		return true
 	}
 	apiSendObj(out, req, http.StatusUnauthorized, nil)
@@ -134,7 +187,7 @@ func createAPIServer(basePath string, node *Node) (*http.Server, error) {
 		}
 		apiSetStandardHeaders(out)
 		if req.Method == http.MethodGet || req.Method == http.MethodHead {
-			apiSendObj(out, req, http.StatusOK, &apiStatus{
+			apiSendObj(out, req, http.StatusOK, &APIStatus{
 				Address:                 node.Address(),
 				Clock:                   TimeMs(),
 				Config:                  node.LocalConfig(),
@@ -268,7 +321,7 @@ func createAPIServer(basePath string, node *Node) (*http.Server, error) {
 		}
 	})
 
-	listener, err := createNamedSocketListener(basePath, "apisocket")
+	listener, err := createNamedSocketListener(basePath, APISocketName)
 	if err != nil {
 		return nil, err
 	}
diff --git a/go/pkg/zerotier/misc.go b/go/pkg/zerotier/misc.go
index 4a30c8ded..a0c841f38 100644
--- a/go/pkg/zerotier/misc.go
+++ b/go/pkg/zerotier/misc.go
@@ -21,6 +21,9 @@ import (
 	"unsafe"
 )
 
+// ZeroTierLogoChar is the unicode character that is ZeroTier's logo
+const ZeroTierLogoChar = "⏁"
+
 var base32StdLowerCase = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding)
 
 // TimeMs returns the time in milliseconds since epoch.
diff --git a/go/pkg/zerotier/osdep-posix.go b/go/pkg/zerotier/osdep-posix.go
index e10e32563..019ee51a0 100644
--- a/go/pkg/zerotier/osdep-posix.go
+++ b/go/pkg/zerotier/osdep-posix.go
@@ -16,9 +16,12 @@
 package zerotier
 
 import (
+	"context"
 	"net"
+	"net/http"
 	"os"
 	"path"
+	"time"
 )
 
 func createNamedSocketListener(basePath, name string) (net.Listener, error) {
@@ -26,3 +29,17 @@ func createNamedSocketListener(basePath, name string) (net.Listener, error) {
 	os.Remove(apiSockPath)
 	return net.Listen("unix", apiSockPath)
 }
+
+func createNamedSocketHTTPClient(basePath, name string) (*http.Client, error) {
+	apiSockPath := path.Join(basePath, name)
+	return &http.Client{
+		Timeout: 10 * time.Second,
+		Transport: &http.Transport{
+			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+				return net.Dial("unix", apiSockPath)
+			},
+			DisableKeepAlives:  true,
+			DisableCompression: true,
+		},
+	}, nil
+}