|
@@ -25,6 +25,7 @@
|
|
#include <string.h>
|
|
#include <string.h>
|
|
|
|
|
|
#include "../node/Constants.hpp"
|
|
#include "../node/Constants.hpp"
|
|
|
|
+#include "../node/Identity.hpp"
|
|
#include "../version.h"
|
|
#include "../version.h"
|
|
#include "../osdep/OSUtils.hpp"
|
|
#include "../osdep/OSUtils.hpp"
|
|
#include "../ext/json/json.hpp"
|
|
#include "../ext/json/json.hpp"
|
|
@@ -44,6 +45,7 @@
|
|
#include <map>
|
|
#include <map>
|
|
#include <vector>
|
|
#include <vector>
|
|
#include <tuple>
|
|
#include <tuple>
|
|
|
|
+#include <regex>
|
|
|
|
|
|
#include <curl/curl.h>
|
|
#include <curl/curl.h>
|
|
|
|
|
|
@@ -53,10 +55,21 @@ using namespace ZeroTier;
|
|
#define ZT_CLI_FLAG_VERBOSE 'v'
|
|
#define ZT_CLI_FLAG_VERBOSE 'v'
|
|
#define ZT_CLI_FLAG_UNSAFE_SSL 'X'
|
|
#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
|
|
struct CLIState
|
|
{
|
|
{
|
|
std::string atname;
|
|
std::string atname;
|
|
std::string command;
|
|
std::string command;
|
|
|
|
+ std::string url;
|
|
|
|
+ std::map<std::string,std::string> reqHeaders;
|
|
std::vector<std::string> args;
|
|
std::vector<std::string> args;
|
|
std::map<char,std::string> opts;
|
|
std::map<char,std::string> opts;
|
|
json settings;
|
|
json settings;
|
|
@@ -64,6 +77,22 @@ struct CLIState
|
|
|
|
|
|
namespace {
|
|
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)
|
|
static std::string trimString(const std::string &s)
|
|
{
|
|
{
|
|
unsigned long end = (unsigned long)s.length();
|
|
unsigned long end = (unsigned long)s.length();
|
|
@@ -94,14 +123,31 @@ static inline std::string getSettingsFilePath()
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
-static bool saveSettings(const json &settings)
|
|
|
|
|
|
+static bool saveSettingsBackup(CLIState &state)
|
|
{
|
|
{
|
|
std::string sfp(getSettingsFilePath().c_str());
|
|
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);
|
|
OSUtils::lockDownFile(sfp.c_str(),false);
|
|
|
|
+ std::cout << OK_STR << "changes saved." << std::endl;
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
+ std::cout << FAIL_STR << "unable to write to " << sfp << std::endl;
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -115,17 +161,19 @@ static void dumpHelp()
|
|
std::cout << "Usage: zerotier [-option] [@name] <command> [<command options>]" << std::endl;
|
|
std::cout << "Usage: zerotier [-option] [@name] <command> [<command options>]" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Options:" << 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 << " -X - Do not check SSL certs (CAUTION!)" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "CLI Configuration Commands:" << 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-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-ls - List configured @things" << std::endl;
|
|
std::cout << " cli-rm @name - Remove a configured @thing" << 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-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 << " cli-add-central @name <url> <auth> - Add ZeroTier Central instance" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "ZeroTier One Service Commands:" << 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 << " ls - List currently joined networks" << std::endl;
|
|
std::cout << " join <network> [opt=value ...] - Join a network" << std::endl;
|
|
std::cout << " join <network> [opt=value ...] - Join a network" << std::endl;
|
|
std::cout << " leave <network> - Leave 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-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-show <network> [<address>] - Get network or member info" << std::endl;
|
|
std::cout << " net-auth <network> <address> - Authorize a member" << 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 << " net-set <path> <value> - See 'net-set help'" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Identity Commands:" << 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;
|
|
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;
|
|
std::string body;
|
|
char errbuf[CURL_ERROR_SIZE];
|
|
char errbuf[CURL_ERROR_SIZE];
|
|
char urlbuf[4096];
|
|
char urlbuf[4096];
|
|
|
|
|
|
- CURL *curl = curl_easy_init();
|
|
|
|
|
|
+ CURL *curl;
|
|
|
|
+ curl = curl_easy_init();
|
|
if (!curl) {
|
|
if (!curl) {
|
|
std::cerr << "FATAL: curl_easy_init() failed" << std::endl;
|
|
std::cerr << "FATAL: curl_easy_init() failed" << std::endl;
|
|
exit(-1);
|
|
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());
|
|
Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str());
|
|
curl_easy_setopt(curl,CURLOPT_URL,urlbuf);
|
|
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)
|
|
if (hdrs)
|
|
curl_easy_setopt(curl,CURLOPT_HTTPHEADER,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);
|
|
CURLcode res = curl_easy_perform(curl);
|
|
|
|
+
|
|
errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check
|
|
errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check
|
|
|
|
|
|
if (res != CURLE_OK)
|
|
if (res != CURLE_OK)
|
|
return std::make_tuple(-1,std::string(errbuf));
|
|
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);
|
|
curl_easy_cleanup(curl);
|
|
if (hdrs)
|
|
if (hdrs)
|
|
curl_slist_free_all(hdrs);
|
|
curl_slist_free_all(hdrs);
|
|
-
|
|
|
|
- return std::make_tuple(rc,body);
|
|
|
|
|
|
+ return std::make_tuple(response_code,body);
|
|
}
|
|
}
|
|
|
|
|
|
} // anonymous namespace
|
|
} // 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__
|
|
#ifdef __WINDOWS__
|
|
int _tmain(int argc, _TCHAR* argv[])
|
|
int _tmain(int argc, _TCHAR* argv[])
|
|
#else
|
|
#else
|
|
@@ -223,13 +366,14 @@ int main(int argc,char **argv)
|
|
#endif
|
|
#endif
|
|
|
|
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
-
|
|
|
|
CLIState state;
|
|
CLIState state;
|
|
|
|
+ std::string arg1, arg2, authToken;
|
|
|
|
|
|
for(int i=1;i<argc;++i) {
|
|
for(int i=1;i<argc;++i) {
|
|
- if ((i == 1)&&(argv[i][0] == '@')) {
|
|
|
|
|
|
+ if (argv[i][0] == '@') {
|
|
state.atname = argv[i];
|
|
state.atname = argv[i];
|
|
- } else if (state.command.length() == 0) {
|
|
|
|
|
|
+ }
|
|
|
|
+ else if (state.command.length() == 0) {
|
|
if (argv[i][0] == '-') {
|
|
if (argv[i][0] == '-') {
|
|
if (!argv[i][1]) {
|
|
if (!argv[i][1]) {
|
|
dumpHelp();
|
|
dumpHelp();
|
|
@@ -242,7 +386,8 @@ int main(int argc,char **argv)
|
|
} else {
|
|
} else {
|
|
state.command = argv[i];
|
|
state.command = argv[i];
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
state.args.push_back(std::string(argv[i]));
|
|
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 oneHome(OSUtils::platformDefaultHomePath());
|
|
- std::string authToken,portStr;
|
|
|
|
|
|
+ std::string portStr;
|
|
bool initSuccess = false;
|
|
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)) {
|
|
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);
|
|
portStr = trimString(portStr);
|
|
authToken = trimString(authToken);
|
|
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;
|
|
std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl;
|
|
exit(-1);
|
|
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")) {
|
|
if ((state.command.length() == 0)||(state.command == "help")) {
|
|
dumpHelp();
|
|
dumpHelp();
|
|
return -1;
|
|
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();
|
|
dumpHelp();
|
|
return -1;
|
|
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();
|
|
curl_global_cleanup();
|
|
-
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|