From 4a7c76a11bb799442e5f57954fd75d57a197b3d9 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 20 Jan 2017 10:51:55 -0800 Subject: docs, cleanup, temporarily put cli in attic since it is not done. --- README.md | 73 ++-- attic/cli/README.md | 57 +++ attic/cli/zerotier.cpp | 957 +++++++++++++++++++++++++++++++++++++++++++++++++ cli/README.md | 57 --- cli/zerotier.cpp | 957 ------------------------------------------------- 5 files changed, 1060 insertions(+), 1041 deletions(-) create mode 100644 attic/cli/README.md create mode 100644 attic/cli/zerotier.cpp delete mode 100644 cli/README.md delete mode 100644 cli/zerotier.cpp diff --git a/README.md b/README.md index 5c6ff381..5be0c0ce 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,7 @@ ZeroTier - A Planetary Ethernet Switch ====== -ZeroTier is a software-based managed Ethernet switch for planet Earth. - -It erases the LAN/WAN distinction and makes VPNs, tunnels, proxies, and other kludges arising from the inflexible nature of physical networks obsolete. Everything is encrypted end-to-end and traffic takes the most direct (peer to peer) path available. - -This repository contains ZeroTier One, a service that provides ZeroTier network connectivity to devices running Windows, Mac, Linux, iOS, Android, and FreeBSD and makes joining virtual networks as easy as joining IRC or Slack channels. It also contains the OS-independent core ZeroTier protocol implementation in [node/](node/). +ZeroTier is an advanced SDN Ethernet switch for planet Earth. It erases the LAN/WAN distinction and makes VPNs, tunnels, proxies, and other kludges arising from the inflexible nature of physical networks obsolete. Everything is encrypted end-to-end and traffic takes the most direct (peer to peer) path available. Visit [ZeroTier's site](https://www.zerotier.com/) for more information and [pre-built binary packages](https://www.zerotier.com/download.shtml). Apps for Android and iOS are available for free in the Google Play and Apple app stores. @@ -13,36 +9,59 @@ Visit [ZeroTier's site](https://www.zerotier.com/) for more information and [pre ZeroTier's basic operation is easy to understand. Devices have 10-digit *ZeroTier addresses* like `89e92ceee5` and networks have 16-digit network IDs like `8056c2e21c000001`. All it takes for a device to join a network is its 16-digit ID, and all it takes for a network to authorize a device is its 10-digit address. Everything else is automatic. -A "device" can be anything really: desktops, laptops, phones, servers, VMs/VPSes, containers, and even (soon) apps. +A "device" in our terminology is any "unit of compute" capable of talking to a network: desktops, laptops, phones, servers, VMs/VPSes, containers, and even user-space applications via our [SDK](https://github.com/zerotier/ZeroTierSDK). -For testing we provide a public virtual network called *Earth* with network ID `8056c2e21c000001`. On Linux and Mac you can do this with: +For testing purposes we provide a public virtual network called *Earth* with network ID `8056c2e21c000001`. You can join it with: sudo zerotier-cli join 8056c2e21c000001 -Now wait about 30 seconds and check your system with `ip addr list` or `ifconfig`. You'll see a new interface whose name starts with *zt* and it should quickly get an IPv4 and an IPv6 address. Once you see it get an IP, try pinging `earth.zerotier.net` at `29.209.112.93`. If you've joined Earth from more than one system, try pinging your other machine. - -*(IPv4 addresses for Earth are assigned from the block 28.0.0.0/7, which is not a part of the public Internet but is non-standard for private networks. It's used to avoid IP conflicts during testing. Your networks can run any IP addressing scheme you want.)* - -If you don't want to belong to a giant Ethernet party line anymore, just type: +Now wait about 30 seconds and check your system with `ip addr list` or `ifconfig`. You'll see a new interface whose name starts with *zt* and it should quickly get an IPv4 and an IPv6 address. Once you see it get an IP, try pinging `earth.zerotier.net` at `29.209.112.93`. If you've joined Earth from more than one system, try pinging your other machine. If you don't want to belong to a giant Ethernet party line anymore, just type: sudo zerotier-cli leave 8056c2e21c000001 The *zt* interface will disappear. You're no longer on the network. -To create networks of your own you'll need a network controller. You can use [our hosted controller at my.zerotier.com](https://my.zerotier.com) which is free for up to 100 devices on an unlimited number of networks, or you can build your own controller and run it through its local JSON API. See [README.md in controller/](controller/) for more information. - -### Building from Source - -For Mac, Linux, and BSD, just type `make` (or `gmake` on BSD). Platform requirements are: - - - **Mac**: Xcode command line tools for OSX 10.7 or newer. - - **Linux**: GCC/G++ 4.9 or newer or CLANG 3.4 or newer, and GNU make and standard shell tools of course. - - The Linux make file will auto-detect and prefer CLANG if present since in our experience it does a better job optimizing C++ code. You can specify CC= and CXX= on the make command line to override this behavior. - - Several distributions including CentOS 7 ship with G++ 4.8 which has broken C++11 support. If you are running CentOS 7 type `sudo yum install clang` to install CLANG 3.4, which will work fine. There is no C++11 in the ZeroTier core but we have started using it in the service and network controller code. - - If any of the following have development headers on the system they are auto-detected and the build will link against system versions: `libnatpmp`, and `miniupnpc`. If these are missing (or too old) the versions in `ext/` are used and are statically linked into the binary. Please be aware of this since if you build with these present the resulting binary will require them on other systems too. - - **FreeBSD**: GCC/G++ 4.9 or newer and `gmake` since our make files use GNU extensions. - -Each supported platform has its own *make-XXX.mk* file that contains the actual make rules for the platform. The right .mk file is included by the main Makefile based on the GNU make *OSTYPE* variable. Take a look at the .mk file for your platform for other targets, debug build rules, etc. +To create networks of your own, you'll need a network controller. ZeroTier One (for desktops and servers) includes controller functionality in its default build that can be configured via its JSON API (see [README.md in controller/](controller/)). ZeroTier provides a hosted solution with a nice web UI and SaaS add-ons at [my.zerotier.com](https://my.zerotier.com/). Basic controller functionality is free for up to 100 devices. + +### Project Layout + + - `artwork/`: icons, logos, etc. + - `attic/`: old stuff and experimental code that we want to keep around for reference. + - `controller/`: the reference network controller implementation, which is built and included by default on desktop and server build targets. + - `debian/`: files for building Debian packages on Linux. + - `doc/`: manual pages and other documentation. + - `ext/`: third party libraries, binaries that we ship for convenience on some platforms (Mac and Windows), and installation support files. + - `include/`: include files for the ZeroTier core. + - `java/`: a JNI wrapper used with our Android mobile app. (The whole Android app is not open source but may be made so in the future.) + - `macui/`: a Macintosh menu-bar app for controlling ZeroTier One, written in Objective C. + - `node/`: the ZeroTier virtual Ethernet switch core, which is designed to be entirely separate from the rest of the code and able to be built as a stand-alone OS-independent library. Note to developers: do not use C++11 features in here, since we want this to build on old embedded platforms that lack C++11 support. C++11 can be used elsewhere. + - `osdep/`: code to support and integrate with OSes, including platform-specific stuff only built for certain targets. + - `service/`: the ZeroTier One service, which wraps the ZeroTier core and provides VPN-like connectivity to virtual networks for desktops, laptops, servers, VMs, and containers. + - `tcp-proxy/`: TCP proxy code run by ZeroTier, Inc. to provide TCP fallback (this will die soon!). + - `windows/`: Visual Studio solution files, Windows service code for ZeroTier One, and the Windows task bar app UI. + - `world/`: ZeroTier "world" definition and source for building and signing it -- normally only of use to ZeroTier, Inc. A "world" is one virtual switch. We've defined one for planet Earth and surrounding space and operate a network of free anchor points (root servers) for it. + +The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc. + +### Build and Platform Notes + +To build on Mac and Linux just type `make`. On FreeBSD and OpenBSD `gmake` (GNU make) is required and can be installed from packages or ports. For Windows there is a Visual Studio solution in `windows/'. + + - **Mac** + - Xcode command line tools for OSX 10.7 or newer are required. + - Tap device driver kext source is in `ext/tap-mac` and a signed pre-built binary can be found in `ext/bin/tap-mac`. You should not need to build it yourself. It's a fork of [tuntaposx](http://tuntaposx.sourceforge.net) with device names changed to `zt#`, support for a larger MTU, and tun functionality removed. + - **Linux** + - The minimum compiler versions required are GCC/G++ 4.9.3 or CLANG/CLANG++ 3.4.2. + - Linux makefiles automatically detect and prefer clang/clang++ if present as it produces smaller and slightly faster binaries in most cases. You can override by supplying CC and CXX variables on the make command line. + - CentOS 7 ships with a version of GCC/G++ that is too old, but a new enough version of CLANG can be found in the *epel* repositories. Type `yum install epel-release` and then `yum install clang` to build there. + - **FreeBSD** + - Tested most recently on FreeBSD-11. Older versions may work but we're not sure. + - GCC/G++ 4.9 and gmake are required. These can be installed from packages or ports. Type `gmake` to build. + - **OpenBSD** + - There is a limit of four network memberships on OpenBSD as there are only four tap devices (`/dev/tap0` through `/dev/tap3`). We're not sure if this can be increased. + - OpenBSD lacks `getifmaddrs` (or any equivalent method) to get interface multicast memberships. As a result multicast will only work on OpenBSD for ARP and NDP (IP/MAC lookup) and not for other purposes. + - Only tested on OpenBSD 6.0. Older versions may not work. + - GCC/G++ 4.9 and gmake are required and can be installed using `pkg_add` or from ports. They get installed in `/usr/local/bin` as `egcc` and `eg++` and our makefile is pre-configured to use them on OpenBSD. Typing `make selftest` will build a *zerotier-selftest* binary which unit tests various internals and reports on a few aspects of the build environment. It's a good idea to try this on novel platforms or architectures. @@ -65,7 +84,7 @@ The service is controlled via the JSON API, which by default is available at 127 Here's where home folders live (by default) on each OS: * **Linux**: `/var/lib/zerotier-one` - * **FreeBSD**: `/var/db/zerotier-one` + * **FreeBSD** / **OpenBSD**: `/var/db/zerotier-one` * **Mac**: `/Library/Application Support/ZeroTier/One` * **Windows**: `\ProgramData\ZeroTier\One` (That's for Windows 7. The base 'shared app data' folder might be different on different Windows versions.) diff --git a/attic/cli/README.md b/attic/cli/README.md new file mode 100644 index 00000000..595df07e --- /dev/null +++ b/attic/cli/README.md @@ -0,0 +1,57 @@ +The new ZeroTier CLI! +==== + +With this update we've expanded upon the previous CLI's functionality, so things should seem pretty familiar. Here are some of the new features we've introduced: + + - Create and administer networks on ZeroTier Central directly from the console. + - Service configurations, allows you to control local/remote instances of ZeroTier One + - Identity generation and management is now part of the same CLI tool + +*** +## Configurations + +Configurations are a way for you to nickname and logically organize the control of ZeroTier services running locally or remotely (this includes ZeroTier Central!). They're merely groupings of service API url's and auth tokens. The CLI's settings data is contained within `.zerotierCliSettings`. + +For instance, you can control your local instance of ZeroTier One via the `@local` config. By default it is represented as follows: + +``` +"local": { + "auth": "7tyqRoFytajf21j2l2t9QPm5", + "type": "one", + "url": "http://127.0.0.1:9993/" +} +``` + +As an example, if you issue the command `zerotier ls` is it implicitly stating `zerotier @local ls`. + +With the same line of thinking, you could create a `@my.zerotier.com` which would allow for something like `zerotier @my.zerotier.com net-create` which talks to our hosted ZeroTier Central to create a new network. + + + +## Command families + +- `cli-` is for configuring the settings data for the CLI itself, such as adding/removing `@thing` configurations, variables, etc. +- `net-` is for operating on a *ZeroTier Central* service such as `https://my.zerotier.com` +- `id-` is for handling ZeroTier identities. + +And those commands with no prefix are there to allow you to operate ZeroTier One instances either local or remote. + +*** +## Useful command examples + +*Add a ZeroTier One configuration:* + + - `zerotier cli-add-zt MyLocalConfigName https://127.0.0.1:9993/ ` + +*Add a ZeroTier Central configuration:* + + - `zerotier cli-add-central MyZTCentralConfigName https://my.zerotier.com/ ` + +*Set a default ZeroTier One instance:* + + - `zerotier cli-set defaultOne MyLocalConfigName` + +*Set a default ZeroTier Central:* + + - `zerotier cli-set defaultCentral MyZTCentralConfigName` + diff --git a/attic/cli/zerotier.cpp b/attic/cli/zerotier.cpp new file mode 100644 index 00000000..e75268d1 --- /dev/null +++ b/attic/cli/zerotier.cpp @@ -0,0 +1,957 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// Note: unlike the rest of ZT's code base, this requires C++11 due to +// the JSON library it uses and other things. + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Identity.hpp" +#include "../version.h" +#include "../osdep/OSUtils.hpp" +#include "../ext/offbase/json/json.hpp" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +using json = nlohmann::json; +using namespace ZeroTier; + +#define ZT_CLI_FLAG_VERBOSE 'v' +#define ZT_CLI_FLAG_UNSAFE_SSL 'X' + +#define REQ_GET 0 +#define REQ_POST 1 +#define REQ_DEL 2 + +#define OK_STR "[OK ]: " +#define FAIL_STR "[FAIL]: " +#define WARN_STR "[WARN]: " +#define INVALID_ARGS_STR "Invalid args. Usage: " + +struct CLIState +{ + std::string atname; + std::string command; + std::string url; + std::map reqHeaders; + std::vector args; + std::map opts; + json settings; +}; + +namespace { + +static Identity getIdFromArg(char *arg) +{ + Identity id; + if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line + if (id.fromString(arg)) + return id; + } else { // identity is to be read from a file + std::string idser; + if (OSUtils::readFile(arg,idser)) { + if (id.fromString(idser)) + return id; + } + } + return Identity(); +} + +static std::string trimString(const std::string &s) +{ + unsigned long end = (unsigned long)s.length(); + while (end) { + char c = s[end - 1]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + --end; + else break; + } + unsigned long start = 0; + while (start < end) { + char c = s[start]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + ++start; + else break; + } + return s.substr(start,end - start); +} + +static inline std::string getSettingsFilePath() +{ +#ifdef __WINDOWS__ +#else + const char *home = getenv("HOME"); + if (!home) + home = "/"; + return (std::string(home) + "/.zerotierCliSettings"); +#endif +} + +static bool saveSettingsBackup(CLIState &state) +{ + std::string sfp(getSettingsFilePath().c_str()); + if(state.settings.find("generateBackupConfig") != state.settings.end() + && state.settings["generateBackupConfig"].get() == "true") { + std::string backup_file = getSettingsFilePath() + ".bak"; + if(!OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { + OSUtils::lockDownFile(sfp.c_str(),false); + std::cout << WARN_STR << "unable to write backup config file" << std::endl; + return false; + } + return true; + } + return false; +} + +static bool saveSettings(CLIState &state) +{ + std::string sfp(getSettingsFilePath().c_str()); + if(OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { + OSUtils::lockDownFile(sfp.c_str(),false); + std::cout << OK_STR << "changes saved." << std::endl; + return true; + } + std::cout << FAIL_STR << "unable to write to " << sfp << std::endl; + return false; +} + +static void dumpHelp() +{ + std::cout << "ZeroTier Newer-Spiffier CLI " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << std::endl; + std::cout << "(c)2016 ZeroTier, Inc. / Licensed under the GNU GPL v3" << std::endl; + std::cout << std::endl; + std::cout << "Configuration path: " << getSettingsFilePath() << std::endl; + std::cout << std::endl; + std::cout << "Usage: zerotier [-option] [@name] []" << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " -verbose - Verbose JSON output" << std::endl; + std::cout << " -X - Do not check SSL certs (CAUTION!)" << std::endl; + std::cout << std::endl; + std::cout << "CLI Configuration Commands:" << std::endl; + std::cout << " cli-set - Set a CLI option ('cli-set help')" << std::endl; + std::cout << " cli-unset - Un-sets a CLI option ('cli-unset help')" << std::endl; + std::cout << " cli-ls - List configured @things" << std::endl; + std::cout << " cli-rm @name - Remove a configured @thing" << std::endl; + std::cout << " cli-add-zt @name - Add a ZeroTier service" << std::endl; + std::cout << " cli-add-central @name - Add ZeroTier Central instance" << std::endl; + std::cout << std::endl; + std::cout << "ZeroTier One Service Commands:" << std::endl; + std::cout << " -v / -version - Displays default local instance's version'" << std::endl; + std::cout << " ls - List currently joined networks" << std::endl; + std::cout << " join [opt=value ...] - Join a network" << std::endl; + std::cout << " leave - Leave a network" << std::endl; + std::cout << " peers - List ZeroTier VL1 peers" << std::endl; + std::cout << " show [] - Get info about self or object" << std::endl; + std::cout << std::endl; + std::cout << "Network Controller Commands:" << std::endl; + std::cout << " net-create - Create a new network" << std::endl; + std::cout << " net-rm - Delete a network (CAUTION!)" << std::endl; + std::cout << " net-ls - List administered networks" << std::endl; + std::cout << " net-members - List members of a network" << std::endl; + std::cout << " net-show [
] - Get network or member info" << std::endl; + std::cout << " net-auth
- Authorize a member" << std::endl; + std::cout << " net-unauth
- De-authorize a member" << std::endl; + std::cout << " net-set - See 'net-set help'" << std::endl; + std::cout << std::endl; + std::cout << "Identity Commands:" << std::endl; + std::cout << " id-generate [] - Generate a ZeroTier identity" << std::endl; + std::cout << " id-validate - Locally validate an identity" << std::endl; + std::cout << " id-sign - Sign a file" << std::endl; + std::cout << " id-verify - Verify a file's signature" << std::endl; + std::cout << " id-getpublic - Get full identity's public portion" << std::endl; + std::cout << std::endl; +} + +static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,void *stdstring) +{ + size_t totalSize = size * nmemb; + reinterpret_cast(stdstring)->append((const char *)contents,totalSize); + return totalSize; +} + +static std::tuple REQUEST(int requestType, CLIState &state, const std::map &headers, const std::string &postfield, const std::string &url) +{ + std::string body; + char errbuf[CURL_ERROR_SIZE]; + char urlbuf[4096]; + + CURL *curl; + curl = curl_easy_init(); + if (!curl) { + std::cerr << "FATAL: curl_easy_init() failed" << std::endl; + exit(-1); + } + + Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str()); + curl_easy_setopt(curl,CURLOPT_URL,urlbuf); + + struct curl_slist *hdrs = (struct curl_slist *)0; + for(std::map::const_iterator i(headers.begin());i!=headers.end();++i) { + std::string htmp(i->first); + htmp.append(": "); + htmp.append(i->second); + hdrs = curl_slist_append(hdrs,htmp.c_str()); + } + if (hdrs) + curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs); + + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body); + curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback); + + if(std::find(state.args.begin(), state.args.end(), "-X") == state.args.end()) + curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L); + + if(requestType == REQ_POST) { + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfield.c_str()); + } + if(requestType == REQ_DEL) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + if(requestType == REQ_GET) { + curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf); + curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L); + } + + curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI"); + CURLcode res = curl_easy_perform(curl); + + errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check + + if (res != CURLE_OK) + return std::make_tuple(-1,std::string(errbuf)); + + long response_code; + int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &response_code); + + if(response_code == 401) { std::cout << FAIL_STR << response_code << "Unauthorized." << std::endl; exit(0); } + else if(response_code == 403) { std::cout << FAIL_STR << response_code << "Forbidden." << std::endl; exit(0); } + else if(response_code == 404) { std::cout << FAIL_STR << response_code << "Not found." << std::endl; exit(0); } + else if(response_code == 408) { std::cout << FAIL_STR << response_code << "Request timed out." << std::endl; exit(0); } + else if(response_code != 200) { std::cout << FAIL_STR << response_code << "Unable to process request." << std::endl; exit(0); } + + curl_easy_cleanup(curl); + if (hdrs) + curl_slist_free_all(hdrs); + return std::make_tuple(response_code,body); +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////////////// + +// Check for user-specified @thing config +// Make sure it @thing makes sense +// Apply appropriate request headers +static void checkForThing(CLIState &state, std::string thingType, bool warnNoThingProvided) +{ + std::string configName; + if(state.atname.length()) { + configName = state.atname.erase(0,1); + // make sure specified @thing makes sense in the context of the command + if(thingType == "one" && state.settings["things"][configName]["type"].get() != "one") { + std::cout << FAIL_STR << "A ZeroTier Central config was specified for a ZeroTier One command." << std::endl; + exit(0); + } + if(thingType == "central" && state.settings["things"][configName]["type"].get() != "central") { + std::cout << FAIL_STR << "A ZeroTier One config was specified for a ZeroTier Central command." << std::endl; + exit(0); + } + } + else { // no @thing specified, check for defaults depending on type + if(thingType == "one") { + if(state.settings.find("defaultOne") != state.settings.end()) { + if(warnNoThingProvided) + std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier One command: " << state.settings["defaultOne"].get().c_str() << std::endl; + configName = state.settings["defaultOne"].get().erase(0,1); // get default + } + else { + std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; + std::cout << "HELP: To set a default: zerotier cli-set defaultOne @my_default_thing" << std::endl; + exit(0); + } + } + if(thingType == "central") { + if(state.settings.find("defaultCentral") != state.settings.end()) { + if(warnNoThingProvided) + std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier Central command: " << state.settings["defaultCentral"].get().c_str() << std::endl; + configName = state.settings["defaultCentral"].get().erase(0,1); // get default + } + else { + std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; + std::cout << "HELP: To set a default: zerotier cli-set defaultCentral @my_default_thing" << std::endl; + exit(0); + } + } + } + // Apply headers + if(thingType == "one") { + state.reqHeaders["X-ZT1-Auth"] = state.settings["things"][configName]["auth"]; + } + if(thingType == "central"){ + state.reqHeaders["Content-Type"] = "application/json"; + state.reqHeaders["Authorization"] = "Bearer " + state.settings["things"][configName]["auth"].get(); + state.reqHeaders["Accept"] = "application/json"; + } + state.url = state.settings["things"][configName]["url"]; +} + +static bool checkURL(std::string url) +{ + // TODO + return true; +} + +static std::string getLocalVersion(CLIState &state) +{ + json result; + std::tuple res; + checkForThing(state,"one",false); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status"); + if(std::get<0>(res) == 200) { + result = json::parse(std::get<1>(res)); + return result["version"].get(); + } + return "---"; +} + +#ifdef __WINDOWS__ +int _tmain(int argc, _TCHAR* argv[]) +#else +int main(int argc,char **argv) +#endif +{ +#ifdef __WINDOWS__ + { + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2),&wsaData); + } +#endif + + curl_global_init(CURL_GLOBAL_DEFAULT); + CLIState state; + std::string arg1, arg2, authToken; + + for(int i=1;i 0)&&(port < 65536))&&(authToken.length() > 0)) { + state.settings["things"]["local"]["url"] = (std::string("http://127.0.0.1:") + portStr + "/"); + state.settings["things"]["local"]["auth"] = authToken; + initSuccess = true; + } + } + + if (!saveSettings(state)) { + std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl; + exit(-1); + } + + if (initSuccess) { + std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << std::endl; + } else { + std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << " but could not auto-init local ZeroTier One service config from " << oneHome << " -- you will need to set local service URL and port manually if you want to control a local instance of ZeroTier One. (This happens if you are not root/administrator.)" << std::endl; + } + } + } + + // PRE-REQUEST SETUP + + json result; + std::tuple res; + std::string url = ""; + + // META + + if ((state.command.length() == 0)||(state.command == "help")) { + dumpHelp(); + return -1; + } + + // zerotier version + else if (state.command == "v" || state.command == "version") { + std::cout << getLocalVersion(state) << std::endl; + return 1; + } + + // zerotier cli-set + else if (state.command == "cli-set") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-set " << std::endl; + return 1; + } + std::string settingName, settingValue; + if(state.atname.length()) { // User provided @thing erroneously, we will ignore it and adjust argument indices + settingName = argv[3]; + settingValue = argv[4]; + } + else { + settingName = argv[2]; + settingValue = argv[3]; + } + saveSettingsBackup(state); + state.settings[settingName] = settingValue; // changes + saveSettings(state); + } + + // zerotier cli-unset + else if (state.command == "cli-unset") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-unset " << std::endl; + return 1; + } + std::string settingName; + if(state.atname.length()) // User provided @thing erroneously, we will ignore it and adjust argument indices + settingName = argv[3]; + else + settingName = argv[2]; + saveSettingsBackup(state); + state.settings.erase(settingName); // changes + saveSettings(state); + } + + // zerotier @thing_to_remove cli-rm --- removes the configuration + else if (state.command == "cli-rm") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-rm <@thing>" << std::endl; + return 1; + } + if(state.settings["things"].find(state.atname) != state.settings["things"].end()) { + if(state.settings["defaultOne"] == state.atname) { + std::cout << "WARNING: The config you're trying to remove is currently set as your default. Set a new default first!" << std::endl; + std::cout << " | Usage: zerotier set defaultOne @your_other_thing" << std::endl; + } + else { + state.settings["things"].erase(state.atname.c_str()); + saveSettings(state); + } + } + } + + // zerotier cli-add-zt + // TODO: Check for malformed urls/auth + else if (state.command == "cli-add-zt") { + if(argc != 5) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-add-zt " << std::endl; + return 1; + } + std::string thing_name = argv[2], url = argv[3], auth = argv[4]; + if(!checkURL(url)) { + std::cout << FAIL_STR << "Malformed URL" << std::endl; + return 1; + } + if(state.settings.find(thing_name) != state.settings.end()) { + std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() + << " already exists. Choose another name or rename the old @thing" << std::endl; + std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; + } + else { + result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "one" + "\", \"url\": \"" + url + "\" }"); + saveSettingsBackup(state); + // TODO: Handle cases where user may or may not prepend an @ + state.settings["things"][thing_name] = result; // changes + saveSettings(state); + } + } + + // zerotier cli-add-central + // TODO: Check for malformed urls/auth + else if (state.command == "cli-add-central") { + if(argc != 5) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-add-central " << std::endl; + return 1; + } + std::string thing_name = argv[2], url = argv[3], auth = argv[4]; + if(!checkURL(url)) { + std::cout << FAIL_STR << "Malformed URL" << std::endl; + return 1; + } + if(state.settings.find(thing_name) != state.settings.end()) { + std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() + << " already exists. Choose another name or rename the old @thing" << std::endl; + std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; + } + else { + result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "central" + "\", \"url\": \"" + url + "\" }"); + saveSettingsBackup(state); + // TODO: Handle cases where user may or may not prepend an @ + state.settings["things"]["@" + thing_name] = result; // changes + saveSettings(state); + } + } + + // ONE SERVICE + + // zerotier ls --- display all networks currently joined + else if (state.command == "ls" || state.command == "listnetworks") { + if(argc != 2) { + std::cerr << INVALID_ARGS_STR << "zerotier ls" << std::endl; + return 1; + } + checkForThing(state,"one",true); + url = state.url + "network"; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); + if(std::get<0>(res) == 200) { + std::cout << "listnetworks " << std::endl; + auto j = json::parse(std::get<1>(res).c_str()); + if (j.type() == json::value_t::array) { + for(int i=0;i(); + std::string name = j[i]["name"].get(); + std::string mac = j[i]["mac"].get(); + std::string status = j[i]["status"].get(); + std::string type = j[i]["type"].get(); + std::string addrs; + for(int m=0; m() + " "; + } + std::string dev = j[i]["portDeviceName"].get(); + std::cout << "listnetworks " << nwid << " " << name << " " << mac << " " << status << " " << type << " " << dev << " " << addrs << std::endl; + } + } + } + } + + // zerotier join --- joins a network + else if (state.command == "join") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier join " << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_POST,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); + if(std::get<0>(res) == 200) { + std::cout << OK_STR << "connected to " << state.args[0] << std::endl; + } + } + + // zerotier leave --- leaves a network + else if (state.command == "leave") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier leave " << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_DEL,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); + if(std::get<0>(res) == 200) { + std::cout << OK_STR << "disconnected from " << state.args[0] << std::endl; + } + } + + // zerotier peers --- display address and role of all peers + else if (state.command == "peers") { + if(argc != 2) { + std::cerr << INVALID_ARGS_STR << "zerotier peers" << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/peer"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + for(int i=0; i(res) == 200) { + result = json::parse(std::get<1>(res)); + std::string status_str = result["online"].get() ? "ONLINE" : "OFFLINE"; + std::cout << "info " << result["address"].get() + << " " << status_str << " " << result["version"].get() << std::endl; + } + } + + // REMOTE + + // zerotier @thing net-create --- creates a new network + else if (state.command == "net-create") { + if(argc > 3 || (argc == 3 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-create" << std::endl; + return 1; + } + checkForThing(state,"central",true); + res = REQUEST(REQ_POST,state,state.reqHeaders,"",state.url + "api/network"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + std::cout << OK_STR << "created network " << result["config"]["nwid"].get() << std::endl; + } + } + + // zerotier @thing net-rm --- deletes a network + else if (state.command == "net-rm") { + if(argc > 4 || (argc == 4 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-rm " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(!state.args.size()) { + std::cout << "Argument error: No network specified." << std::endl; + std::cout << " | Usage: zerotier net-rm " << std::endl; + } + else { + std::string nwid = state.args[0]; + res = REQUEST(REQ_DEL,state,state.reqHeaders,"",state.url + "api/network/" + nwid); + if(std::get<0>(res) == 200) { + std::cout << "deleted network " << nwid << std::endl; + } + } + } + + // zerotier @thing net-ls --- lists all networks + else if (state.command == "net-ls") { + if(argc > 3 || (argc == 3 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-ls" << std::endl; + return 1; + } + checkForThing(state,"central",true); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + for(int m=0;m() << std::endl; + } + } + } + + // zerotier @thing net-members --- show all members of a network + else if (state.command == "net-members") { + if(argc > 4 || (argc == 4 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-members " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(!state.args.size()) { + std::cout << FAIL_STR << "Argument error: No network specified." << std::endl; + std::cout << " | Usage: zerotier net-members " << std::endl; + } + else { + std::string nwid = state.args[0]; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member"); + json result = json::parse(std::get<1>(res)); + std::cout << "Members of " << nwid << ":" << std::endl; + for (json::iterator it = result.begin(); it != result.end(); ++it) { + std::cout << it.key() << std::endl; + } + } + } + + // zerotier @thing net-show --- show info about a device on a specific network + else if (state.command == "net-show") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-show " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() < 2) { + std::cout << FAIL_STR << "Argument error: Too few arguments." << std::endl; + std::cout << " | Usage: zerotier net-show " << std::endl; + } + else { + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); + // TODO: More info, what would we like to show exactly? + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + std::cout << "Assigned IP: " << std::endl; + for(int m=0; m() << std::endl; + } + } + } + } + + // zerotier @thing net-auth --- authorize a device on a network + else if (state.command == "net-auth") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-auth " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() != 2) { + std::cout << FAIL_STR << "Argument error: Network and/or device ID not specified." << std::endl; + std::cout << " | Usage: zerotier net-auth " << std::endl; + } + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + url = state.url + "api/network/" + nwid + "/member/" + devid; + // Add device to network + res = REQUEST(REQ_POST,state,state.reqHeaders,"",(const std::string)url); + if(std::get<0>(res) == 200) { + result = json::parse(std::get<1>(res)); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); + result = json::parse(std::get<1>(res)); + result["config"]["authorized"] = "true"; + std::string newconfig = result.dump(); + res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,(const std::string)url); + if(std::get<0>(res) == 200) + std::cout << OK_STR << devid << " authorized on " << nwid << std::endl; + else + std::cout << FAIL_STR << "There was a problem authorizing that device." << std::endl; + } + } + + // zerotier @thing net-unauth + else if (state.command == "net-unauth") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-unauth " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() != 2) { + std::cout << FAIL_STR << "Bad argument. No network and/or device ID specified." << std::endl; + std::cout << " | Usage: zerotier net-unauth " << std::endl; + } + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + // If successful, get member config + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); + result = json::parse(std::get<1>(res)); + // modify auth field and re-POST + result["config"]["authorized"] = "false"; + std::string newconfig = result.dump(); + res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,state.url + "api/network/" + nwid + "/member/" + devid); + if(std::get<0>(res) == 200) + std::cout << OK_STR << devid << " de-authorized from " << nwid << std::endl; + else + std::cout << FAIL_STR << "There was a problem de-authorizing that device." << std::endl; + } + + // zerotier @thing net-set + else if (state.command == "net-set") { + } + + // ID + + // zerotier id-generate [] + else if (state.command == "id-generate") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-generate []" << std::endl; + return 1; + } + uint64_t vanity = 0; + int vanityBits = 0; + if (argc >= 5) { + vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL; + vanityBits = 4 * strlen(argv[4]); + if (vanityBits > 40) + vanityBits = 40; + } + + ZeroTier::Identity id; + for(;;) { + id.generate(); + if ((id.address().toInt() >> (40 - vanityBits)) == vanity) { + if (vanityBits > 0) { + fprintf(stderr,"vanity address: found %.10llx !\n",(unsigned long long)id.address().toInt()); + } + break; + } else { + fprintf(stderr,"vanity address: tried %.10llx looking for first %d bits of %.10llx\n",(unsigned long long)id.address().toInt(),vanityBits,(unsigned long long)(vanity << (40 - vanityBits))); + } + } + + std::string idser = id.toString(true); + if (argc >= 3) { + if (!OSUtils::writeFile(argv[2],idser)) { + std::cerr << "Error writing to " << argv[2] << std::endl; + return 1; + } else std::cout << argv[2] << " written" << std::endl; + if (argc >= 4) { + idser = id.toString(false); + if (!OSUtils::writeFile(argv[3],idser)) { + std::cerr << "Error writing to " << argv[3] << std::endl; + return 1; + } else std::cout << argv[3] << " written" << std::endl; + } + } else std::cout << idser << std::endl; + } + + // zerotier id-validate + else if (state.command == "id-validate") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-validate " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + if (!id.locallyValidate()) { + std::cerr << argv[2] << " FAILED validation." << std::endl; + return 1; + } else std::cout << argv[2] << "is a valid identity" << std::endl; + } + + // zerotier id-sign + else if (state.command == "id-sign") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier id-sign " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + if (!id.hasPrivate()) { + std::cerr << argv[2] << " does not contain a private key (must use private to sign)" << std::endl; + return 1; + } + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + std::cerr << argv[3] << " is not readable" << std::endl; + return 1; + } + C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length()); + std::cout << Utils::hex(signature.data,(unsigned int)signature.size()) << std::endl; + } + + // zerotier id-verify + else if (state.command == "id-verify") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier id-verify " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + std::cerr << argv[3] << " is not readable" << std::endl; + return 1; + } + std::string signature(Utils::unhex(argv[4])); + if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) { + std::cout << argv[3] << " signature valid" << std::endl; + } else { + std::cerr << argv[3] << " signature check FAILED" << std::endl; + return 1; + } + } + + // zerotier id-getpublic + else if (state.command == "id-getpublic") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-getpublic " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + std::cerr << id.toString(false) << std::endl; + } + // + else { + dumpHelp(); + return -1; + } + if(std::find(state.args.begin(), state.args.end(), "-verbose") != state.args.end()) + std::cout << "\n\nAPI response = " << std::get<1>(res) << std::endl; + curl_global_cleanup(); + return 0; +} diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index 595df07e..00000000 --- a/cli/README.md +++ /dev/null @@ -1,57 +0,0 @@ -The new ZeroTier CLI! -==== - -With this update we've expanded upon the previous CLI's functionality, so things should seem pretty familiar. Here are some of the new features we've introduced: - - - Create and administer networks on ZeroTier Central directly from the console. - - Service configurations, allows you to control local/remote instances of ZeroTier One - - Identity generation and management is now part of the same CLI tool - -*** -## Configurations - -Configurations are a way for you to nickname and logically organize the control of ZeroTier services running locally or remotely (this includes ZeroTier Central!). They're merely groupings of service API url's and auth tokens. The CLI's settings data is contained within `.zerotierCliSettings`. - -For instance, you can control your local instance of ZeroTier One via the `@local` config. By default it is represented as follows: - -``` -"local": { - "auth": "7tyqRoFytajf21j2l2t9QPm5", - "type": "one", - "url": "http://127.0.0.1:9993/" -} -``` - -As an example, if you issue the command `zerotier ls` is it implicitly stating `zerotier @local ls`. - -With the same line of thinking, you could create a `@my.zerotier.com` which would allow for something like `zerotier @my.zerotier.com net-create` which talks to our hosted ZeroTier Central to create a new network. - - - -## Command families - -- `cli-` is for configuring the settings data for the CLI itself, such as adding/removing `@thing` configurations, variables, etc. -- `net-` is for operating on a *ZeroTier Central* service such as `https://my.zerotier.com` -- `id-` is for handling ZeroTier identities. - -And those commands with no prefix are there to allow you to operate ZeroTier One instances either local or remote. - -*** -## Useful command examples - -*Add a ZeroTier One configuration:* - - - `zerotier cli-add-zt MyLocalConfigName https://127.0.0.1:9993/ ` - -*Add a ZeroTier Central configuration:* - - - `zerotier cli-add-central MyZTCentralConfigName https://my.zerotier.com/ ` - -*Set a default ZeroTier One instance:* - - - `zerotier cli-set defaultOne MyLocalConfigName` - -*Set a default ZeroTier Central:* - - - `zerotier cli-set defaultCentral MyZTCentralConfigName` - diff --git a/cli/zerotier.cpp b/cli/zerotier.cpp deleted file mode 100644 index e75268d1..00000000 --- a/cli/zerotier.cpp +++ /dev/null @@ -1,957 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// Note: unlike the rest of ZT's code base, this requires C++11 due to -// the JSON library it uses and other things. - -#include -#include -#include -#include - -#include "../node/Constants.hpp" -#include "../node/Identity.hpp" -#include "../version.h" -#include "../osdep/OSUtils.hpp" -#include "../ext/offbase/json/json.hpp" - -#ifdef __WINDOWS__ -#include -#include -#include -#include -#else -#include -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include - -using json = nlohmann::json; -using namespace ZeroTier; - -#define ZT_CLI_FLAG_VERBOSE 'v' -#define ZT_CLI_FLAG_UNSAFE_SSL 'X' - -#define REQ_GET 0 -#define REQ_POST 1 -#define REQ_DEL 2 - -#define OK_STR "[OK ]: " -#define FAIL_STR "[FAIL]: " -#define WARN_STR "[WARN]: " -#define INVALID_ARGS_STR "Invalid args. Usage: " - -struct CLIState -{ - std::string atname; - std::string command; - std::string url; - std::map reqHeaders; - std::vector args; - std::map opts; - json settings; -}; - -namespace { - -static Identity getIdFromArg(char *arg) -{ - Identity id; - if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line - if (id.fromString(arg)) - return id; - } else { // identity is to be read from a file - std::string idser; - if (OSUtils::readFile(arg,idser)) { - if (id.fromString(idser)) - return id; - } - } - return Identity(); -} - -static std::string trimString(const std::string &s) -{ - unsigned long end = (unsigned long)s.length(); - while (end) { - char c = s[end - 1]; - if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) - --end; - else break; - } - unsigned long start = 0; - while (start < end) { - char c = s[start]; - if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) - ++start; - else break; - } - return s.substr(start,end - start); -} - -static inline std::string getSettingsFilePath() -{ -#ifdef __WINDOWS__ -#else - const char *home = getenv("HOME"); - if (!home) - home = "/"; - return (std::string(home) + "/.zerotierCliSettings"); -#endif -} - -static bool saveSettingsBackup(CLIState &state) -{ - std::string sfp(getSettingsFilePath().c_str()); - if(state.settings.find("generateBackupConfig") != state.settings.end() - && state.settings["generateBackupConfig"].get() == "true") { - std::string backup_file = getSettingsFilePath() + ".bak"; - if(!OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { - OSUtils::lockDownFile(sfp.c_str(),false); - std::cout << WARN_STR << "unable to write backup config file" << std::endl; - return false; - } - return true; - } - return false; -} - -static bool saveSettings(CLIState &state) -{ - std::string sfp(getSettingsFilePath().c_str()); - if(OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { - OSUtils::lockDownFile(sfp.c_str(),false); - std::cout << OK_STR << "changes saved." << std::endl; - return true; - } - std::cout << FAIL_STR << "unable to write to " << sfp << std::endl; - return false; -} - -static void dumpHelp() -{ - std::cout << "ZeroTier Newer-Spiffier CLI " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << std::endl; - std::cout << "(c)2016 ZeroTier, Inc. / Licensed under the GNU GPL v3" << std::endl; - std::cout << std::endl; - std::cout << "Configuration path: " << getSettingsFilePath() << std::endl; - std::cout << std::endl; - std::cout << "Usage: zerotier [-option] [@name] []" << std::endl; - std::cout << std::endl; - std::cout << "Options:" << std::endl; - std::cout << " -verbose - Verbose JSON output" << std::endl; - std::cout << " -X - Do not check SSL certs (CAUTION!)" << std::endl; - std::cout << std::endl; - std::cout << "CLI Configuration Commands:" << std::endl; - std::cout << " cli-set - Set a CLI option ('cli-set help')" << std::endl; - std::cout << " cli-unset - Un-sets a CLI option ('cli-unset help')" << std::endl; - std::cout << " cli-ls - List configured @things" << std::endl; - std::cout << " cli-rm @name - Remove a configured @thing" << std::endl; - std::cout << " cli-add-zt @name - Add a ZeroTier service" << std::endl; - std::cout << " cli-add-central @name - Add ZeroTier Central instance" << std::endl; - std::cout << std::endl; - std::cout << "ZeroTier One Service Commands:" << std::endl; - std::cout << " -v / -version - Displays default local instance's version'" << std::endl; - std::cout << " ls - List currently joined networks" << std::endl; - std::cout << " join [opt=value ...] - Join a network" << std::endl; - std::cout << " leave - Leave a network" << std::endl; - std::cout << " peers - List ZeroTier VL1 peers" << std::endl; - std::cout << " show [] - Get info about self or object" << std::endl; - std::cout << std::endl; - std::cout << "Network Controller Commands:" << std::endl; - std::cout << " net-create - Create a new network" << std::endl; - std::cout << " net-rm - Delete a network (CAUTION!)" << std::endl; - std::cout << " net-ls - List administered networks" << std::endl; - std::cout << " net-members - List members of a network" << std::endl; - std::cout << " net-show [
] - Get network or member info" << std::endl; - std::cout << " net-auth
- Authorize a member" << std::endl; - std::cout << " net-unauth
- De-authorize a member" << std::endl; - std::cout << " net-set - See 'net-set help'" << std::endl; - std::cout << std::endl; - std::cout << "Identity Commands:" << std::endl; - std::cout << " id-generate [] - Generate a ZeroTier identity" << std::endl; - std::cout << " id-validate - Locally validate an identity" << std::endl; - std::cout << " id-sign - Sign a file" << std::endl; - std::cout << " id-verify - Verify a file's signature" << std::endl; - std::cout << " id-getpublic - Get full identity's public portion" << std::endl; - std::cout << std::endl; -} - -static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,void *stdstring) -{ - size_t totalSize = size * nmemb; - reinterpret_cast(stdstring)->append((const char *)contents,totalSize); - return totalSize; -} - -static std::tuple REQUEST(int requestType, CLIState &state, const std::map &headers, const std::string &postfield, const std::string &url) -{ - std::string body; - char errbuf[CURL_ERROR_SIZE]; - char urlbuf[4096]; - - CURL *curl; - curl = curl_easy_init(); - if (!curl) { - std::cerr << "FATAL: curl_easy_init() failed" << std::endl; - exit(-1); - } - - Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str()); - curl_easy_setopt(curl,CURLOPT_URL,urlbuf); - - struct curl_slist *hdrs = (struct curl_slist *)0; - for(std::map::const_iterator i(headers.begin());i!=headers.end();++i) { - std::string htmp(i->first); - htmp.append(": "); - htmp.append(i->second); - hdrs = curl_slist_append(hdrs,htmp.c_str()); - } - if (hdrs) - curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs); - - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); - curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body); - curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback); - - if(std::find(state.args.begin(), state.args.end(), "-X") == state.args.end()) - curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L); - - if(requestType == REQ_POST) { - curl_easy_setopt(curl, CURLOPT_POST, 1); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfield.c_str()); - } - if(requestType == REQ_DEL) - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - if(requestType == REQ_GET) { - curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf); - curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L); - } - - curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI"); - CURLcode res = curl_easy_perform(curl); - - errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check - - if (res != CURLE_OK) - return std::make_tuple(-1,std::string(errbuf)); - - long response_code; - int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &response_code); - - if(response_code == 401) { std::cout << FAIL_STR << response_code << "Unauthorized." << std::endl; exit(0); } - else if(response_code == 403) { std::cout << FAIL_STR << response_code << "Forbidden." << std::endl; exit(0); } - else if(response_code == 404) { std::cout << FAIL_STR << response_code << "Not found." << std::endl; exit(0); } - else if(response_code == 408) { std::cout << FAIL_STR << response_code << "Request timed out." << std::endl; exit(0); } - else if(response_code != 200) { std::cout << FAIL_STR << response_code << "Unable to process request." << std::endl; exit(0); } - - curl_easy_cleanup(curl); - if (hdrs) - curl_slist_free_all(hdrs); - return std::make_tuple(response_code,body); -} - -} // anonymous namespace - -////////////////////////////////////////////////////////////////////////////// - -// Check for user-specified @thing config -// Make sure it @thing makes sense -// Apply appropriate request headers -static void checkForThing(CLIState &state, std::string thingType, bool warnNoThingProvided) -{ - std::string configName; - if(state.atname.length()) { - configName = state.atname.erase(0,1); - // make sure specified @thing makes sense in the context of the command - if(thingType == "one" && state.settings["things"][configName]["type"].get() != "one") { - std::cout << FAIL_STR << "A ZeroTier Central config was specified for a ZeroTier One command." << std::endl; - exit(0); - } - if(thingType == "central" && state.settings["things"][configName]["type"].get() != "central") { - std::cout << FAIL_STR << "A ZeroTier One config was specified for a ZeroTier Central command." << std::endl; - exit(0); - } - } - else { // no @thing specified, check for defaults depending on type - if(thingType == "one") { - if(state.settings.find("defaultOne") != state.settings.end()) { - if(warnNoThingProvided) - std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier One command: " << state.settings["defaultOne"].get().c_str() << std::endl; - configName = state.settings["defaultOne"].get().erase(0,1); // get default - } - else { - std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; - std::cout << "HELP: To set a default: zerotier cli-set defaultOne @my_default_thing" << std::endl; - exit(0); - } - } - if(thingType == "central") { - if(state.settings.find("defaultCentral") != state.settings.end()) { - if(warnNoThingProvided) - std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier Central command: " << state.settings["defaultCentral"].get().c_str() << std::endl; - configName = state.settings["defaultCentral"].get().erase(0,1); // get default - } - else { - std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; - std::cout << "HELP: To set a default: zerotier cli-set defaultCentral @my_default_thing" << std::endl; - exit(0); - } - } - } - // Apply headers - if(thingType == "one") { - state.reqHeaders["X-ZT1-Auth"] = state.settings["things"][configName]["auth"]; - } - if(thingType == "central"){ - state.reqHeaders["Content-Type"] = "application/json"; - state.reqHeaders["Authorization"] = "Bearer " + state.settings["things"][configName]["auth"].get(); - state.reqHeaders["Accept"] = "application/json"; - } - state.url = state.settings["things"][configName]["url"]; -} - -static bool checkURL(std::string url) -{ - // TODO - return true; -} - -static std::string getLocalVersion(CLIState &state) -{ - json result; - std::tuple res; - checkForThing(state,"one",false); - res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status"); - if(std::get<0>(res) == 200) { - result = json::parse(std::get<1>(res)); - return result["version"].get(); - } - return "---"; -} - -#ifdef __WINDOWS__ -int _tmain(int argc, _TCHAR* argv[]) -#else -int main(int argc,char **argv) -#endif -{ -#ifdef __WINDOWS__ - { - WSADATA wsaData; - WSAStartup(MAKEWORD(2,2),&wsaData); - } -#endif - - curl_global_init(CURL_GLOBAL_DEFAULT); - CLIState state; - std::string arg1, arg2, authToken; - - for(int i=1;i 0)&&(port < 65536))&&(authToken.length() > 0)) { - state.settings["things"]["local"]["url"] = (std::string("http://127.0.0.1:") + portStr + "/"); - state.settings["things"]["local"]["auth"] = authToken; - initSuccess = true; - } - } - - if (!saveSettings(state)) { - std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl; - exit(-1); - } - - if (initSuccess) { - std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << std::endl; - } else { - std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << " but could not auto-init local ZeroTier One service config from " << oneHome << " -- you will need to set local service URL and port manually if you want to control a local instance of ZeroTier One. (This happens if you are not root/administrator.)" << std::endl; - } - } - } - - // PRE-REQUEST SETUP - - json result; - std::tuple res; - std::string url = ""; - - // META - - if ((state.command.length() == 0)||(state.command == "help")) { - dumpHelp(); - return -1; - } - - // zerotier version - else if (state.command == "v" || state.command == "version") { - std::cout << getLocalVersion(state) << std::endl; - return 1; - } - - // zerotier cli-set - else if (state.command == "cli-set") { - if(argc != 4) { - std::cerr << INVALID_ARGS_STR << "zerotier cli-set " << std::endl; - return 1; - } - std::string settingName, settingValue; - if(state.atname.length()) { // User provided @thing erroneously, we will ignore it and adjust argument indices - settingName = argv[3]; - settingValue = argv[4]; - } - else { - settingName = argv[2]; - settingValue = argv[3]; - } - saveSettingsBackup(state); - state.settings[settingName] = settingValue; // changes - saveSettings(state); - } - - // zerotier cli-unset - else if (state.command == "cli-unset") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier cli-unset " << std::endl; - return 1; - } - std::string settingName; - if(state.atname.length()) // User provided @thing erroneously, we will ignore it and adjust argument indices - settingName = argv[3]; - else - settingName = argv[2]; - saveSettingsBackup(state); - state.settings.erase(settingName); // changes - saveSettings(state); - } - - // zerotier @thing_to_remove cli-rm --- removes the configuration - else if (state.command == "cli-rm") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier cli-rm <@thing>" << std::endl; - return 1; - } - if(state.settings["things"].find(state.atname) != state.settings["things"].end()) { - if(state.settings["defaultOne"] == state.atname) { - std::cout << "WARNING: The config you're trying to remove is currently set as your default. Set a new default first!" << std::endl; - std::cout << " | Usage: zerotier set defaultOne @your_other_thing" << std::endl; - } - else { - state.settings["things"].erase(state.atname.c_str()); - saveSettings(state); - } - } - } - - // zerotier cli-add-zt - // TODO: Check for malformed urls/auth - else if (state.command == "cli-add-zt") { - if(argc != 5) { - std::cerr << INVALID_ARGS_STR << "zerotier cli-add-zt " << std::endl; - return 1; - } - std::string thing_name = argv[2], url = argv[3], auth = argv[4]; - if(!checkURL(url)) { - std::cout << FAIL_STR << "Malformed URL" << std::endl; - return 1; - } - if(state.settings.find(thing_name) != state.settings.end()) { - std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() - << " already exists. Choose another name or rename the old @thing" << std::endl; - std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; - } - else { - result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "one" + "\", \"url\": \"" + url + "\" }"); - saveSettingsBackup(state); - // TODO: Handle cases where user may or may not prepend an @ - state.settings["things"][thing_name] = result; // changes - saveSettings(state); - } - } - - // zerotier cli-add-central - // TODO: Check for malformed urls/auth - else if (state.command == "cli-add-central") { - if(argc != 5) { - std::cerr << INVALID_ARGS_STR << "zerotier cli-add-central " << std::endl; - return 1; - } - std::string thing_name = argv[2], url = argv[3], auth = argv[4]; - if(!checkURL(url)) { - std::cout << FAIL_STR << "Malformed URL" << std::endl; - return 1; - } - if(state.settings.find(thing_name) != state.settings.end()) { - std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() - << " already exists. Choose another name or rename the old @thing" << std::endl; - std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; - } - else { - result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "central" + "\", \"url\": \"" + url + "\" }"); - saveSettingsBackup(state); - // TODO: Handle cases where user may or may not prepend an @ - state.settings["things"]["@" + thing_name] = result; // changes - saveSettings(state); - } - } - - // ONE SERVICE - - // zerotier ls --- display all networks currently joined - else if (state.command == "ls" || state.command == "listnetworks") { - if(argc != 2) { - std::cerr << INVALID_ARGS_STR << "zerotier ls" << std::endl; - return 1; - } - checkForThing(state,"one",true); - url = state.url + "network"; - res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); - if(std::get<0>(res) == 200) { - std::cout << "listnetworks " << std::endl; - auto j = json::parse(std::get<1>(res).c_str()); - if (j.type() == json::value_t::array) { - for(int i=0;i(); - std::string name = j[i]["name"].get(); - std::string mac = j[i]["mac"].get(); - std::string status = j[i]["status"].get(); - std::string type = j[i]["type"].get(); - std::string addrs; - for(int m=0; m() + " "; - } - std::string dev = j[i]["portDeviceName"].get(); - std::cout << "listnetworks " << nwid << " " << name << " " << mac << " " << status << " " << type << " " << dev << " " << addrs << std::endl; - } - } - } - } - - // zerotier join --- joins a network - else if (state.command == "join") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier join " << std::endl; - return 1; - } - checkForThing(state,"one",true); - res = REQUEST(REQ_POST,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); - if(std::get<0>(res) == 200) { - std::cout << OK_STR << "connected to " << state.args[0] << std::endl; - } - } - - // zerotier leave --- leaves a network - else if (state.command == "leave") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier leave " << std::endl; - return 1; - } - checkForThing(state,"one",true); - res = REQUEST(REQ_DEL,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); - if(std::get<0>(res) == 200) { - std::cout << OK_STR << "disconnected from " << state.args[0] << std::endl; - } - } - - // zerotier peers --- display address and role of all peers - else if (state.command == "peers") { - if(argc != 2) { - std::cerr << INVALID_ARGS_STR << "zerotier peers" << std::endl; - return 1; - } - checkForThing(state,"one",true); - res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/peer"); - if(std::get<0>(res) == 200) { - json result = json::parse(std::get<1>(res)); - for(int i=0; i(res) == 200) { - result = json::parse(std::get<1>(res)); - std::string status_str = result["online"].get() ? "ONLINE" : "OFFLINE"; - std::cout << "info " << result["address"].get() - << " " << status_str << " " << result["version"].get() << std::endl; - } - } - - // REMOTE - - // zerotier @thing net-create --- creates a new network - else if (state.command == "net-create") { - if(argc > 3 || (argc == 3 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-create" << std::endl; - return 1; - } - checkForThing(state,"central",true); - res = REQUEST(REQ_POST,state,state.reqHeaders,"",state.url + "api/network"); - if(std::get<0>(res) == 200) { - json result = json::parse(std::get<1>(res)); - std::cout << OK_STR << "created network " << result["config"]["nwid"].get() << std::endl; - } - } - - // zerotier @thing net-rm --- deletes a network - else if (state.command == "net-rm") { - if(argc > 4 || (argc == 4 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-rm " << std::endl; - return 1; - } - checkForThing(state,"central",true); - if(!state.args.size()) { - std::cout << "Argument error: No network specified." << std::endl; - std::cout << " | Usage: zerotier net-rm " << std::endl; - } - else { - std::string nwid = state.args[0]; - res = REQUEST(REQ_DEL,state,state.reqHeaders,"",state.url + "api/network/" + nwid); - if(std::get<0>(res) == 200) { - std::cout << "deleted network " << nwid << std::endl; - } - } - } - - // zerotier @thing net-ls --- lists all networks - else if (state.command == "net-ls") { - if(argc > 3 || (argc == 3 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-ls" << std::endl; - return 1; - } - checkForThing(state,"central",true); - res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network"); - if(std::get<0>(res) == 200) { - json result = json::parse(std::get<1>(res)); - for(int m=0;m() << std::endl; - } - } - } - - // zerotier @thing net-members --- show all members of a network - else if (state.command == "net-members") { - if(argc > 4 || (argc == 4 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-members " << std::endl; - return 1; - } - checkForThing(state,"central",true); - if(!state.args.size()) { - std::cout << FAIL_STR << "Argument error: No network specified." << std::endl; - std::cout << " | Usage: zerotier net-members " << std::endl; - } - else { - std::string nwid = state.args[0]; - res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member"); - json result = json::parse(std::get<1>(res)); - std::cout << "Members of " << nwid << ":" << std::endl; - for (json::iterator it = result.begin(); it != result.end(); ++it) { - std::cout << it.key() << std::endl; - } - } - } - - // zerotier @thing net-show --- show info about a device on a specific network - else if (state.command == "net-show") { - if(argc > 5 || (argc == 5 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-show " << std::endl; - return 1; - } - checkForThing(state,"central",true); - if(state.args.size() < 2) { - std::cout << FAIL_STR << "Argument error: Too few arguments." << std::endl; - std::cout << " | Usage: zerotier net-show " << std::endl; - } - else { - std::string nwid = state.args[0]; - std::string devid = state.args[1]; - res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); - // TODO: More info, what would we like to show exactly? - if(std::get<0>(res) == 200) { - json result = json::parse(std::get<1>(res)); - std::cout << "Assigned IP: " << std::endl; - for(int m=0; m() << std::endl; - } - } - } - } - - // zerotier @thing net-auth --- authorize a device on a network - else if (state.command == "net-auth") { - if(argc > 5 || (argc == 5 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-auth " << std::endl; - return 1; - } - checkForThing(state,"central",true); - if(state.args.size() != 2) { - std::cout << FAIL_STR << "Argument error: Network and/or device ID not specified." << std::endl; - std::cout << " | Usage: zerotier net-auth " << std::endl; - } - std::string nwid = state.args[0]; - std::string devid = state.args[1]; - url = state.url + "api/network/" + nwid + "/member/" + devid; - // Add device to network - res = REQUEST(REQ_POST,state,state.reqHeaders,"",(const std::string)url); - if(std::get<0>(res) == 200) { - result = json::parse(std::get<1>(res)); - res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); - result = json::parse(std::get<1>(res)); - result["config"]["authorized"] = "true"; - std::string newconfig = result.dump(); - res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,(const std::string)url); - if(std::get<0>(res) == 200) - std::cout << OK_STR << devid << " authorized on " << nwid << std::endl; - else - std::cout << FAIL_STR << "There was a problem authorizing that device." << std::endl; - } - } - - // zerotier @thing net-unauth - else if (state.command == "net-unauth") { - if(argc > 5 || (argc == 5 && !state.atname.length())) { - std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-unauth " << std::endl; - return 1; - } - checkForThing(state,"central",true); - if(state.args.size() != 2) { - std::cout << FAIL_STR << "Bad argument. No network and/or device ID specified." << std::endl; - std::cout << " | Usage: zerotier net-unauth " << std::endl; - } - std::string nwid = state.args[0]; - std::string devid = state.args[1]; - // If successful, get member config - res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); - result = json::parse(std::get<1>(res)); - // modify auth field and re-POST - result["config"]["authorized"] = "false"; - std::string newconfig = result.dump(); - res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,state.url + "api/network/" + nwid + "/member/" + devid); - if(std::get<0>(res) == 200) - std::cout << OK_STR << devid << " de-authorized from " << nwid << std::endl; - else - std::cout << FAIL_STR << "There was a problem de-authorizing that device." << std::endl; - } - - // zerotier @thing net-set - else if (state.command == "net-set") { - } - - // ID - - // zerotier id-generate [] - else if (state.command == "id-generate") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier id-generate []" << std::endl; - return 1; - } - uint64_t vanity = 0; - int vanityBits = 0; - if (argc >= 5) { - vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL; - vanityBits = 4 * strlen(argv[4]); - if (vanityBits > 40) - vanityBits = 40; - } - - ZeroTier::Identity id; - for(;;) { - id.generate(); - if ((id.address().toInt() >> (40 - vanityBits)) == vanity) { - if (vanityBits > 0) { - fprintf(stderr,"vanity address: found %.10llx !\n",(unsigned long long)id.address().toInt()); - } - break; - } else { - fprintf(stderr,"vanity address: tried %.10llx looking for first %d bits of %.10llx\n",(unsigned long long)id.address().toInt(),vanityBits,(unsigned long long)(vanity << (40 - vanityBits))); - } - } - - std::string idser = id.toString(true); - if (argc >= 3) { - if (!OSUtils::writeFile(argv[2],idser)) { - std::cerr << "Error writing to " << argv[2] << std::endl; - return 1; - } else std::cout << argv[2] << " written" << std::endl; - if (argc >= 4) { - idser = id.toString(false); - if (!OSUtils::writeFile(argv[3],idser)) { - std::cerr << "Error writing to " << argv[3] << std::endl; - return 1; - } else std::cout << argv[3] << " written" << std::endl; - } - } else std::cout << idser << std::endl; - } - - // zerotier id-validate - else if (state.command == "id-validate") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier id-validate " << std::endl; - return 1; - } - Identity id = getIdFromArg(argv[2]); - if (!id) { - std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; - return 1; - } - if (!id.locallyValidate()) { - std::cerr << argv[2] << " FAILED validation." << std::endl; - return 1; - } else std::cout << argv[2] << "is a valid identity" << std::endl; - } - - // zerotier id-sign - else if (state.command == "id-sign") { - if(argc != 4) { - std::cerr << INVALID_ARGS_STR << "zerotier id-sign " << std::endl; - return 1; - } - Identity id = getIdFromArg(argv[2]); - if (!id) { - std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; - return 1; - } - if (!id.hasPrivate()) { - std::cerr << argv[2] << " does not contain a private key (must use private to sign)" << std::endl; - return 1; - } - std::string inf; - if (!OSUtils::readFile(argv[3],inf)) { - std::cerr << argv[3] << " is not readable" << std::endl; - return 1; - } - C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length()); - std::cout << Utils::hex(signature.data,(unsigned int)signature.size()) << std::endl; - } - - // zerotier id-verify - else if (state.command == "id-verify") { - if(argc != 4) { - std::cerr << INVALID_ARGS_STR << "zerotier id-verify " << std::endl; - return 1; - } - Identity id = getIdFromArg(argv[2]); - if (!id) { - std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; - return 1; - } - std::string inf; - if (!OSUtils::readFile(argv[3],inf)) { - std::cerr << argv[3] << " is not readable" << std::endl; - return 1; - } - std::string signature(Utils::unhex(argv[4])); - if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) { - std::cout << argv[3] << " signature valid" << std::endl; - } else { - std::cerr << argv[3] << " signature check FAILED" << std::endl; - return 1; - } - } - - // zerotier id-getpublic - else if (state.command == "id-getpublic") { - if(argc != 3) { - std::cerr << INVALID_ARGS_STR << "zerotier id-getpublic " << std::endl; - return 1; - } - Identity id = getIdFromArg(argv[2]); - if (!id) { - std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; - return 1; - } - std::cerr << id.toString(false) << std::endl; - } - // - else { - dumpHelp(); - return -1; - } - if(std::find(state.args.begin(), state.args.end(), "-verbose") != state.args.end()) - std::cout << "\n\nAPI response = " << std::get<1>(res) << std::endl; - curl_global_cleanup(); - return 0; -} -- cgit v1.2.3