zerotier.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. /*
  2. * ZeroTier One - Network Virtualization Everywhere
  3. * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. // Note: unlike the rest of ZT's code base, this requires C++11 due to
  19. // the JSON library it uses and other things.
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <stdint.h>
  23. #include <string.h>
  24. #include "../node/Constants.hpp"
  25. #include "../node/Identity.hpp"
  26. #include "../version.h"
  27. #include "../osdep/OSUtils.hpp"
  28. #include "../ext/offbase/json/json.hpp"
  29. #ifdef __WINDOWS__
  30. #include <WinSock2.h>
  31. #include <windows.h>
  32. #include <tchar.h>
  33. #include <wchar.h>
  34. #else
  35. #include <ctype.h>
  36. #include <unistd.h>
  37. #endif
  38. #include <iostream>
  39. #include <string>
  40. #include <map>
  41. #include <vector>
  42. #include <tuple>
  43. #include <regex>
  44. #include <curl/curl.h>
  45. using json = nlohmann::json;
  46. using namespace ZeroTier;
  47. #define ZT_CLI_FLAG_VERBOSE 'v'
  48. #define ZT_CLI_FLAG_UNSAFE_SSL 'X'
  49. #define REQ_GET 0
  50. #define REQ_POST 1
  51. #define REQ_DEL 2
  52. #define OK_STR "[OK ]: "
  53. #define FAIL_STR "[FAIL]: "
  54. #define WARN_STR "[WARN]: "
  55. #define INVALID_ARGS_STR "Invalid args. Usage: "
  56. struct CLIState
  57. {
  58. std::string atname;
  59. std::string command;
  60. std::string url;
  61. std::map<std::string,std::string> reqHeaders;
  62. std::vector<std::string> args;
  63. std::map<char,std::string> opts;
  64. json settings;
  65. };
  66. namespace {
  67. static Identity getIdFromArg(char *arg)
  68. {
  69. Identity id;
  70. if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line
  71. if (id.fromString(arg))
  72. return id;
  73. } else { // identity is to be read from a file
  74. std::string idser;
  75. if (OSUtils::readFile(arg,idser)) {
  76. if (id.fromString(idser))
  77. return id;
  78. }
  79. }
  80. return Identity();
  81. }
  82. static std::string trimString(const std::string &s)
  83. {
  84. unsigned long end = (unsigned long)s.length();
  85. while (end) {
  86. char c = s[end - 1];
  87. if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t'))
  88. --end;
  89. else break;
  90. }
  91. unsigned long start = 0;
  92. while (start < end) {
  93. char c = s[start];
  94. if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t'))
  95. ++start;
  96. else break;
  97. }
  98. return s.substr(start,end - start);
  99. }
  100. static inline std::string getSettingsFilePath()
  101. {
  102. #ifdef __WINDOWS__
  103. #else
  104. const char *home = getenv("HOME");
  105. if (!home)
  106. home = "/";
  107. return (std::string(home) + "/.zerotierCliSettings");
  108. #endif
  109. }
  110. static bool saveSettingsBackup(CLIState &state)
  111. {
  112. std::string sfp(getSettingsFilePath().c_str());
  113. if(state.settings.find("generateBackupConfig") != state.settings.end()
  114. && state.settings["generateBackupConfig"].get<std::string>() == "true") {
  115. std::string backup_file = getSettingsFilePath() + ".bak";
  116. if(!OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) {
  117. OSUtils::lockDownFile(sfp.c_str(),false);
  118. std::cout << WARN_STR << "unable to write backup config file" << std::endl;
  119. return false;
  120. }
  121. return true;
  122. }
  123. return false;
  124. }
  125. static bool saveSettings(CLIState &state)
  126. {
  127. std::string sfp(getSettingsFilePath().c_str());
  128. if(OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) {
  129. OSUtils::lockDownFile(sfp.c_str(),false);
  130. std::cout << OK_STR << "changes saved." << std::endl;
  131. return true;
  132. }
  133. std::cout << FAIL_STR << "unable to write to " << sfp << std::endl;
  134. return false;
  135. }
  136. static void dumpHelp()
  137. {
  138. std::cout << "ZeroTier Newer-Spiffier CLI " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << std::endl;
  139. std::cout << "(c)2016 ZeroTier, Inc. / Licensed under the GNU GPL v3" << std::endl;
  140. std::cout << std::endl;
  141. std::cout << "Configuration path: " << getSettingsFilePath() << std::endl;
  142. std::cout << std::endl;
  143. std::cout << "Usage: zerotier [-option] [@name] <command> [<command options>]" << std::endl;
  144. std::cout << std::endl;
  145. std::cout << "Options:" << std::endl;
  146. std::cout << " -verbose - Verbose JSON output" << std::endl;
  147. std::cout << " -X - Do not check SSL certs (CAUTION!)" << std::endl;
  148. std::cout << std::endl;
  149. std::cout << "CLI Configuration Commands:" << std::endl;
  150. std::cout << " cli-set <setting> <value> - Set a CLI option ('cli-set help')" << std::endl;
  151. std::cout << " cli-unset <setting> <value> - Un-sets a CLI option ('cli-unset help')" << std::endl;
  152. std::cout << " cli-ls - List configured @things" << std::endl;
  153. std::cout << " cli-rm @name - Remove a configured @thing" << std::endl;
  154. std::cout << " cli-add-zt @name <url> <auth> - Add a ZeroTier service" << std::endl;
  155. std::cout << " cli-add-central @name <url> <auth> - Add ZeroTier Central instance" << std::endl;
  156. std::cout << std::endl;
  157. std::cout << "ZeroTier One Service Commands:" << std::endl;
  158. std::cout << " -v / -version - Displays default local instance's version'" << std::endl;
  159. std::cout << " ls - List currently joined networks" << std::endl;
  160. std::cout << " join <network> [opt=value ...] - Join a network" << std::endl;
  161. std::cout << " leave <network> - Leave a network" << std::endl;
  162. std::cout << " peers - List ZeroTier VL1 peers" << std::endl;
  163. std::cout << " show [<network/peer address>] - Get info about self or object" << std::endl;
  164. std::cout << std::endl;
  165. std::cout << "Network Controller Commands:" << std::endl;
  166. std::cout << " net-create - Create a new network" << std::endl;
  167. std::cout << " net-rm <network> - Delete a network (CAUTION!)" << std::endl;
  168. std::cout << " net-ls - List administered networks" << std::endl;
  169. std::cout << " net-members <network> - List members of a network" << std::endl;
  170. std::cout << " net-show <network> [<address>] - Get network or member info" << std::endl;
  171. std::cout << " net-auth <network> <address> - Authorize a member" << std::endl;
  172. std::cout << " net-unauth <network> <address> - De-authorize a member" << std::endl;
  173. std::cout << " net-set <path> <value> - See 'net-set help'" << std::endl;
  174. std::cout << std::endl;
  175. std::cout << "Identity Commands:" << std::endl;
  176. std::cout << " id-generate [<vanity prefix>] - Generate a ZeroTier identity" << std::endl;
  177. std::cout << " id-validate <identity> - Locally validate an identity" << std::endl;
  178. std::cout << " id-sign <identity> <file> - Sign a file" << std::endl;
  179. std::cout << " id-verify <secret> <file> <sig> - Verify a file's signature" << std::endl;
  180. std::cout << " id-getpublic <secret> - Get full identity's public portion" << std::endl;
  181. std::cout << std::endl;
  182. }
  183. static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,void *stdstring)
  184. {
  185. size_t totalSize = size * nmemb;
  186. reinterpret_cast<std::string *>(stdstring)->append((const char *)contents,totalSize);
  187. return totalSize;
  188. }
  189. 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)
  190. {
  191. std::string body;
  192. char errbuf[CURL_ERROR_SIZE];
  193. char urlbuf[4096];
  194. CURL *curl;
  195. curl = curl_easy_init();
  196. if (!curl) {
  197. std::cerr << "FATAL: curl_easy_init() failed" << std::endl;
  198. exit(-1);
  199. }
  200. Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str());
  201. curl_easy_setopt(curl,CURLOPT_URL,urlbuf);
  202. struct curl_slist *hdrs = (struct curl_slist *)0;
  203. for(std::map<std::string,std::string>::const_iterator i(headers.begin());i!=headers.end();++i) {
  204. std::string htmp(i->first);
  205. htmp.append(": ");
  206. htmp.append(i->second);
  207. hdrs = curl_slist_append(hdrs,htmp.c_str());
  208. }
  209. if (hdrs)
  210. curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs);
  211. //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
  212. curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body);
  213. curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback);
  214. if(std::find(state.args.begin(), state.args.end(), "-X") == state.args.end())
  215. curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L);
  216. if(requestType == REQ_POST) {
  217. curl_easy_setopt(curl, CURLOPT_POST, 1);
  218. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfield.c_str());
  219. }
  220. if(requestType == REQ_DEL)
  221. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
  222. if(requestType == REQ_GET) {
  223. curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf);
  224. curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L);
  225. }
  226. curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI");
  227. CURLcode res = curl_easy_perform(curl);
  228. errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check
  229. if (res != CURLE_OK)
  230. return std::make_tuple(-1,std::string(errbuf));
  231. long response_code;
  232. int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &response_code);
  233. if(response_code == 401) { std::cout << FAIL_STR << response_code << "Unauthorized." << std::endl; exit(0); }
  234. else if(response_code == 403) { std::cout << FAIL_STR << response_code << "Forbidden." << std::endl; exit(0); }
  235. else if(response_code == 404) { std::cout << FAIL_STR << response_code << "Not found." << std::endl; exit(0); }
  236. else if(response_code == 408) { std::cout << FAIL_STR << response_code << "Request timed out." << std::endl; exit(0); }
  237. else if(response_code != 200) { std::cout << FAIL_STR << response_code << "Unable to process request." << std::endl; exit(0); }
  238. curl_easy_cleanup(curl);
  239. if (hdrs)
  240. curl_slist_free_all(hdrs);
  241. return std::make_tuple(response_code,body);
  242. }
  243. } // anonymous namespace
  244. //////////////////////////////////////////////////////////////////////////////
  245. // Check for user-specified @thing config
  246. // Make sure it @thing makes sense
  247. // Apply appropriate request headers
  248. static void checkForThing(CLIState &state, std::string thingType, bool warnNoThingProvided)
  249. {
  250. std::string configName;
  251. if(state.atname.length()) {
  252. configName = state.atname.erase(0,1);
  253. // make sure specified @thing makes sense in the context of the command
  254. if(thingType == "one" && state.settings["things"][configName]["type"].get<std::string>() != "one") {
  255. std::cout << FAIL_STR << "A ZeroTier Central config was specified for a ZeroTier One command." << std::endl;
  256. exit(0);
  257. }
  258. if(thingType == "central" && state.settings["things"][configName]["type"].get<std::string>() != "central") {
  259. std::cout << FAIL_STR << "A ZeroTier One config was specified for a ZeroTier Central command." << std::endl;
  260. exit(0);
  261. }
  262. }
  263. else { // no @thing specified, check for defaults depending on type
  264. if(thingType == "one") {
  265. if(state.settings.find("defaultOne") != state.settings.end()) {
  266. if(warnNoThingProvided)
  267. std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier One command: " << state.settings["defaultOne"].get<std::string>().c_str() << std::endl;
  268. configName = state.settings["defaultOne"].get<std::string>().erase(0,1); // get default
  269. }
  270. else {
  271. std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl;
  272. std::cout << "HELP: To set a default: zerotier cli-set defaultOne @my_default_thing" << std::endl;
  273. exit(0);
  274. }
  275. }
  276. if(thingType == "central") {
  277. if(state.settings.find("defaultCentral") != state.settings.end()) {
  278. if(warnNoThingProvided)
  279. std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier Central command: " << state.settings["defaultCentral"].get<std::string>().c_str() << std::endl;
  280. configName = state.settings["defaultCentral"].get<std::string>().erase(0,1); // get default
  281. }
  282. else {
  283. std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl;
  284. std::cout << "HELP: To set a default: zerotier cli-set defaultCentral @my_default_thing" << std::endl;
  285. exit(0);
  286. }
  287. }
  288. }
  289. // Apply headers
  290. if(thingType == "one") {
  291. state.reqHeaders["X-ZT1-Auth"] = state.settings["things"][configName]["auth"];
  292. }
  293. if(thingType == "central"){
  294. state.reqHeaders["Content-Type"] = "application/json";
  295. state.reqHeaders["Authorization"] = "Bearer " + state.settings["things"][configName]["auth"].get<std::string>();
  296. state.reqHeaders["Accept"] = "application/json";
  297. }
  298. state.url = state.settings["things"][configName]["url"];
  299. }
  300. static bool checkURL(std::string url)
  301. {
  302. // TODO
  303. return true;
  304. }
  305. static std::string getLocalVersion(CLIState &state)
  306. {
  307. json result;
  308. std::tuple<int,std::string> res;
  309. checkForThing(state,"one",false);
  310. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status");
  311. if(std::get<0>(res) == 200) {
  312. result = json::parse(std::get<1>(res));
  313. return result["version"].get<std::string>();
  314. }
  315. return "---";
  316. }
  317. #ifdef __WINDOWS__
  318. int _tmain(int argc, _TCHAR* argv[])
  319. #else
  320. int main(int argc,char **argv)
  321. #endif
  322. {
  323. #ifdef __WINDOWS__
  324. {
  325. WSADATA wsaData;
  326. WSAStartup(MAKEWORD(2,2),&wsaData);
  327. }
  328. #endif
  329. curl_global_init(CURL_GLOBAL_DEFAULT);
  330. CLIState state;
  331. std::string arg1, arg2, authToken;
  332. for(int i=1;i<argc;++i) {
  333. if (argv[i][0] == '@') {
  334. state.atname = argv[i];
  335. }
  336. else if (state.command.length() == 0) {
  337. if (argv[i][0] == '-') {
  338. if (!argv[i][1]) {
  339. dumpHelp();
  340. return -1;
  341. } else if (argv[i][2]) {
  342. state.opts[argv[i][1]] = argv[i] + 2;
  343. } else {
  344. state.opts[argv[i][1]] = "";
  345. }
  346. } else {
  347. state.command = argv[i];
  348. }
  349. }
  350. else {
  351. state.args.push_back(std::string(argv[i]));
  352. }
  353. }
  354. {
  355. std::string buf;
  356. if (OSUtils::readFile(getSettingsFilePath().c_str(),buf))
  357. state.settings = json::parse(buf);
  358. if (state.settings.empty()) {
  359. // Default settings
  360. state.settings = {
  361. { "configVersion", 1 },
  362. { "things", {
  363. { "my.zerotier.com", {
  364. { "type", "central" },
  365. { "url", "https://my.zerotier.com/" },
  366. { "auth", "" }
  367. }},
  368. { "local", {
  369. { "type", "one" },
  370. { "url", "" },
  371. { "auth", "" }
  372. }}
  373. }},
  374. { "defaultController", "@my.zerotier.com" },
  375. { "defaultOne", "@local" }
  376. };
  377. std::string oneHome(OSUtils::platformDefaultHomePath());
  378. std::string portStr;
  379. bool initSuccess = false;
  380. std::string path = oneHome + ZT_PATH_SEPARATOR_S ;
  381. 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)) {
  382. portStr = trimString(portStr);
  383. authToken = trimString(authToken);
  384. int port = Utils::strToInt(portStr.c_str());
  385. if (((port > 0)&&(port < 65536))&&(authToken.length() > 0)) {
  386. state.settings["things"]["local"]["url"] = (std::string("http://127.0.0.1:") + portStr + "/");
  387. state.settings["things"]["local"]["auth"] = authToken;
  388. initSuccess = true;
  389. }
  390. }
  391. if (!saveSettings(state)) {
  392. std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl;
  393. exit(-1);
  394. }
  395. if (initSuccess) {
  396. std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << std::endl;
  397. } else {
  398. std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << " but could not auto-init local ZeroTier One service config from " << oneHome << " -- you will need to set local service URL and port manually if you want to control a local instance of ZeroTier One. (This happens if you are not root/administrator.)" << std::endl;
  399. }
  400. }
  401. }
  402. // PRE-REQUEST SETUP
  403. json result;
  404. std::tuple<int,std::string> res;
  405. std::string url = "";
  406. // META
  407. if ((state.command.length() == 0)||(state.command == "help")) {
  408. dumpHelp();
  409. return -1;
  410. }
  411. // zerotier version
  412. else if (state.command == "v" || state.command == "version") {
  413. std::cout << getLocalVersion(state) << std::endl;
  414. return 1;
  415. }
  416. // zerotier cli-set <setting> <value>
  417. else if (state.command == "cli-set") {
  418. if(argc != 4) {
  419. std::cerr << INVALID_ARGS_STR << "zerotier cli-set <setting> <value>" << std::endl;
  420. return 1;
  421. }
  422. std::string settingName, settingValue;
  423. if(state.atname.length()) { // User provided @thing erroneously, we will ignore it and adjust argument indices
  424. settingName = argv[3];
  425. settingValue = argv[4];
  426. }
  427. else {
  428. settingName = argv[2];
  429. settingValue = argv[3];
  430. }
  431. saveSettingsBackup(state);
  432. state.settings[settingName] = settingValue; // changes
  433. saveSettings(state);
  434. }
  435. // zerotier cli-unset <setting>
  436. else if (state.command == "cli-unset") {
  437. if(argc != 3) {
  438. std::cerr << INVALID_ARGS_STR << "zerotier cli-unset <setting>" << std::endl;
  439. return 1;
  440. }
  441. std::string settingName;
  442. if(state.atname.length()) // User provided @thing erroneously, we will ignore it and adjust argument indices
  443. settingName = argv[3];
  444. else
  445. settingName = argv[2];
  446. saveSettingsBackup(state);
  447. state.settings.erase(settingName); // changes
  448. saveSettings(state);
  449. }
  450. // zerotier @thing_to_remove cli-rm --- removes the configuration
  451. else if (state.command == "cli-rm") {
  452. if(argc != 3) {
  453. std::cerr << INVALID_ARGS_STR << "zerotier cli-rm <@thing>" << std::endl;
  454. return 1;
  455. }
  456. if(state.settings["things"].find(state.atname) != state.settings["things"].end()) {
  457. if(state.settings["defaultOne"] == state.atname) {
  458. std::cout << "WARNING: The config you're trying to remove is currently set as your default. Set a new default first!" << std::endl;
  459. std::cout << " | Usage: zerotier set defaultOne @your_other_thing" << std::endl;
  460. }
  461. else {
  462. state.settings["things"].erase(state.atname.c_str());
  463. saveSettings(state);
  464. }
  465. }
  466. }
  467. // zerotier cli-add-zt <shortname> <url> <auth>
  468. // TODO: Check for malformed urls/auth
  469. else if (state.command == "cli-add-zt") {
  470. if(argc != 5) {
  471. std::cerr << INVALID_ARGS_STR << "zerotier cli-add-zt <shortname> <url> <authToken>" << std::endl;
  472. return 1;
  473. }
  474. std::string thing_name = argv[2], url = argv[3], auth = argv[4];
  475. if(!checkURL(url)) {
  476. std::cout << FAIL_STR << "Malformed URL" << std::endl;
  477. return 1;
  478. }
  479. if(state.settings.find(thing_name) != state.settings.end()) {
  480. std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str()
  481. << " already exists. Choose another name or rename the old @thing" << std::endl;
  482. std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl;
  483. }
  484. else {
  485. result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "one" + "\", \"url\": \"" + url + "\" }");
  486. saveSettingsBackup(state);
  487. // TODO: Handle cases where user may or may not prepend an @
  488. state.settings["things"][thing_name] = result; // changes
  489. saveSettings(state);
  490. }
  491. }
  492. // zerotier cli-add-central <shortname> <url> <auth>
  493. // TODO: Check for malformed urls/auth
  494. else if (state.command == "cli-add-central") {
  495. if(argc != 5) {
  496. std::cerr << INVALID_ARGS_STR << "zerotier cli-add-central <shortname> <url> <authToken>" << std::endl;
  497. return 1;
  498. }
  499. std::string thing_name = argv[2], url = argv[3], auth = argv[4];
  500. if(!checkURL(url)) {
  501. std::cout << FAIL_STR << "Malformed URL" << std::endl;
  502. return 1;
  503. }
  504. if(state.settings.find(thing_name) != state.settings.end()) {
  505. std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str()
  506. << " already exists. Choose another name or rename the old @thing" << std::endl;
  507. std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl;
  508. }
  509. else {
  510. result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "central" + "\", \"url\": \"" + url + "\" }");
  511. saveSettingsBackup(state);
  512. // TODO: Handle cases where user may or may not prepend an @
  513. state.settings["things"]["@" + thing_name] = result; // changes
  514. saveSettings(state);
  515. }
  516. }
  517. // ONE SERVICE
  518. // zerotier ls --- display all networks currently joined
  519. else if (state.command == "ls" || state.command == "listnetworks") {
  520. if(argc != 2) {
  521. std::cerr << INVALID_ARGS_STR << "zerotier ls" << std::endl;
  522. return 1;
  523. }
  524. checkForThing(state,"one",true);
  525. url = state.url + "network";
  526. res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url);
  527. if(std::get<0>(res) == 200) {
  528. std::cout << "listnetworks <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>" << std::endl;
  529. auto j = json::parse(std::get<1>(res).c_str());
  530. if (j.type() == json::value_t::array) {
  531. for(int i=0;i<j.size();i++){
  532. std::string nwid = j[i]["nwid"].get<std::string>();
  533. std::string name = j[i]["name"].get<std::string>();
  534. std::string mac = j[i]["mac"].get<std::string>();
  535. std::string status = j[i]["status"].get<std::string>();
  536. std::string type = j[i]["type"].get<std::string>();
  537. std::string addrs;
  538. for(int m=0; m<j[i]["assignedAddresses"].size(); m++) {
  539. addrs += j[i]["assignedAddresses"][m].get<std::string>() + " ";
  540. }
  541. std::string dev = j[i]["portDeviceName"].get<std::string>();
  542. std::cout << "listnetworks " << nwid << " " << name << " " << mac << " " << status << " " << type << " " << dev << " " << addrs << std::endl;
  543. }
  544. }
  545. }
  546. }
  547. // zerotier join <nwid> --- joins a network
  548. else if (state.command == "join") {
  549. if(argc != 3) {
  550. std::cerr << INVALID_ARGS_STR << "zerotier join <nwid>" << std::endl;
  551. return 1;
  552. }
  553. checkForThing(state,"one",true);
  554. res = REQUEST(REQ_POST,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]);
  555. if(std::get<0>(res) == 200) {
  556. std::cout << OK_STR << "connected to " << state.args[0] << std::endl;
  557. }
  558. }
  559. // zerotier leave <nwid> --- leaves a network
  560. else if (state.command == "leave") {
  561. if(argc != 3) {
  562. std::cerr << INVALID_ARGS_STR << "zerotier leave <nwid>" << std::endl;
  563. return 1;
  564. }
  565. checkForThing(state,"one",true);
  566. res = REQUEST(REQ_DEL,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]);
  567. if(std::get<0>(res) == 200) {
  568. std::cout << OK_STR << "disconnected from " << state.args[0] << std::endl;
  569. }
  570. }
  571. // zerotier peers --- display address and role of all peers
  572. else if (state.command == "peers") {
  573. if(argc != 2) {
  574. std::cerr << INVALID_ARGS_STR << "zerotier peers" << std::endl;
  575. return 1;
  576. }
  577. checkForThing(state,"one",true);
  578. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/peer");
  579. if(std::get<0>(res) == 200) {
  580. json result = json::parse(std::get<1>(res));
  581. for(int i=0; i<result.size(); i++) {
  582. std::cout << result[i]["address"] << " " << result[i]["role"] << std::endl;
  583. }
  584. }
  585. }
  586. // zerotier show --- display status of local instance
  587. else if (state.command == "show" || state.command == "status") {
  588. if(argc != 2) {
  589. std::cerr << INVALID_ARGS_STR << "zerotier show" << std::endl;
  590. return 1;
  591. }
  592. checkForThing(state,"one",true);
  593. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status");
  594. if(std::get<0>(res) == 200) {
  595. result = json::parse(std::get<1>(res));
  596. std::string status_str = result["online"].get<bool>() ? "ONLINE" : "OFFLINE";
  597. std::cout << "info " << result["address"].get<std::string>()
  598. << " " << status_str << " " << result["version"].get<std::string>() << std::endl;
  599. }
  600. }
  601. // REMOTE
  602. // zerotier @thing net-create --- creates a new network
  603. else if (state.command == "net-create") {
  604. if(argc > 3 || (argc == 3 && !state.atname.length())) {
  605. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-create" << std::endl;
  606. return 1;
  607. }
  608. checkForThing(state,"central",true);
  609. res = REQUEST(REQ_POST,state,state.reqHeaders,"",state.url + "api/network");
  610. if(std::get<0>(res) == 200) {
  611. json result = json::parse(std::get<1>(res));
  612. std::cout << OK_STR << "created network " << result["config"]["nwid"].get<std::string>() << std::endl;
  613. }
  614. }
  615. // zerotier @thing net-rm <nwid> --- deletes a network
  616. else if (state.command == "net-rm") {
  617. if(argc > 4 || (argc == 4 && !state.atname.length())) {
  618. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-rm <nwid>" << std::endl;
  619. return 1;
  620. }
  621. checkForThing(state,"central",true);
  622. if(!state.args.size()) {
  623. std::cout << "Argument error: No network specified." << std::endl;
  624. std::cout << " | Usage: zerotier net-rm <nwid>" << std::endl;
  625. }
  626. else {
  627. std::string nwid = state.args[0];
  628. res = REQUEST(REQ_DEL,state,state.reqHeaders,"",state.url + "api/network/" + nwid);
  629. if(std::get<0>(res) == 200) {
  630. std::cout << "deleted network " << nwid << std::endl;
  631. }
  632. }
  633. }
  634. // zerotier @thing net-ls --- lists all networks
  635. else if (state.command == "net-ls") {
  636. if(argc > 3 || (argc == 3 && !state.atname.length())) {
  637. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-ls" << std::endl;
  638. return 1;
  639. }
  640. checkForThing(state,"central",true);
  641. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network");
  642. if(std::get<0>(res) == 200) {
  643. json result = json::parse(std::get<1>(res));
  644. for(int m=0;m<result.size(); m++) {
  645. std::cout << "network " << result[m]["id"].get<std::string>() << std::endl;
  646. }
  647. }
  648. }
  649. // zerotier @thing net-members <nwid> --- show all members of a network
  650. else if (state.command == "net-members") {
  651. if(argc > 4 || (argc == 4 && !state.atname.length())) {
  652. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-members <nwid>" << std::endl;
  653. return 1;
  654. }
  655. checkForThing(state,"central",true);
  656. if(!state.args.size()) {
  657. std::cout << FAIL_STR << "Argument error: No network specified." << std::endl;
  658. std::cout << " | Usage: zerotier net-members <nwid>" << std::endl;
  659. }
  660. else {
  661. std::string nwid = state.args[0];
  662. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member");
  663. json result = json::parse(std::get<1>(res));
  664. std::cout << "Members of " << nwid << ":" << std::endl;
  665. for (json::iterator it = result.begin(); it != result.end(); ++it) {
  666. std::cout << it.key() << std::endl;
  667. }
  668. }
  669. }
  670. // zerotier @thing net-show <nwid> <devID> --- show info about a device on a specific network
  671. else if (state.command == "net-show") {
  672. if(argc > 5 || (argc == 5 && !state.atname.length())) {
  673. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-show <nwid> <devID>" << std::endl;
  674. return 1;
  675. }
  676. checkForThing(state,"central",true);
  677. if(state.args.size() < 2) {
  678. std::cout << FAIL_STR << "Argument error: Too few arguments." << std::endl;
  679. std::cout << " | Usage: zerotier net-show <nwid> <devID>" << std::endl;
  680. }
  681. else {
  682. std::string nwid = state.args[0];
  683. std::string devid = state.args[1];
  684. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid);
  685. // TODO: More info, what would we like to show exactly?
  686. if(std::get<0>(res) == 200) {
  687. json result = json::parse(std::get<1>(res));
  688. std::cout << "Assigned IP: " << std::endl;
  689. for(int m=0; m<result["config"]["ipAssignments"].size();m++) {
  690. std::cout << "\t" << result["config"]["ipAssignments"][m].get<std::string>() << std::endl;
  691. }
  692. }
  693. }
  694. }
  695. // zerotier @thing net-auth <nwid> <devID> --- authorize a device on a network
  696. else if (state.command == "net-auth") {
  697. if(argc > 5 || (argc == 5 && !state.atname.length())) {
  698. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-auth <nwid> <devID>" << std::endl;
  699. return 1;
  700. }
  701. checkForThing(state,"central",true);
  702. if(state.args.size() != 2) {
  703. std::cout << FAIL_STR << "Argument error: Network and/or device ID not specified." << std::endl;
  704. std::cout << " | Usage: zerotier net-auth <nwid> <devID>" << std::endl;
  705. }
  706. std::string nwid = state.args[0];
  707. std::string devid = state.args[1];
  708. url = state.url + "api/network/" + nwid + "/member/" + devid;
  709. // Add device to network
  710. res = REQUEST(REQ_POST,state,state.reqHeaders,"",(const std::string)url);
  711. if(std::get<0>(res) == 200) {
  712. result = json::parse(std::get<1>(res));
  713. res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url);
  714. result = json::parse(std::get<1>(res));
  715. result["config"]["authorized"] = "true";
  716. std::string newconfig = result.dump();
  717. res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,(const std::string)url);
  718. if(std::get<0>(res) == 200)
  719. std::cout << OK_STR << devid << " authorized on " << nwid << std::endl;
  720. else
  721. std::cout << FAIL_STR << "There was a problem authorizing that device." << std::endl;
  722. }
  723. }
  724. // zerotier @thing net-unauth <nwid> <devID>
  725. else if (state.command == "net-unauth") {
  726. if(argc > 5 || (argc == 5 && !state.atname.length())) {
  727. std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-unauth <nwid> <devID>" << std::endl;
  728. return 1;
  729. }
  730. checkForThing(state,"central",true);
  731. if(state.args.size() != 2) {
  732. std::cout << FAIL_STR << "Bad argument. No network and/or device ID specified." << std::endl;
  733. std::cout << " | Usage: zerotier net-unauth <nwid> <devID>" << std::endl;
  734. }
  735. std::string nwid = state.args[0];
  736. std::string devid = state.args[1];
  737. // If successful, get member config
  738. res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid);
  739. result = json::parse(std::get<1>(res));
  740. // modify auth field and re-POST
  741. result["config"]["authorized"] = "false";
  742. std::string newconfig = result.dump();
  743. res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,state.url + "api/network/" + nwid + "/member/" + devid);
  744. if(std::get<0>(res) == 200)
  745. std::cout << OK_STR << devid << " de-authorized from " << nwid << std::endl;
  746. else
  747. std::cout << FAIL_STR << "There was a problem de-authorizing that device." << std::endl;
  748. }
  749. // zerotier @thing net-set
  750. else if (state.command == "net-set") {
  751. }
  752. // ID
  753. // zerotier id-generate [<vanity prefix>]
  754. else if (state.command == "id-generate") {
  755. if(argc != 3) {
  756. std::cerr << INVALID_ARGS_STR << "zerotier id-generate [<vanity prefix>]" << std::endl;
  757. return 1;
  758. }
  759. uint64_t vanity = 0;
  760. int vanityBits = 0;
  761. if (argc >= 5) {
  762. vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL;
  763. vanityBits = 4 * strlen(argv[4]);
  764. if (vanityBits > 40)
  765. vanityBits = 40;
  766. }
  767. ZeroTier::Identity id;
  768. for(;;) {
  769. id.generate();
  770. if ((id.address().toInt() >> (40 - vanityBits)) == vanity) {
  771. if (vanityBits > 0) {
  772. fprintf(stderr,"vanity address: found %.10llx !\n",(unsigned long long)id.address().toInt());
  773. }
  774. break;
  775. } else {
  776. 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)));
  777. }
  778. }
  779. std::string idser = id.toString(true);
  780. if (argc >= 3) {
  781. if (!OSUtils::writeFile(argv[2],idser)) {
  782. std::cerr << "Error writing to " << argv[2] << std::endl;
  783. return 1;
  784. } else std::cout << argv[2] << " written" << std::endl;
  785. if (argc >= 4) {
  786. idser = id.toString(false);
  787. if (!OSUtils::writeFile(argv[3],idser)) {
  788. std::cerr << "Error writing to " << argv[3] << std::endl;
  789. return 1;
  790. } else std::cout << argv[3] << " written" << std::endl;
  791. }
  792. } else std::cout << idser << std::endl;
  793. }
  794. // zerotier id-validate <identity>
  795. else if (state.command == "id-validate") {
  796. if(argc != 3) {
  797. std::cerr << INVALID_ARGS_STR << "zerotier id-validate <identity>" << std::endl;
  798. return 1;
  799. }
  800. Identity id = getIdFromArg(argv[2]);
  801. if (!id) {
  802. std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl;
  803. return 1;
  804. }
  805. if (!id.locallyValidate()) {
  806. std::cerr << argv[2] << " FAILED validation." << std::endl;
  807. return 1;
  808. } else std::cout << argv[2] << "is a valid identity" << std::endl;
  809. }
  810. // zerotier id-sign <identity> <file>
  811. else if (state.command == "id-sign") {
  812. if(argc != 4) {
  813. std::cerr << INVALID_ARGS_STR << "zerotier id-sign <identity> <file>" << std::endl;
  814. return 1;
  815. }
  816. Identity id = getIdFromArg(argv[2]);
  817. if (!id) {
  818. std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl;
  819. return 1;
  820. }
  821. if (!id.hasPrivate()) {
  822. std::cerr << argv[2] << " does not contain a private key (must use private to sign)" << std::endl;
  823. return 1;
  824. }
  825. std::string inf;
  826. if (!OSUtils::readFile(argv[3],inf)) {
  827. std::cerr << argv[3] << " is not readable" << std::endl;
  828. return 1;
  829. }
  830. C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length());
  831. std::cout << Utils::hex(signature.data,(unsigned int)signature.size()) << std::endl;
  832. }
  833. // zerotier id-verify <secret> <file> <sig>
  834. else if (state.command == "id-verify") {
  835. if(argc != 4) {
  836. std::cerr << INVALID_ARGS_STR << "zerotier id-verify <secret> <file> <sig>" << std::endl;
  837. return 1;
  838. }
  839. Identity id = getIdFromArg(argv[2]);
  840. if (!id) {
  841. std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl;
  842. return 1;
  843. }
  844. std::string inf;
  845. if (!OSUtils::readFile(argv[3],inf)) {
  846. std::cerr << argv[3] << " is not readable" << std::endl;
  847. return 1;
  848. }
  849. std::string signature(Utils::unhex(argv[4]));
  850. if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) {
  851. std::cout << argv[3] << " signature valid" << std::endl;
  852. } else {
  853. std::cerr << argv[3] << " signature check FAILED" << std::endl;
  854. return 1;
  855. }
  856. }
  857. // zerotier id-getpublic <secret>
  858. else if (state.command == "id-getpublic") {
  859. if(argc != 3) {
  860. std::cerr << INVALID_ARGS_STR << "zerotier id-getpublic <secret>" << std::endl;
  861. return 1;
  862. }
  863. Identity id = getIdFromArg(argv[2]);
  864. if (!id) {
  865. std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl;
  866. return 1;
  867. }
  868. std::cerr << id.toString(false) << std::endl;
  869. }
  870. //
  871. else {
  872. dumpHelp();
  873. return -1;
  874. }
  875. if(std::find(state.args.begin(), state.args.end(), "-verbose") != state.args.end())
  876. std::cout << "\n\nAPI response = " << std::get<1>(res) << std::endl;
  877. curl_global_cleanup();
  878. return 0;
  879. }