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 +}