summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/README.md59
-rw-r--r--cli/zerotier.cpp718
2 files changed, 725 insertions, 52 deletions
diff --git a/cli/README.md b/cli/README.md
index dabbd302..595df07e 100644
--- a/cli/README.md
+++ b/cli/README.md
@@ -1,6 +1,57 @@
-ZeroTier Newer-Spiffier Command Line Interface
-======
+The new ZeroTier CLI!
+====
-This will be the future home of our new unified CLI for ZeroTier One, controllers, and Central (my.zerotier.com etc.).
+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/ <authtoken>`
+
+*Add a ZeroTier Central configuration:*
+
+ - `zerotier cli-add-central MyZTCentralConfigName https://my.zerotier.com/ <centralAPIAuthtoken>`
+
+*Set a default ZeroTier One instance:*
+
+ - `zerotier cli-set defaultOne MyLocalConfigName`
+
+*Set a default ZeroTier Central:*
+
+ - `zerotier cli-set defaultCentral MyZTCentralConfigName`
-IT IS NOT DONE AND DOES NOT WORK EVEN A LITTLE BIT. GO AWAY.
diff --git a/cli/zerotier.cpp b/cli/zerotier.cpp
index f9eec5d0..2b746ec2 100644
--- a/cli/zerotier.cpp
+++ b/cli/zerotier.cpp
@@ -25,6 +25,7 @@
#include <string.h>
#include "../node/Constants.hpp"
+#include "../node/Identity.hpp"
#include "../version.h"
#include "../osdep/OSUtils.hpp"
#include "../ext/json/json.hpp"
@@ -44,6 +45,7 @@
#include <map>
#include <vector>
#include <tuple>
+#include <regex>
#include <curl/curl.h>
@@ -53,10 +55,21 @@ 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<std::string,std::string> reqHeaders;
std::vector<std::string> args;
std::map<char,std::string> opts;
json settings;
@@ -64,6 +77,22 @@ struct CLIState
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();
@@ -94,14 +123,31 @@ static inline std::string getSettingsFilePath()
#endif
}
-static bool saveSettings(const json &settings)
+static bool saveSettingsBackup(CLIState &state)
{
std::string sfp(getSettingsFilePath().c_str());
- std::string buf(settings.dump(2));
- if (OSUtils::writeFile(sfp.c_str(),buf)) {
+ if(state.settings.find("generateBackupConfig") != state.settings.end()
+ && state.settings["generateBackupConfig"].get<std::string>() == "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;
}
@@ -115,17 +161,19 @@ static void dumpHelp()
std::cout << "Usage: zerotier [-option] [@name] <command> [<command options>]" << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
- std::cout << " -v - Verbose JSON output" << 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 <setting> <value> - Set a CLI option ('cli-set help')" << std::endl;
+ std::cout << " cli-unset <setting> <value> - 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 <url> <auth> - Add a ZeroTier service" << std::endl;
std::cout << " cli-add-central @name <url> <auth> - 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 <network> [opt=value ...] - Join a network" << std::endl;
std::cout << " leave <network> - Leave a network" << std::endl;
@@ -139,6 +187,7 @@ static void dumpHelp()
std::cout << " net-members <network> - List members of a network" << std::endl;
std::cout << " net-show <network> [<address>] - Get network or member info" << std::endl;
std::cout << " net-auth <network> <address> - Authorize a member" << std::endl;
+ std::cout << " net-unauth <network> <address> - De-authorize a member" << std::endl;
std::cout << " net-set <path> <value> - See 'net-set help'" << std::endl;
std::cout << std::endl;
std::cout << "Identity Commands:" << std::endl;
@@ -157,25 +206,19 @@ static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,
return totalSize;
}
-static std::tuple<int,std::string> GET(const CLIState &state,const std::map<std::string,std::string> &headers,const std::string &url)
+static std::tuple<int,std::string> REQUEST(int requestType, CLIState &state, const std::map<std::string,std::string> &headers, const std::string &postfield, const std::string &url)
{
std::string body;
char errbuf[CURL_ERROR_SIZE];
char urlbuf[4096];
- CURL *curl = curl_easy_init();
+ CURL *curl;
+ curl = curl_easy_init();
if (!curl) {
std::cerr << "FATAL: curl_easy_init() failed" << std::endl;
exit(-1);
}
- curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback);
- curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body);
- curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI");
- curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L);
- curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf);
- curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L);
-
Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str());
curl_easy_setopt(curl,CURLOPT_URL,urlbuf);
@@ -189,26 +232,126 @@ static std::tuple<int,std::string> GET(const CLIState &state,const std::map<std:
if (hdrs)
curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs);
- memset(errbuf,0,sizeof(errbuf));
+ //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));
- int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE);
+ 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(rc,body);
+ 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<std::string>() != "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<std::string>() != "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<std::string>().c_str() << std::endl;
+ configName = state.settings["defaultOne"].get<std::string>().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<std::string>().c_str() << std::endl;
+ configName = state.settings["defaultCentral"].get<std::string>().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<std::string>();
+ 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<int,std::string> 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<std::string>();
+ }
+ return "---";
+}
+
#ifdef __WINDOWS__
int _tmain(int argc, _TCHAR* argv[])
#else
@@ -223,13 +366,14 @@ int main(int argc,char **argv)
#endif
curl_global_init(CURL_GLOBAL_DEFAULT);
-
CLIState state;
+ std::string arg1, arg2, authToken;
for(int i=1;i<argc;++i) {
- if ((i == 1)&&(argv[i][0] == '@')) {
+ if (argv[i][0] == '@') {
state.atname = argv[i];
- } else if (state.command.length() == 0) {
+ }
+ else if (state.command.length() == 0) {
if (argv[i][0] == '-') {
if (!argv[i][1]) {
dumpHelp();
@@ -242,7 +386,8 @@ int main(int argc,char **argv)
} else {
state.command = argv[i];
}
- } else {
+ }
+ else {
state.args.push_back(std::string(argv[i]));
}
}
@@ -273,8 +418,9 @@ int main(int argc,char **argv)
};
std::string oneHome(OSUtils::platformDefaultHomePath());
- std::string authToken,portStr;
+ std::string portStr;
bool initSuccess = false;
+ std::string path = oneHome + ZT_PATH_SEPARATOR_S ;
if (OSUtils::readFile((oneHome + ZT_PATH_SEPARATOR_S + "authtoken.secret").c_str(),authToken)&&OSUtils::readFile((oneHome + ZT_PATH_SEPARATOR_S + "zerotier-one.port").c_str(),portStr)) {
portStr = trimString(portStr);
authToken = trimString(authToken);
@@ -286,7 +432,7 @@ int main(int argc,char **argv)
}
}
- if (!saveSettings(state.settings)) {
+ if (!saveSettings(state)) {
std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl;
exit(-1);
}
@@ -299,37 +445,513 @@ int main(int argc,char **argv)
}
}
+ // PRE-REQUEST SETUP
+
+ json result;
+ std::tuple<int,std::string> res;
+ std::string url = "";
+
+ // META
+
if ((state.command.length() == 0)||(state.command == "help")) {
dumpHelp();
return -1;
- } else if (state.command == "cli-set") {
- } else if (state.command == "cli-ls") {
- } else if (state.command == "cli-rm") {
- } else if (state.command == "cli-add-zt") {
- } else if (state.command == "cli-add-central") {
- } else if (state.command == "ls") {
- } else if (state.command == "join") {
- } else if (state.command == "leave") {
- } else if (state.command == "peers") {
- } else if (state.command == "show") {
- } else if (state.command == "net-create") {
- } else if (state.command == "net-rm") {
- } else if (state.command == "net-ls") {
- } else if (state.command == "net-members") {
- } else if (state.command == "net-show") {
- } else if (state.command == "net-auth") {
- } else if (state.command == "net-set") {
- } else if (state.command == "id-generate") {
- } else if (state.command == "id-validate") {
- } else if (state.command == "id-sign") {
- } else if (state.command == "id-verify") {
- } else if (state.command == "id-getpublic") {
- } else {
+ }
+
+ // zerotier version
+ else if (state.command == "v" || state.command == "version") {
+ std::cout << getLocalVersion(state) << std::endl;
+ return 1;
+ }
+
+ // zerotier cli-set <setting> <value>
+ else if (state.command == "cli-set") {
+ if(argc != 4) {
+ std::cerr << INVALID_ARGS_STR << "zerotier cli-set <setting> <value>" << 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 <setting>
+ else if (state.command == "cli-unset") {
+ if(argc != 3) {
+ std::cerr << INVALID_ARGS_STR << "zerotier cli-unset <setting>" << 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 <shortname> <url> <auth>
+ // 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 <shortname> <url> <authToken>" << 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 <shortname> <url> <auth>
+ // 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 <shortname> <url> <authToken>" << 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 <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>" << std::endl;
+ auto j = json::parse(std::get<1>(res).c_str());
+ if (j.type() == json::value_t::array) {
+ for(int i=0;i<j.size();i++){
+ std::string nwid = j[i]["nwid"].get<std::string>();
+ std::string name = j[i]["name"].get<std::string>();
+ std::string mac = j[i]["mac"].get<std::string>();
+ std::string status = j[i]["status"].get<std::string>();
+ std::string type = j[i]["type"].get<std::string>();
+ std::string addrs;
+ for(int m=0; m<j[i]["assignedAddresses"].size(); m++) {
+ addrs += j[i]["assignedAddresses"][m].get<std::string>() + " ";
+ }
+ std::string dev = j[i]["portDeviceName"].get<std::string>();
+ std::cout << "listnetworks " << nwid << " " << name << " " << mac << " " << status << " " << type << " " << dev << " " << addrs << std::endl;
+ }
+ }
+ }
+ }
+
+ // zerotier join <nwid> --- joins a network
+ else if (state.command == "join") {
+ if(argc != 3) {
+ std::cerr << INVALID_ARGS_STR << "zerotier join <nwid>" << 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 <nwid> --- leaves a network
+ else if (state.command == "leave") {
+ if(argc != 3) {
+ std::cerr << INVALID_ARGS_STR << "zerotier leave <nwid>" << 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<result.size(); i++) {
+ std::cout << result[i]["address"] << " " << result[i]["role"] << std::endl;
+ }
+ }
+ }
+
+ // zerotier show --- display status of local instance
+ else if (state.command == "show" || state.command == "status") {
+ if(argc != 2) {
+ std::cerr << INVALID_ARGS_STR << "zerotier show" << std::endl;
+ return 1;
+ }
+ checkForThing(state,"one",true);
+ res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status");
+ if(std::get<0>(res) == 200) {
+ result = json::parse(std::get<1>(res));
+ std::string status_str = result["online"].get<bool>() ? "ONLINE" : "OFFLINE";
+ std::cout << "info " << result["address"].get<std::string>()
+ << " " << status_str << " " << result["version"].get<std::string>() << 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::string>() << std::endl;
+ }
+ }
+
+ // zerotier @thing net-rm <nwid> --- 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 <nwid>" << 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 <nwid>" << 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<result.size(); m++) {
+ std::cout << "network " << result[m]["id"].get<std::string>() << std::endl;
+ }
+ }
+ }
+
+ // zerotier @thing net-members <nwid> --- 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 <nwid>" << 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 <nwid>" << 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 <nwid> <devID> --- 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 <nwid> <devID>" << 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 <nwid> <devID>" << 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<result["config"]["ipAssignments"].size();m++) {
+ std::cout << "\t" << result["config"]["ipAssignments"][m].get<std::string>() << std::endl;
+ }
+ }
+ }
+ }
+
+ // zerotier @thing net-auth <nwid> <devID> --- 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 <nwid> <devID>" << 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 <nwid> <devID>" << 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 <nwid> <devID>
+ else if (state.command == "net-unauth") {
+ if(argc > 5 || (argc == 5 && !state.atname.length())) {
+ std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-unauth <nwid> <devID>" << 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 <nwid> <devID>" << 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 [<vanity prefix>]
+ else if (state.command == "id-generate") {
+ if(argc != 3) {
+ std::cerr << INVALID_ARGS_STR << "zerotier id-generate [<vanity prefix>]" << 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 <identity>
+ else if (state.command == "id-validate") {
+ if(argc != 3) {
+ std::cerr << INVALID_ARGS_STR << "zerotier id-validate <identity>" << 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 <identity> <file>
+ else if (state.command == "id-sign") {
+ if(argc != 4) {
+ std::cerr << INVALID_ARGS_STR << "zerotier id-sign <identity> <file>" << 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 <secret> <file> <sig>
+ else if (state.command == "id-verify") {
+ if(argc != 4) {
+ std::cerr << INVALID_ARGS_STR << "zerotier id-verify <secret> <file> <sig>" << 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 <secret>
+ else if (state.command == "id-getpublic") {
+ if(argc != 3) {
+ std::cerr << INVALID_ARGS_STR << "zerotier id-getpublic <secret>" << 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;
}