From 77bd8aacd1bb17cd9c2213ffb4dd455330e587e0 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 2 Aug 2016 13:50:09 -0700 Subject: merge cli from edge --- cli/zerotier.cpp | 718 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 670 insertions(+), 48 deletions(-) (limited to 'cli/zerotier.cpp') 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 #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 #include #include +#include #include @@ -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 reqHeaders; std::vector args; std::map 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() == "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] []" << 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 - 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; @@ -139,6 +187,7 @@ static void dumpHelp() 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; @@ -157,25 +206,19 @@ static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb, return totalSize; } -static std::tuple GET(const CLIState &state,const std::map &headers,const std::string &url) +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_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 GET(const CLIState &state,const std::map 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() != "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 @@ -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 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 + 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