ZeroTierOne/one.cpp
Chris Spiegel 9e53fe4b12 Avoid using the same source and destination in snprintf()
It's undefined to write to a buffer you're reading from in snprintf().
On modern glibc and musl versions, this results in the string
"/zerotier_dump.txt" being generated, i.e. in the root directory. Use a
new variable to hold the string dump.

This is done for MacOS as well. On Sequoia, at least, it's not
necessary, as it handles overlapping objects fine, but this is more
future-proof.

At the same time, include a specific error message when the dumpfile
can't be opened to help users track down problems. Also, truncate the
file so that new writes don't potentially leave stale data.
2025-11-06 08:28:33 -08:00

2420 lines
77 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include "node/ECC.hpp"
#endif
#include "node/Constants.hpp"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef __WINDOWS__
// clang-format off
#include <winsock2.h>
#include <windows.h>
#include <tchar.h>
#include <wchar.h>
#include <lmcons.h>
#include <newdev.h>
#include <atlbase.h>
#include <iphlpapi.h>
#include <iomanip>
#include <shlobj.h>
#include "osdep/WindowsEthernetTap.hpp"
#include "windows/ZeroTierOne/ServiceInstaller.h"
#include "windows/ZeroTierOne/ServiceBase.h"
#include "windows/ZeroTierOne/ZeroTierOneService.h"
// clang-format on
#else
#include <dirent.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#ifdef __LINUX__
#include "osdep/ExtOsdep.hpp"
#include <ifaddrs.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifndef ZT_NO_CAPABILITIES
#include <linux/capability.h>
#include <linux/securebits.h>
#endif
#endif
#endif
#include "include/ZeroTierOne.h"
#include "node/Bond.hpp"
#include "node/Buffer.hpp"
#include "node/CertificateOfMembership.hpp"
#include "node/Identity.hpp"
#include "node/NetworkController.hpp"
#include "node/Utils.hpp"
#include "node/World.hpp"
#include "osdep/Http.hpp"
#include "osdep/OSUtils.hpp"
#include "osdep/Thread.hpp"
#include "service/OneService.hpp"
#include "version.h"
#include <algorithm>
#include <iostream>
#include <nlohmann/json.hpp>
#include <sstream>
#include <stdexcept>
#include <string>
#ifdef __APPLE__
#include <CoreServices/CoreServices.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#define ZT_PID_PATH "zerotier-one.pid"
using namespace ZeroTier;
static OneService* volatile zt1Service = (OneService*)0;
#define PROGRAM_NAME "ZeroTier One"
#define COPYRIGHT_NOTICE "Copyright (c) ZeroTier, Inc."
#ifdef ZT_NONFREE_CONTROLLER
#define LICENSE_GRANT ZT_EOL_S "Licensed under a Source-Available License for Non-Commercial" ZT_EOL_S "Use (nonfree/LICENSE.md). Use of this build for Commercial Use" ZT_EOL_S "requires a paid subscription plan or a commercial license" ZT_EOL_S "agreement with ZeroTier, Inc. Visit https://www.zerotier.com for" ZT_EOL_S "more information."
#else
#define LICENSE_GRANT "Licensed under Mozilla Public License v2.0 (LICENSE-MPL.txt)."
#endif
/****************************************************************************/
/* zerotier-cli personality */
/****************************************************************************/
static void cliPrintHelp(const char* pn, FILE* out)
{
fprintf(
out,
"%s version %d.%d.%d build %d (platform %d arch %d)" ZT_EOL_S,
PROGRAM_NAME,
ZEROTIER_ONE_VERSION_MAJOR,
ZEROTIER_ONE_VERSION_MINOR,
ZEROTIER_ONE_VERSION_REVISION,
ZEROTIER_ONE_VERSION_BUILD,
ZT_BUILD_PLATFORM,
ZT_BUILD_ARCHITECTURE);
fprintf(out, COPYRIGHT_NOTICE ZT_EOL_S LICENSE_GRANT ZT_EOL_S);
fprintf(out, ZT_EOL_S "Usage: %s [-switches] <command/path> [<args>]" ZT_EOL_S "" ZT_EOL_S, pn);
fprintf(out, "Available switches:" ZT_EOL_S);
fprintf(out, " -h - Display this help" ZT_EOL_S);
fprintf(out, " -v - Show version" ZT_EOL_S);
fprintf(out, " -j - Display full raw JSON output" ZT_EOL_S);
fprintf(out, " -D<path> - ZeroTier home path for parameter auto-detect" ZT_EOL_S);
fprintf(out, " -p<port> - HTTP port (default: auto)" ZT_EOL_S);
fprintf(out, " -T<token> - Authentication token (default: auto)" ZT_EOL_S);
fprintf(out, ZT_EOL_S "Available commands:" ZT_EOL_S);
fprintf(out, " info - Display status info" ZT_EOL_S);
fprintf(out, " listpeers - List all peers" ZT_EOL_S);
fprintf(out, " peers - List all peers (prettier)" ZT_EOL_S);
fprintf(out, " listnetworks - List all networks" ZT_EOL_S);
fprintf(out, " join <network ID> - Join a network" ZT_EOL_S);
fprintf(out, " leave <network ID> - Leave a network" ZT_EOL_S);
fprintf(out, " set <network ID> <setting> - Set a network setting" ZT_EOL_S);
fprintf(out, " get <network ID> <setting> - Get a network setting" ZT_EOL_S);
fprintf(out, " dump - Debug settings dump for support" ZT_EOL_S);
fprintf(out, ZT_EOL_S "Available settings:" ZT_EOL_S);
fprintf(out, " Settings to use with [get/set] may include property names from " ZT_EOL_S);
fprintf(out, " the JSON output of \"zerotier-cli -j listnetworks\". Additionally, " ZT_EOL_S);
fprintf(out, " (ip, ip4, ip6, ip6plane, and ip6prefix can be used). For instance:" ZT_EOL_S);
fprintf(out, " zerotier-cli get <network ID> ip6plane will return the 6PLANE address" ZT_EOL_S);
fprintf(out, " assigned to this node." ZT_EOL_S);
}
static std::string cliFixJsonCRs(const std::string& s)
{
std::string r;
for (std::string::const_iterator c(s.begin()); c != s.end(); ++c) {
if (*c == '\n')
r.append(ZT_EOL_S);
else
r.push_back(*c);
}
return r;
}
#ifdef __WINDOWS__
static int cli(int argc, _TCHAR* argv[])
#else
static int cli(int argc, char** argv)
#endif
{
unsigned int port = 0;
std::string homeDir, command, arg1, arg2, arg3, arg4, authToken;
std::string ip("127.0.0.1");
bool json = false;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-') {
switch (argv[i][1]) {
case 'q': // ignore -q used to invoke this personality
if (argv[i][2]) {
cliPrintHelp(argv[0], stdout);
return 1;
}
break;
case 'j':
if (argv[i][2]) {
cliPrintHelp(argv[0], stdout);
return 1;
}
json = true;
break;
case 'p':
port = Utils::strToUInt(argv[i] + 2);
if ((port > 0xffff) || (port == 0)) {
cliPrintHelp(argv[0], stdout);
return 1;
}
break;
case 'D':
if (argv[i][2]) {
homeDir = argv[i] + 2;
}
else {
cliPrintHelp(argv[0], stdout);
return 1;
}
break;
case 'H':
if (argv[i][2]) {
ip = argv[i] + 2;
}
else {
cliPrintHelp(argv[0], stdout);
return 1;
}
break;
case 'T':
if (argv[i][2]) {
authToken = argv[i] + 2;
}
else {
cliPrintHelp(argv[0], stdout);
return 1;
}
break;
case 'v':
if (argv[i][2]) {
cliPrintHelp(argv[0], stdout);
return 1;
}
printf("%d.%d.%d" ZT_EOL_S, ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION);
return 0;
case 'h':
case '?':
default:
cliPrintHelp(argv[0], stdout);
return 0;
}
}
else {
if (arg1.length())
arg2 = argv[i];
else if (command.length())
arg1 = argv[i];
else
command = argv[i];
}
}
if (! homeDir.length())
homeDir = OneService::platformDefaultHomePath();
// TODO: cleanup this logic
if ((! port) || (! authToken.length())) {
if (! homeDir.length()) {
fprintf(stderr, "%s: missing port or authentication token and no home directory specified to auto-detect" ZT_EOL_S, argv[0]);
return 2;
}
if (! port) {
std::string portStr;
OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "zerotier-one.port").c_str(), portStr);
port = Utils::strToUInt(portStr.c_str());
if ((port == 0) || (port > 0xffff)) {
fprintf(stderr, "%s: missing port and zerotier-one.port not found in %s" ZT_EOL_S, argv[0], homeDir.c_str());
return 2;
}
}
if (! authToken.length()) {
OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "authtoken.secret").c_str(), authToken);
#ifdef __UNIX_LIKE__
if (! authToken.length()) {
const char* hd = getenv("HOME");
if (hd) {
char p[4096];
#ifdef __APPLE__
OSUtils::ztsnprintf(p, sizeof(p), "%s/Library/Application Support/ZeroTier/One/authtoken.secret", hd);
#else
OSUtils::ztsnprintf(p, sizeof(p), "%s/.zeroTierOneAuthToken", hd);
#endif
OSUtils::readFile(p, authToken);
}
}
#endif
if (! authToken.length()) {
fprintf(stderr, "%s: authtoken.secret not found or readable in %s (try again as root)" ZT_EOL_S, argv[0], homeDir.c_str());
return 2;
}
}
}
InetAddress addr;
{
char addrtmp[256];
OSUtils::ztsnprintf(addrtmp, sizeof(addrtmp), "%s/%u", ip.c_str(), port);
addr = InetAddress(addrtmp);
}
std::map<std::string, std::string> requestHeaders;
std::map<std::string, std::string> responseHeaders;
std::string responseBody;
requestHeaders["X-ZT1-Auth"] = authToken;
if ((command.length() > 0) && (command[0] == '/')) {
unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, command.c_str(), requestHeaders, responseHeaders, responseBody);
if (scode == 200) {
printf("%s", cliFixJsonCRs(responseBody).c_str());
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if ((command == "info") || (command == "status")) {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/status", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
if (j.is_object()) {
printf("200 info %s %s %s" ZT_EOL_S, OSUtils::jsonString(j["address"], "-").c_str(), OSUtils::jsonString(j["version"], "-").c_str(), ((j["tcpFallbackActive"]) ? "TUNNELED" : ((j["online"]) ? "ONLINE" : "OFFLINE")));
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "listpeers") {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/peer", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
printf("200 listpeers <ztaddr> <path> <latency> <version> <role>" ZT_EOL_S);
if (j.is_array()) {
for (unsigned long k = 0; k < j.size(); ++k) {
nlohmann::json& p = j[k];
std::string bestPath;
nlohmann::json& paths = p["paths"];
if (paths.is_array()) {
for (unsigned long i = 0; i < paths.size(); ++i) {
nlohmann::json& path = paths[i];
if (path["preferred"]) {
char tmp[256];
std::string addr = path["address"];
const int64_t now = OSUtils::now();
int64_t lastSendDiff = (uint64_t)path["lastSend"] ? now - (uint64_t)path["lastSend"] : -1;
int64_t lastReceiveDiff = (uint64_t)path["lastReceive"] ? now - (uint64_t)path["lastReceive"] : -1;
OSUtils::ztsnprintf(tmp, sizeof(tmp), "%s;%lld;%lld", addr.c_str(), lastSendDiff, lastReceiveDiff);
bestPath = tmp;
break;
}
}
}
if (bestPath.length() == 0)
bestPath = "-";
char ver[128];
int64_t vmaj = p["versionMajor"];
int64_t vmin = p["versionMinor"];
int64_t vrev = p["versionRev"];
if (vmaj >= 0) {
OSUtils::ztsnprintf(ver, sizeof(ver), "%lld.%lld.%lld", vmaj, vmin, vrev);
}
else {
ver[0] = '-';
ver[1] = (char)0;
}
printf("200 listpeers %s %s %d %s %s" ZT_EOL_S, OSUtils::jsonString(p["address"], "-").c_str(), bestPath.c_str(), (int)OSUtils::jsonInt(p["latency"], 0), ver, OSUtils::jsonString(p["role"], "-").c_str());
}
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "peers") {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/peer", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
bool anyTunneled = false;
printf("200 peers\n<ztaddr> <ver> <role> <lat> <link> <lastTX> <lastRX> <path>" ZT_EOL_S);
if (j.is_array()) {
for (unsigned long k = 0; k < j.size(); ++k) {
nlohmann::json& p = j[k];
std::string bestPath;
nlohmann::json& paths = p["paths"];
if (p["tunneled"]) {
anyTunneled = true;
}
if (paths.is_array()) {
for (unsigned long i = 0; i < paths.size(); ++i) {
nlohmann::json& path = paths[i];
if (path["preferred"]) {
char tmp[256];
std::string addr = path["address"];
const int64_t now = OSUtils::now();
int64_t lastSendDiff = (uint64_t)path["lastSend"] ? now - (uint64_t)path["lastSend"] : -1;
int64_t lastReceiveDiff = (uint64_t)path["lastReceive"] ? now - (uint64_t)path["lastReceive"] : -1;
OSUtils::ztsnprintf(tmp, sizeof(tmp), "%-8lld %-8lld %s", lastSendDiff, lastReceiveDiff, addr.c_str());
if (p["tunneled"]) {
bestPath = std::string("RELAY ") + tmp;
}
else {
bestPath = std::string("DIRECT ") + tmp;
}
break;
}
}
}
if (bestPath.length() == 0) {
bestPath = "RELAY";
}
char ver[128];
int64_t vmaj = p["versionMajor"];
int64_t vmin = p["versionMinor"];
int64_t vrev = p["versionRev"];
if (vmaj >= 0) {
OSUtils::ztsnprintf(ver, sizeof(ver), "%lld.%lld.%lld", vmaj, vmin, vrev);
}
else {
ver[0] = '-';
ver[1] = (char)0;
}
printf("%s %-6s %-6s %5d %s" ZT_EOL_S, OSUtils::jsonString(p["address"], "-").c_str(), ver, OSUtils::jsonString(p["role"], "-").c_str(), (int)OSUtils::jsonInt(p["latency"], 0), bestPath.c_str());
}
}
if (anyTunneled) {
printf("NOTE: Currently tunneling through a TCP relay. Ensure that UDP is not blocked.\n");
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "bond") {
/* zerotier-cli bond <cmd> */
if (arg1.empty()) {
printf("(bond) command is missing required arguments" ZT_EOL_S);
return 2;
}
/* zerotier-cli bond list */
if (arg1 == "list") {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/peer", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
bool bFoundBond = false;
printf(" <peer> <bondtype> <links>" ZT_EOL_S);
if (j.is_array()) {
for (unsigned long k = 0; k < j.size(); ++k) {
nlohmann::json& p = j[k];
bool isBonded = p["isBonded"];
if (isBonded) {
int8_t bondingPolicyCode = p["bondingPolicyCode"];
int8_t numAliveLinks = p["numAliveLinks"];
int8_t numTotalLinks = p["numTotalLinks"];
bFoundBond = true;
std::string policyStr = "none";
if (bondingPolicyCode >= ZT_BOND_POLICY_NONE && bondingPolicyCode <= ZT_BOND_POLICY_BALANCE_AWARE) {
policyStr = Bond::getPolicyStrByCode(bondingPolicyCode);
}
printf("%10s %32s %d/%d" ZT_EOL_S, OSUtils::jsonString(p["address"], "-").c_str(), policyStr.c_str(), numAliveLinks, numTotalLinks);
}
}
}
if (! bFoundBond) {
printf(" NONE\t\t\t\tNONE\t NONE NONE" ZT_EOL_S);
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (arg1 == "setmtu") { /* zerotier-cli bond setmtu <mtu> <iface> <ip> */
requestHeaders["Content-Type"] = "application/json";
requestHeaders["Content-Length"] = "2";
if (argc == 8) {
arg2 = argv[5];
arg3 = argv[6];
arg4 = argv[7];
}
unsigned int scode = Http::POST(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/bond/") + arg1 + "/" + arg2 + "/" + arg3 + "/" + arg4).c_str(), requestHeaders, "{}", 2, responseHeaders, responseBody);
if (scode == 200) {
printf("200 setmtu OK" ZT_EOL_S);
return 0;
}
else {
printf("%d Failed to set MTU: %s" ZT_EOL_S, scode, responseBody.c_str());
return 1;
}
return 0;
}
else if (arg1.length() == 10) {
if (arg2 == "rotate") { /* zerotier-cli bond <peerId> rotate */
requestHeaders["Content-Type"] = "application/json";
requestHeaders["Content-Length"] = "2";
unsigned int scode = Http::POST(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/bond/") + arg2 + "/" + arg1).c_str(), requestHeaders, "{}", 2, responseHeaders, responseBody);
if (scode == 200) {
if (json) {
printf("%s", cliFixJsonCRs(responseBody).c_str());
}
else {
printf("200 rotate OK" ZT_EOL_S);
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
return 0;
}
if (arg2 == "show") {
// fprintf(stderr, "zerotier-cli bond <peerId> show\n");
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/bond/") + arg2 + "/" + arg1).c_str(), requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
int numAliveLinks = OSUtils::jsonInt(j["numAliveLinks"], 0);
int numTotalLinks = OSUtils::jsonInt(j["numTotalLinks"], 0);
printf("Peer : %s\n", arg1.c_str());
printf("Bond : %s\n", OSUtils::jsonString(j["bondingPolicyStr"], "-").c_str());
printf("Link Select Method : %d\n", (int)OSUtils::jsonInt(j["linkSelectMethod"], 0));
printf("Links : %d/%d\n", numAliveLinks, numTotalLinks);
printf("Failover Interval (ms) : %d\n", (int)OSUtils::jsonInt(j["failoverInterval"], 0));
printf("Up Delay (ms) : %d\n", (int)OSUtils::jsonInt(j["upDelay"], 0));
printf("Down Delay (ms) : %d\n", (int)OSUtils::jsonInt(j["downDelay"], 0));
printf("Packets Per Link : %d\n", (int)OSUtils::jsonInt(j["packetsPerLink"], 0));
nlohmann::json& p = j["paths"];
if (p.is_array()) {
printf("\nidx"
" interface"
" "
"path socket local port\n");
for (int i = 0; i < 120; i++) {
printf("-");
}
printf("\n");
for (int i = 0; i < p.size(); i++) {
printf(
"%2d: %26s %51s %.16llx %12d\n",
i,
OSUtils::jsonString(p[i]["ifname"], "-").c_str(),
OSUtils::jsonString(p[i]["address"], "-").c_str(),
(unsigned long long)OSUtils::jsonInt(p[i]["localSocket"], 0),
(uint16_t)OSUtils::jsonInt(p[i]["localPort"], 0));
}
printf("\nidx lat pdv "
"capacity qual "
"rx_age tx_age eligible bonded flows\n");
for (int i = 0; i < 120; i++) {
printf("-");
}
printf("\n");
for (int i = 0; i < p.size(); i++) {
printf(
"%2d: %8.2f %8.2f %10d %7.4f %11d %11d %9d %7d %7d\n",
i,
OSUtils::jsonDouble(p[i]["latencyMean"], 0),
OSUtils::jsonDouble(p[i]["latencyVariance"], 0),
(int)OSUtils::jsonInt(p[i]["givenLinkSpeed"], 0),
OSUtils::jsonDouble(p[i]["relativeQuality"], 0),
(int)OSUtils::jsonInt(p[i]["lastInAge"], 0),
(int)OSUtils::jsonInt(p[i]["lastOutAge"], 0),
(int)OSUtils::jsonInt(p[i]["eligible"], 0),
(int)OSUtils::jsonInt(p[i]["bonded"], 0),
(int)OSUtils::jsonInt(p[i]["assignedFlowCount"], 0));
}
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
return 2;
}
}
/* zerotier-cli bond command was malformed in some way */
printf("(bond) command is missing required arguments" ZT_EOL_S);
return 2;
}
else if (command == "listbonds") {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/peer", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
bool bFoundBond = false;
printf(" <peer> <bondtype> <links>" ZT_EOL_S);
if (j.is_array()) {
for (unsigned long k = 0; k < j.size(); ++k) {
nlohmann::json& p = j[k];
bool isBonded = p["isBonded"];
if (isBonded) {
int8_t bondingPolicyCode = p["bondingPolicyCode"];
int8_t numAliveLinks = p["numAliveLinks"];
int8_t numTotalLinks = p["numTotalLinks"];
bFoundBond = true;
std::string policyStr = "none";
if (bondingPolicyCode >= ZT_BOND_POLICY_NONE && bondingPolicyCode <= ZT_BOND_POLICY_BALANCE_AWARE) {
policyStr = Bond::getPolicyStrByCode(bondingPolicyCode);
}
printf("%10s %32s %d/%d" ZT_EOL_S, OSUtils::jsonString(p["address"], "-").c_str(), policyStr.c_str(), numAliveLinks, numTotalLinks);
}
}
}
if (! bFoundBond) {
printf(" NONE\t\t\t\tNONE\t NONE NONE" ZT_EOL_S);
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "listnetworks") {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/network", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
if (json) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
}
else {
printf("200 listnetworks <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>" ZT_EOL_S);
if (j.is_array()) {
for (unsigned long i = 0; i < j.size(); ++i) {
nlohmann::json& n = j[i];
if (n.is_object()) {
std::string aa;
nlohmann::json& assignedAddresses = n["assignedAddresses"];
if (assignedAddresses.is_array()) {
for (unsigned long j = 0; j < assignedAddresses.size(); ++j) {
nlohmann::json& addr = assignedAddresses[j];
if (addr.is_string()) {
if (aa.length() > 0)
aa.push_back(',');
aa.append(addr.get<std::string>());
}
}
}
if (aa.length() == 0)
aa = "-";
const std::string status = OSUtils::jsonString(n["status"], "-");
printf(
"200 listnetworks %s %s %s %s %s %s %s" ZT_EOL_S,
OSUtils::jsonString(n["nwid"], "-").c_str(),
OSUtils::jsonString(n["name"], "-").c_str(),
OSUtils::jsonString(n["mac"], "-").c_str(),
status.c_str(),
OSUtils::jsonString(n["type"], "-").c_str(),
OSUtils::jsonString(n["portDeviceName"], "-").c_str(),
aa.c_str());
if (OSUtils::jsonBool(n["ssoEnabled"], false)) {
uint64_t authenticationExpiryTime = n["authenticationExpiryTime"];
if (status == "AUTHENTICATION_REQUIRED") {
printf(" AUTH EXPIRED, URL: %s" ZT_EOL_S, OSUtils::jsonString(n["authenticationURL"], "(null)").c_str());
}
else if (status == "OK") {
int64_t expiresIn = ((int64_t)authenticationExpiryTime - OSUtils::now()) / 1000LL;
if (expiresIn >= 0) {
printf(" AUTH OK, expires in: %lld seconds" ZT_EOL_S, expiresIn);
}
}
}
}
}
}
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "join") {
if (arg1.length() != 16) {
printf("invalid network id" ZT_EOL_S);
return 2;
}
requestHeaders["Content-Type"] = "application/json";
requestHeaders["Content-Length"] = "2";
unsigned int scode = Http::POST(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/network/") + arg1).c_str(), requestHeaders, "{}", 2, responseHeaders, responseBody);
if (scode == 200) {
if (json) {
printf("%s", cliFixJsonCRs(responseBody).c_str());
}
else {
printf("200 join OK" ZT_EOL_S);
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "leave") {
if (arg1.length() != 16) {
printf("invalid network id" ZT_EOL_S);
return 2;
}
unsigned int scode = Http::DEL(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/network/") + arg1).c_str(), requestHeaders, responseHeaders, responseBody);
if (scode == 200) {
if (json) {
printf("%s", cliFixJsonCRs(responseBody).c_str());
}
else {
printf("200 leave OK" ZT_EOL_S);
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "listmoons") {
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/moon", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
if (scode == 200) {
printf("%s" ZT_EOL_S, OSUtils::jsonDump(j).c_str());
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "orbit") {
const uint64_t worldId = Utils::hexStrToU64(arg1.c_str());
const uint64_t seed = Utils::hexStrToU64(arg2.c_str());
if ((worldId) && (seed)) {
char jsons[1024];
OSUtils::ztsnprintf(jsons, sizeof(jsons), "{\"seed\":\"%s\"}", arg2.c_str());
char cl[128];
OSUtils::ztsnprintf(cl, sizeof(cl), "%u", (unsigned int)strlen(jsons));
requestHeaders["Content-Type"] = "application/json";
requestHeaders["Content-Length"] = cl;
unsigned int scode = Http::POST(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/moon/") + arg1).c_str(), requestHeaders, jsons, (unsigned long)strlen(jsons), responseHeaders, responseBody);
if (scode == 200) {
printf("200 orbit OK" ZT_EOL_S);
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
}
else if (command == "deorbit") {
unsigned int scode = Http::DEL(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/moon/") + arg1).c_str(), requestHeaders, responseHeaders, responseBody);
if (scode == 200) {
if (json) {
printf("%s", cliFixJsonCRs(responseBody).c_str());
}
else {
printf("200 deorbit OK" ZT_EOL_S);
}
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "set") {
if (arg1.length() != 16) {
fprintf(stderr, "invalid format: must be a 16-digit (network) ID\n");
return 2;
}
if (! arg2.length()) {
fprintf(stderr, "invalid format: include a property name to set\n");
return 2;
}
std::size_t eqidx = arg2.find('=');
if (eqidx != std::string::npos) {
if ((arg2.substr(0, eqidx) == "allowManaged") || (arg2.substr(0, eqidx) == "allowGlobal") || (arg2.substr(0, eqidx) == "allowDefault") || (arg2.substr(0, eqidx) == "allowDNS")) {
char jsons[1024];
OSUtils::ztsnprintf(jsons, sizeof(jsons), "{\"%s\":%s}", arg2.substr(0, eqidx).c_str(), (((arg2.substr(eqidx, 2) == "=t") || (arg2.substr(eqidx, 2) == "=1")) ? "true" : "false"));
char cl[128];
OSUtils::ztsnprintf(cl, sizeof(cl), "%u", (unsigned int)strlen(jsons));
requestHeaders["Content-Type"] = "application/json";
requestHeaders["Content-Length"] = cl;
unsigned int scode = Http::POST(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, (std::string("/network/") + arg1).c_str(), requestHeaders, jsons, (unsigned long)strlen(jsons), responseHeaders, responseBody);
if (scode == 200) {
printf("%s", cliFixJsonCRs(responseBody).c_str());
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
}
else {
cliPrintHelp(argv[0], stderr);
return 2;
}
}
else if (command == "get") {
if (arg1.length() != 16) {
fprintf(stderr, "invalid format: must be a 16-digit (network) ID\n");
return 2;
}
if (! arg2.length()) {
fprintf(stderr, "invalid format: include a property name to get\n");
return 2;
}
const unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/network", requestHeaders, responseHeaders, responseBody);
if (scode == 0) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
nlohmann::json j;
try {
j = OSUtils::jsonParse(responseBody);
}
catch (std::exception& exc) {
printf("%u %s invalid JSON response (%s)" ZT_EOL_S, scode, command.c_str(), exc.what());
return 1;
}
catch (...) {
printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S, scode, command.c_str());
return 1;
}
bool bNetworkFound = false;
if (j.is_array()) {
for (unsigned long i = 0; i < j.size(); ++i) {
nlohmann::json& n = j[i];
if (n.is_object()) {
if (n["id"] == arg1) {
bNetworkFound = true;
std::string aa;
if (arg2 != "ip" && arg2 != "ip4" && arg2 != "ip6" && arg2 != "ip6plane" && arg2 != "ip6prefix") {
aa.append(OSUtils::jsonString(n[arg2], "-")); // Standard network property field
if (aa == "-") {
printf("error, unknown property name\n");
break;
}
printf("%s\n", aa.c_str());
break;
}
nlohmann::json& assignedAddresses = n["assignedAddresses"];
if (assignedAddresses.is_array()) {
int matchingIdxs[ZT_MAX_ZT_ASSIGNED_ADDRESSES];
int addressCountOfType = 0;
for (int k = 0; k < std::min(ZT_MAX_ZT_ASSIGNED_ADDRESSES, (int)assignedAddresses.size()); ++k) {
nlohmann::json& addr = assignedAddresses[k];
if ((arg2 == "ip4" && addr.get<std::string>().find('.') != std::string::npos) || ((arg2.find("ip6") == 0) && addr.get<std::string>().find(":") != std::string::npos) || (arg2 == "ip")) {
matchingIdxs[addressCountOfType++] = k;
}
}
for (int k = 0; k < addressCountOfType; k++) {
nlohmann::json& addr = assignedAddresses[matchingIdxs[k]];
if (! addr.is_string()) {
continue;
}
if (arg2.find("ip6p") == 0) {
if (arg2 == "ip6plane") {
if (addr.get<std::string>().find("fc") == 0) {
aa.append(addr.get<std::string>().substr(0, addr.get<std::string>().find('/')));
if (k < addressCountOfType - 1)
aa.append("\n");
}
}
if (arg2 == "ip6prefix") {
if (addr.get<std::string>().find("fc") == 0) {
aa.append(addr.get<std::string>().substr(0, addr.get<std::string>().find('/')).substr(0, 24));
if (k < addressCountOfType - 1)
aa.append("\n");
}
}
}
else {
aa.append(addr.get<std::string>().substr(0, addr.get<std::string>().find('/')));
if (k < addressCountOfType - 1)
aa.append("\n");
}
}
}
printf("%s\n", aa.c_str());
}
}
}
}
if (! bNetworkFound) {
fprintf(stderr, "unknown network ID, check that you are a member of the network\n");
}
if (scode == 200) {
return 0;
}
else {
printf("%u %s %s" ZT_EOL_S, scode, command.c_str(), responseBody.c_str());
return 1;
}
}
else if (command == "dump") {
std::stringstream dump;
dump << "platform: ";
#ifdef __APPLE__
dump << "macOS" << ZT_EOL_S;
#elif defined(_WIN32)
dump << "Windows" << ZT_EOL_S;
#elif defined(__LINUX__)
dump << "Linux" << ZT_EOL_S;
#else
dump << "other unix based OS" << ZT_EOL_S;
#endif
dump << "zerotier version: " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << ZT_EOL_S << ZT_EOL_S;
// grab status
dump << "status" << ZT_EOL_S << "------" << ZT_EOL_S;
unsigned int scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/status", requestHeaders, responseHeaders, responseBody);
if (scode != 200) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
dump << responseBody << ZT_EOL_S;
responseHeaders.clear();
responseBody = "";
// grab network list
dump << ZT_EOL_S << "networks" << ZT_EOL_S << "--------" << ZT_EOL_S;
scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/network", requestHeaders, responseHeaders, responseBody);
if (scode != 200) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
dump << responseBody << ZT_EOL_S;
responseHeaders.clear();
responseBody = "";
// list peers
dump << ZT_EOL_S << "peers" << ZT_EOL_S << "-----" << ZT_EOL_S;
scode = Http::GET(1024 * 1024 * 16, 60000, (const struct sockaddr*)&addr, "/peer", requestHeaders, responseHeaders, responseBody);
if (scode != 200) {
printf("Error connecting to the ZeroTier service: %s\n\nPlease check that the service is running and that TCP port 9993 can be contacted via 127.0.0.1." ZT_EOL_S, responseBody.c_str());
return 1;
}
dump << responseBody << ZT_EOL_S;
// Bonds don't need to be queried separately since their data originates from "/peer" responses anyway
responseHeaders.clear();
responseBody = "";
dump << ZT_EOL_S << "local.conf" << ZT_EOL_S << "----------" << ZT_EOL_S;
std::string localConf;
OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "local.conf").c_str(), localConf);
if (localConf.empty()) {
dump << "None Present" << ZT_EOL_S;
}
else {
dump << localConf << ZT_EOL_S;
}
dump << ZT_EOL_S << "Network Interfaces" << ZT_EOL_S << "------------------" << ZT_EOL_S << ZT_EOL_S;
#ifdef __APPLE__
CFArrayRef interfaces = SCNetworkInterfaceCopyAll();
CFIndex size = CFArrayGetCount(interfaces);
for (CFIndex i = 0; i < size; ++i) {
SCNetworkInterfaceRef iface = (SCNetworkInterfaceRef)CFArrayGetValueAtIndex(interfaces, i);
dump << "Interface " << i << ZT_EOL_S << "-----------" << ZT_EOL_S;
CFStringRef tmp = SCNetworkInterfaceGetBSDName(iface);
char stringBuffer[512] = {};
CFStringGetCString(tmp, stringBuffer, sizeof(stringBuffer), kCFStringEncodingUTF8);
dump << "Name: " << stringBuffer << ZT_EOL_S;
std::string ifName(stringBuffer);
int mtuCur, mtuMin, mtuMax;
SCNetworkInterfaceCopyMTU(iface, &mtuCur, &mtuMin, &mtuMax);
dump << "MTU: " << mtuCur << ZT_EOL_S;
tmp = SCNetworkInterfaceGetHardwareAddressString(iface);
CFStringGetCString(tmp, stringBuffer, sizeof(stringBuffer), kCFStringEncodingUTF8);
dump << "MAC: " << stringBuffer << ZT_EOL_S;
tmp = SCNetworkInterfaceGetInterfaceType(iface);
CFStringGetCString(tmp, stringBuffer, sizeof(stringBuffer), kCFStringEncodingUTF8);
dump << "Type: " << stringBuffer << ZT_EOL_S;
dump << "Addresses:" << ZT_EOL_S;
struct ifaddrs *ifap, *ifa;
void* addr;
getifaddrs(&ifap);
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (strcmp(ifName.c_str(), ifa->ifa_name) == 0) {
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in* ipv4 = (struct sockaddr_in*)ifa->ifa_addr;
addr = &ipv4->sin_addr;
}
else if (ifa->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)ifa->ifa_addr;
addr = &ipv6->sin6_addr;
}
else {
continue;
}
inet_ntop(ifa->ifa_addr->sa_family, addr, stringBuffer, sizeof(stringBuffer));
dump << stringBuffer << ZT_EOL_S;
}
}
dump << ZT_EOL_S;
}
FSRef fsref;
UInt8 path[PATH_MAX];
if (FSFindFolder(kUserDomain, kDesktopFolderType, kDontCreateFolder, &fsref) == noErr && FSRefMakePath(&fsref, path, sizeof(path)) == noErr) {}
else if (getenv("SUDO_USER")) {
snprintf((char*)path, sizeof(path), "/Users/%s/Desktop", getenv("SUDO_USER"));
}
else {
fprintf(stdout, "%s", dump.str().c_str());
return 0;
}
char dumpfile[PATH_MAX];
snprintf(dumpfile, sizeof(dumpfile), "%s%szerotier_dump.txt", (char*)path, ZT_PATH_SEPARATOR_S);
fprintf(stdout, "Writing dump to: %s\n", dumpfile);
int fd = open(dumpfile, O_CREAT | O_WRONLY | O_TRUNC, 0664);
if (fd == -1) {
perror("Error creating file");
return 1;
}
write(fd, dump.str().c_str(), dump.str().size());
close(fd);
#elif defined(_WIN32)
ULONG buffLen = 16384;
PIP_ADAPTER_ADDRESSES addresses;
ULONG ret = 0;
do {
addresses = (PIP_ADAPTER_ADDRESSES)malloc(buffLen);
ret = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, addresses, &buffLen);
if (ret == ERROR_BUFFER_OVERFLOW) {
free(addresses);
addresses = NULL;
}
else {
break;
}
} while (ret == ERROR_BUFFER_OVERFLOW);
int i = 0;
if (ret == NO_ERROR) {
PIP_ADAPTER_ADDRESSES curAddr = addresses;
while (curAddr) {
dump << "Interface " << i << ZT_EOL_S << "-----------" << ZT_EOL_S;
dump << "Name: " << curAddr->AdapterName << ZT_EOL_S;
dump << "MTU: " << curAddr->Mtu << ZT_EOL_S;
dump << "MAC: ";
char macBuffer[64] = {};
snprintf(
macBuffer,
sizeof(macBuffer),
"%02x:%02x:%02x:%02x:%02x:%02x",
curAddr->PhysicalAddress[0],
curAddr->PhysicalAddress[1],
curAddr->PhysicalAddress[2],
curAddr->PhysicalAddress[3],
curAddr->PhysicalAddress[4],
curAddr->PhysicalAddress[5]);
dump << macBuffer << ZT_EOL_S;
dump << "Type: " << curAddr->IfType << ZT_EOL_S;
dump << "Addresses:" << ZT_EOL_S;
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
pUnicast = curAddr->FirstUnicastAddress;
if (pUnicast) {
for (int j = 0; pUnicast != NULL; ++j) {
char buf[128] = {};
DWORD bufLen = 128;
LPSOCKADDR a = pUnicast->Address.lpSockaddr;
WSAAddressToStringA(pUnicast->Address.lpSockaddr, pUnicast->Address.iSockaddrLength, NULL, buf, &bufLen);
dump << buf << ZT_EOL_S;
pUnicast = pUnicast->Next;
}
}
curAddr = curAddr->Next;
++i;
}
}
if (addresses) {
free(addresses);
addresses = NULL;
}
char path[MAX_PATH + 1] = {};
if (SHGetFolderPathA(NULL, CSIDL_DESKTOP, NULL, 0, path) == S_OK) {
snprintf(path, sizeof(path), "%s%szerotier_dump.txt", path, ZT_PATH_SEPARATOR_S);
fprintf(stdout, "Writing dump to: %s\n", path);
HANDLE file = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE) {
fprintf(stdout, "%s", dump.str().c_str());
return 0;
}
BOOL err = WriteFile(file, dump.str().c_str(), dump.str().size(), NULL, NULL);
if (err = FALSE) {
fprintf(stderr, "Error writing file");
return 1;
}
CloseHandle(file);
}
else {
fprintf(stdout, "%s", dump.str().c_str());
}
#elif defined(__LINUX__)
struct ifreq ifr;
struct ifconf ifc;
char buf[1024];
char stringBuffer[128];
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
ioctl(sock, SIOCGIFCONF, &ifc);
struct ifreq* it = ifc.ifc_req;
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
int count = 0;
for (; it != end; ++it) {
strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // skip loopback
dump << "Interface " << count++ << ZT_EOL_S << "-----------" << ZT_EOL_S;
dump << "Name: " << ifr.ifr_name << ZT_EOL_S;
if (ioctl(sock, SIOCGIFMTU, &ifr) == 0) {
dump << "MTU: " << ifr.ifr_mtu << ZT_EOL_S;
}
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
unsigned char mac_addr[6];
memcpy(mac_addr, ifr.ifr_hwaddr.sa_data, 6);
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
dump << "MAC: " << macStr << ZT_EOL_S;
}
dump << "Addresses: " << ZT_EOL_S;
struct ifaddrs *ifap, *ifa;
void* addr;
getifaddrs(&ifap);
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (strcmp(ifr.ifr_name, ifa->ifa_name) == 0 && ifa->ifa_addr != NULL) {
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in* ipv4 = (struct sockaddr_in*)ifa->ifa_addr;
addr = &ipv4->sin_addr;
}
else if (ifa->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)ifa->ifa_addr;
addr = &ipv6->sin6_addr;
}
else {
continue;
}
inet_ntop(ifa->ifa_addr->sa_family, addr, stringBuffer, sizeof(stringBuffer));
dump << stringBuffer << ZT_EOL_S;
}
}
}
}
}
close(sock);
char cwd[16384];
if (getcwd(cwd, sizeof(cwd)) == nullptr) {
strcpy(cwd, ".");
}
char dumpfile[sizeof(cwd) + 32];
snprintf(dumpfile, sizeof(dumpfile), "%s%szerotier_dump.txt", cwd, ZT_PATH_SEPARATOR_S);
fprintf(stdout, "Writing dump to: %s\n", dumpfile);
int fd = open(dumpfile, O_CREAT | O_WRONLY | O_TRUNC, 0664);
if (fd == -1) {
perror("Error creating file");
return 1;
}
write(fd, dump.str().c_str(), dump.str().size());
close(fd);
#else
fprintf(stderr, "%s", dump.str().c_str());
#endif
// fprintf(stderr, "%s\n", dump.str().c_str());
}
else {
cliPrintHelp(argv[0], stderr);
return 0;
}
return 0;
}
/****************************************************************************/
/* zerotier-idtool personality */
/****************************************************************************/
static void idtoolPrintHelp(FILE* out, const char* pn)
{
fprintf(out, "%s version %d.%d.%d" ZT_EOL_S, PROGRAM_NAME, ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION);
fprintf(out, COPYRIGHT_NOTICE ZT_EOL_S LICENSE_GRANT ZT_EOL_S);
fprintf(out, "Usage: %s <command> [<args>]" ZT_EOL_S "" ZT_EOL_S "Commands:" ZT_EOL_S, pn);
fprintf(out, " generate [<identity.secret>] [<identity.public>] [<vanity>]" ZT_EOL_S);
fprintf(out, " validate <identity.secret/public>" ZT_EOL_S);
fprintf(out, " getpublic <identity.secret>" ZT_EOL_S);
fprintf(out, " sign <identity.secret> <file>" ZT_EOL_S);
fprintf(out, " verify <identity.secret/public> <file> <signature>" ZT_EOL_S);
fprintf(out, " initmoon <identity.public of first seed>" ZT_EOL_S);
fprintf(out, " genmoon <moon json>" ZT_EOL_S);
}
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.c_str()))
return id;
}
}
return Identity();
}
#ifdef __WINDOWS__
static int idtool(int argc, _TCHAR* argv[])
#else
static int idtool(int argc, char** argv)
#endif
{
if (argc < 2) {
idtoolPrintHelp(stdout, argv[0]);
return 1;
}
if (! strcmp(argv[1], "generate")) {
uint64_t vanity = 0;
int vanityBits = 0;
if (argc >= 5) {
vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL;
vanityBits = 4 * (int)strlen(argv[4]);
if (vanityBits > 40)
vanityBits = 40;
}
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)));
}
}
char idtmp[1024];
std::string idser = id.toString(true, idtmp);
if (argc >= 3) {
if (! OSUtils::writeFile(argv[2], idser)) {
fprintf(stderr, "Error writing to %s" ZT_EOL_S, argv[2]);
return 1;
}
else
printf("%s written" ZT_EOL_S, argv[2]);
if (argc >= 4) {
idser = id.toString(false, idtmp);
if (! OSUtils::writeFile(argv[3], idser)) {
fprintf(stderr, "Error writing to %s" ZT_EOL_S, argv[3]);
return 1;
}
else
printf("%s written" ZT_EOL_S, argv[3]);
}
}
else
printf("%s", idser.c_str());
}
else if (! strcmp(argv[1], "validate")) {
if (argc < 3) {
idtoolPrintHelp(stdout, argv[0]);
return 1;
}
Identity id = getIdFromArg(argv[2]);
if (! id) {
fprintf(stderr, "Identity argument invalid or file unreadable: %s" ZT_EOL_S, argv[2]);
return 1;
}
if (! id.locallyValidate()) {
fprintf(stderr, "%s FAILED validation." ZT_EOL_S, argv[2]);
return 1;
}
else
printf("%s is a valid identity" ZT_EOL_S, argv[2]);
}
else if (! strcmp(argv[1], "getpublic")) {
if (argc < 3) {
idtoolPrintHelp(stdout, argv[0]);
return 1;
}
Identity id = getIdFromArg(argv[2]);
if (! id) {
fprintf(stderr, "Identity argument invalid or file unreadable: %s" ZT_EOL_S, argv[2]);
return 1;
}
char idtmp[1024];
printf("%s", id.toString(false, idtmp));
}
else if (! strcmp(argv[1], "sign")) {
if (argc < 4) {
idtoolPrintHelp(stdout, argv[0]);
return 1;
}
Identity id = getIdFromArg(argv[2]);
if (! id) {
fprintf(stderr, "Identity argument invalid or file unreadable: %s" ZT_EOL_S, argv[2]);
return 1;
}
if (! id.hasPrivate()) {
fprintf(stderr, "%s does not contain a private key (must use private to sign)" ZT_EOL_S, argv[2]);
return 1;
}
std::string inf;
if (! OSUtils::readFile(argv[3], inf)) {
fprintf(stderr, "%s is not readable" ZT_EOL_S, argv[3]);
return 1;
}
ECC::Signature signature = id.sign(inf.data(), (unsigned int)inf.length());
char hexbuf[1024];
printf("%s", Utils::hex(signature.data, ZT_ECC_SIGNATURE_LEN, hexbuf));
}
else if (! strcmp(argv[1], "verify")) {
if (argc < 5) {
idtoolPrintHelp(stdout, argv[0]);
return 1;
}
Identity id = getIdFromArg(argv[2]);
if (! id) {
fprintf(stderr, "Identity argument invalid or file unreadable: %s" ZT_EOL_S, argv[2]);
return 1;
}
std::string inf;
if (! OSUtils::readFile(argv[3], inf)) {
fprintf(stderr, "%s is not readable" ZT_EOL_S, argv[3]);
return 1;
}
char buf[4096];
std::string signature(buf, Utils::unhex(argv[4], buf, (unsigned int)sizeof(buf)));
if ((signature.length() > ZT_ADDRESS_LENGTH) && (id.verify(inf.data(), (unsigned int)inf.length(), signature.data(), (unsigned int)signature.length()))) {
printf("%s signature valid" ZT_EOL_S, argv[3]);
}
else {
signature.clear();
if (OSUtils::readFile(argv[4], signature)) {
signature.assign(buf, Utils::unhex(signature.c_str(), buf, (unsigned int)sizeof(buf)));
if ((signature.length() > ZT_ADDRESS_LENGTH) && (id.verify(inf.data(), (unsigned int)inf.length(), signature.data(), (unsigned int)signature.length()))) {
printf("%s signature valid" ZT_EOL_S, argv[3]);
}
else {
fprintf(stderr, "%s signature check FAILED" ZT_EOL_S, argv[3]);
return 1;
}
}
else {
fprintf(stderr, "%s signature check FAILED" ZT_EOL_S, argv[3]);
return 1;
}
}
}
else if (! strcmp(argv[1], "initmoon")) {
if (argc < 3) {
idtoolPrintHelp(stdout, argv[0]);
}
else {
const Identity id = getIdFromArg(argv[2]);
if (! id) {
fprintf(stderr, "%s is not a valid identity" ZT_EOL_S, argv[2]);
return 1;
}
ECC::Pair kp(ECC::generate());
char idtmp[4096];
nlohmann::json mj;
mj["objtype"] = "world";
mj["worldType"] = "moon";
mj["updatesMustBeSignedBy"] = mj["signingKey"] = Utils::hex(kp.pub.data, ZT_ECC_PUBLIC_KEY_SET_LEN, idtmp);
mj["signingKey_SECRET"] = Utils::hex(kp.priv.data, ZT_ECC_PRIVATE_KEY_SET_LEN, idtmp);
mj["id"] = id.address().toString(idtmp);
nlohmann::json seedj;
seedj["identity"] = id.toString(false, idtmp);
seedj["stableEndpoints"] = nlohmann::json::array();
(mj["roots"] = nlohmann::json::array()).push_back(seedj);
std::string mjd(OSUtils::jsonDump(mj));
printf("%s" ZT_EOL_S, mjd.c_str());
}
}
else if (! strcmp(argv[1], "genmoon")) {
if (argc < 3) {
idtoolPrintHelp(stdout, argv[0]);
}
else {
std::string buf;
if (! OSUtils::readFile(argv[2], buf)) {
fprintf(stderr, "cannot read %s" ZT_EOL_S, argv[2]);
return 1;
}
nlohmann::json mj(OSUtils::jsonParse(buf));
const uint64_t id = Utils::hexStrToU64(OSUtils::jsonString(mj["id"], "0").c_str());
if (! id) {
fprintf(stderr, "ID in %s is invalid" ZT_EOL_S, argv[2]);
return 1;
}
World::Type t;
if (mj["worldType"] == "moon") {
t = World::TYPE_MOON;
}
else if (mj["worldType"] == "planet") {
t = World::TYPE_PLANET;
}
else {
fprintf(stderr, "invalid worldType" ZT_EOL_S);
return 1;
}
ECC::Pair signingKey;
ECC::Public updatesMustBeSignedBy;
Utils::unhex(OSUtils::jsonString(mj["signingKey"], "").c_str(), signingKey.pub.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
Utils::unhex(OSUtils::jsonString(mj["signingKey_SECRET"], "").c_str(), signingKey.priv.data, ZT_ECC_PRIVATE_KEY_SET_LEN);
Utils::unhex(OSUtils::jsonString(mj["updatesMustBeSignedBy"], "").c_str(), updatesMustBeSignedBy.data, ZT_ECC_PUBLIC_KEY_SET_LEN);
std::vector<World::Root> roots;
nlohmann::json& rootsj = mj["roots"];
if (rootsj.is_array()) {
for (unsigned long i = 0; i < (unsigned long)rootsj.size(); ++i) {
nlohmann::json& r = rootsj[i];
if (r.is_object()) {
roots.push_back(World::Root());
roots.back().identity = Identity(OSUtils::jsonString(r["identity"], "").c_str());
nlohmann::json& stableEndpointsj = r["stableEndpoints"];
if (stableEndpointsj.is_array()) {
for (unsigned long k = 0; k < (unsigned long)stableEndpointsj.size(); ++k)
roots.back().stableEndpoints.push_back(InetAddress(OSUtils::jsonString(stableEndpointsj[k], "").c_str()));
std::sort(roots.back().stableEndpoints.begin(), roots.back().stableEndpoints.end());
}
}
}
}
std::sort(roots.begin(), roots.end());
const int64_t now = OSUtils::now();
World w(World::make(t, id, now, updatesMustBeSignedBy, roots, signingKey));
Buffer<ZT_WORLD_MAX_SERIALIZED_LENGTH> wbuf;
w.serialize(wbuf);
char fn[128];
OSUtils::ztsnprintf(fn, sizeof(fn), "%.16llx.moon", w.id());
OSUtils::writeFile(fn, wbuf.data(), wbuf.size());
printf("wrote %s (signed world with timestamp %llu)" ZT_EOL_S, fn, (unsigned long long)now);
}
}
else {
idtoolPrintHelp(stdout, argv[0]);
return 1;
}
return 0;
}
/****************************************************************************/
/* Unix helper functions and signal handlers */
/****************************************************************************/
#ifdef __UNIX_LIKE__
static void _sighandlerHup(int sig)
{
}
static void _sighandlerReallyQuit(int sig)
{
exit(0);
}
static void _sighandlerQuit(int sig)
{
alarm(5); // force exit after 5s
OneService* s = zt1Service;
if (s)
s->terminate();
else
exit(0);
}
#endif
// Drop privileges on Linux, if supported by libc etc. and "zerotier-one" user exists on system
#if defined(__LINUX__) && ! defined(ZT_NO_CAPABILITIES)
#ifndef PR_CAP_AMBIENT
#define PR_CAP_AMBIENT 47
#define PR_CAP_AMBIENT_IS_SET 1
#define PR_CAP_AMBIENT_RAISE 2
#define PR_CAP_AMBIENT_LOWER 3
#define PR_CAP_AMBIENT_CLEAR_ALL 4
#endif
#define ZT_LINUX_USER "zerotier-one"
#define ZT_HAVE_DROP_PRIVILEGES 1
namespace {
// libc doesn't export capset, it is instead located in libcap
// We ignore libcap and call it manually.
struct cap_header_struct {
__u32 version;
int pid;
};
struct cap_data_struct {
__u32 effective;
__u32 permitted;
__u32 inheritable;
};
static inline int _zt_capset(cap_header_struct* hdrp, cap_data_struct* datap)
{
return syscall(SYS_capset, hdrp, datap);
}
static void _notDropping(const char* procName, const std::string& homeDir)
{
struct stat buf;
if (lstat(homeDir.c_str(), &buf) < 0) {
if (buf.st_uid != 0 || buf.st_gid != 0) {
fprintf(stderr, "%s: FATAL: failed to drop privileges and can't run as root since privileges were previously dropped (home directory not owned by root)" ZT_EOL_S, procName);
exit(1);
}
}
fprintf(stderr, "%s: WARNING: failed to drop privileges (kernel may not support required prctl features), running as root" ZT_EOL_S, procName);
}
static int _setCapabilities(int flags)
{
cap_header_struct capheader = { _LINUX_CAPABILITY_VERSION_1, 0 };
cap_data_struct capdata;
capdata.inheritable = capdata.permitted = capdata.effective = flags;
return _zt_capset(&capheader, &capdata);
}
static void _recursiveChown(const char* path, uid_t uid, gid_t gid)
{
struct dirent de;
struct dirent* dptr;
lchown(path, uid, gid);
DIR* d = opendir(path);
if (! d)
return;
dptr = (struct dirent*)0;
for (;;) {
if (readdir_r(d, &de, &dptr) != 0)
break;
if (! dptr)
break;
if ((strcmp(dptr->d_name, ".") != 0) && (strcmp(dptr->d_name, "..") != 0) && (strlen(dptr->d_name) > 0)) {
std::string p(path);
p.push_back(ZT_PATH_SEPARATOR);
p.append(dptr->d_name);
_recursiveChown(p.c_str(), uid, gid); // will just fail and return on regular files
}
}
closedir(d);
}
static void dropPrivileges(const char* procName, const std::string& homeDir)
{
if (getuid() != 0)
return;
// dropPrivileges switches to zerotier-one user while retaining CAP_NET_ADMIN
// and CAP_NET_RAW capabilities.
struct passwd* targetUser = getpwnam(ZT_LINUX_USER);
if (! targetUser)
return;
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) {
// Kernel has no support for ambient capabilities.
_notDropping(procName, homeDir);
return;
}
if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS | SECBIT_NOROOT) < 0) {
_notDropping(procName, homeDir);
return;
}
// Change ownership of our home directory if everything looks good (does nothing if already chown'd)
_recursiveChown(homeDir.c_str(), targetUser->pw_uid, targetUser->pw_gid);
if (_setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID) | (1 << CAP_NET_BIND_SERVICE)) < 0) {
_notDropping(procName, homeDir);
return;
}
int oldDumpable = prctl(PR_GET_DUMPABLE);
if (prctl(PR_SET_DUMPABLE, 0) < 0) {
// Disable ptracing. Otherwise there is a small window when previous
// compromised ZeroTier process could ptrace us, when we still have CAP_SETUID.
// (this is mitigated anyway on most distros by ptrace_scope=1)
fprintf(stderr, "%s: FATAL: prctl(PR_SET_DUMPABLE) failed while attempting to relinquish root permissions" ZT_EOL_S, procName);
exit(1);
}
// Relinquish root
if (setgid(targetUser->pw_gid) < 0) {
perror("setgid");
exit(1);
}
if (setuid(targetUser->pw_uid) < 0) {
perror("setuid");
exit(1);
}
if (_setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE)) < 0) {
fprintf(stderr, "%s: FATAL: unable to drop capabilities after relinquishing root" ZT_EOL_S, procName);
exit(1);
}
if (prctl(PR_SET_DUMPABLE, oldDumpable) < 0) {
fprintf(stderr, "%s: FATAL: prctl(PR_SET_DUMPABLE) failed while attempting to relinquish root permissions" ZT_EOL_S, procName);
exit(1);
}
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) < 0) {
fprintf(stderr, "%s: FATAL: prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_RAISE,CAP_NET_ADMIN) failed while attempting to relinquish root permissions" ZT_EOL_S, procName);
exit(1);
}
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0) < 0) {
fprintf(stderr, "%s: FATAL: prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_RAISE,CAP_NET_RAW) failed while attempting to relinquish root permissions" ZT_EOL_S, procName);
exit(1);
}
}
} // anonymous namespace
#endif // __LINUX__
/****************************************************************************/
/* Windows helper functions and signal handlers */
/****************************************************************************/
#ifdef __WINDOWS__
// Console signal handler routine to allow CTRL+C to work, mostly for testing
static BOOL WINAPI _winConsoleCtrlHandler(DWORD dwCtrlType)
{
switch (dwCtrlType) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_SHUTDOWN_EVENT:
OneService* s = zt1Service;
if (s)
s->terminate();
return TRUE;
}
return FALSE;
}
// TODO: revisit this with https://support.microsoft.com/en-us/help/947709/how-to-use-the-netsh-advfirewall-firewall-context-instead-of-the-netsh
static void _winPokeAHole()
{
char myPath[MAX_PATH];
DWORD ps = GetModuleFileNameA(NULL, myPath, sizeof(myPath));
if ((ps > 0) && (ps < (DWORD)sizeof(myPath))) {
STARTUPINFOA startupInfo;
PROCESS_INFORMATION processInfo;
startupInfo.cb = sizeof(startupInfo);
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
if (CreateProcessA(
NULL,
(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall delete rule name=\"ZeroTier One\" program=\"") + myPath + "\"").c_str(),
NULL,
NULL,
FALSE,
CREATE_NO_WINDOW,
NULL,
NULL,
&startupInfo,
&processInfo)) {
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
startupInfo.cb = sizeof(startupInfo);
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
if (CreateProcessA(
NULL,
(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=in action=allow program=\"") + myPath + "\" enable=yes").c_str(),
NULL,
NULL,
FALSE,
CREATE_NO_WINDOW,
NULL,
NULL,
&startupInfo,
&processInfo)) {
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
startupInfo.cb = sizeof(startupInfo);
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
if (CreateProcessA(
NULL,
(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=out action=allow program=\"") + myPath + "\" enable=yes").c_str(),
NULL,
NULL,
FALSE,
CREATE_NO_WINDOW,
NULL,
NULL,
&startupInfo,
&processInfo)) {
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
}
}
// Returns true if this is running as the local administrator
static BOOL IsCurrentUserLocalAdministrator(void)
{
BOOL fReturn = FALSE;
DWORD dwStatus;
DWORD dwAccessMask;
DWORD dwAccessDesired;
DWORD dwACLSize;
DWORD dwStructureSize = sizeof(PRIVILEGE_SET);
PACL pACL = NULL;
PSID psidAdmin = NULL;
HANDLE hToken = NULL;
HANDLE hImpersonationToken = NULL;
PRIVILEGE_SET ps;
GENERIC_MAPPING GenericMapping;
PSECURITY_DESCRIPTOR psdAdmin = NULL;
SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY;
const DWORD ACCESS_READ = 1;
const DWORD ACCESS_WRITE = 2;
__try {
if (! OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, TRUE, &hToken)) {
if (GetLastError() != ERROR_NO_TOKEN)
__leave;
if (! OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hToken))
__leave;
}
if (! DuplicateToken(hToken, SecurityImpersonation, &hImpersonationToken))
__leave;
if (! AllocateAndInitializeSid(&SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdmin))
__leave;
psdAdmin = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if (psdAdmin == NULL)
__leave;
if (! InitializeSecurityDescriptor(psdAdmin, SECURITY_DESCRIPTOR_REVISION))
__leave;
dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psidAdmin) - sizeof(DWORD);
pACL = (PACL)LocalAlloc(LPTR, dwACLSize);
if (pACL == NULL)
__leave;
if (! InitializeAcl(pACL, dwACLSize, ACL_REVISION2))
__leave;
dwAccessMask = ACCESS_READ | ACCESS_WRITE;
if (! AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, psidAdmin))
__leave;
if (! SetSecurityDescriptorDacl(psdAdmin, TRUE, pACL, FALSE))
__leave;
SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE);
SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE);
if (! IsValidSecurityDescriptor(psdAdmin))
__leave;
dwAccessDesired = ACCESS_READ;
GenericMapping.GenericRead = ACCESS_READ;
GenericMapping.GenericWrite = ACCESS_WRITE;
GenericMapping.GenericExecute = 0;
GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE;
if (! AccessCheck(psdAdmin, hImpersonationToken, dwAccessDesired, &GenericMapping, &ps, &dwStructureSize, &dwStatus, &fReturn)) {
fReturn = FALSE;
__leave;
}
}
__finally {
// Clean up.
if (pACL)
LocalFree(pACL);
if (psdAdmin)
LocalFree(psdAdmin);
if (psidAdmin)
FreeSid(psidAdmin);
if (hImpersonationToken)
CloseHandle(hImpersonationToken);
if (hToken)
CloseHandle(hToken);
}
return fReturn;
}
#endif // __WINDOWS__
/****************************************************************************/
/* main() and friends */
/****************************************************************************/
static void printHelp(const char* cn, FILE* out)
{
fprintf(out, "%s version %d.%d.%d" ZT_EOL_S, PROGRAM_NAME, ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION);
fprintf(out, COPYRIGHT_NOTICE ZT_EOL_S LICENSE_GRANT ZT_EOL_S);
fprintf(out, ZT_EOL_S "Usage: %s [-switches] [home directory]" ZT_EOL_S "" ZT_EOL_S, cn);
fprintf(out, "Available switches:" ZT_EOL_S);
fprintf(out, " -h - Display this help" ZT_EOL_S);
fprintf(out, " -v - Show version" ZT_EOL_S);
fprintf(out, " -U - Skip privilege check and do not attempt to drop privileges" ZT_EOL_S);
fprintf(out, " -p<port> - Port for UDP and TCP/HTTP (default: 9993, 0 for random)" ZT_EOL_S);
#ifdef __UNIX_LIKE__
fprintf(out, " -d - Fork and run as daemon (Unix-ish OSes)" ZT_EOL_S);
#endif // __UNIX_LIKE__
#ifdef __WINDOWS__
fprintf(out, " -C - Run from command line instead of as service (Windows)" ZT_EOL_S);
fprintf(out, " -I - Install Windows service (Windows)" ZT_EOL_S);
fprintf(out, " -R - Uninstall Windows service (Windows)" ZT_EOL_S);
fprintf(out, " -D - Remove all instances of Windows tap device (Windows)" ZT_EOL_S);
#endif // __WINDOWS__
fprintf(out, " -i - Generate and manage identities (zerotier-idtool)" ZT_EOL_S);
fprintf(out, " -q - Query API (zerotier-cli)" ZT_EOL_S);
}
class _OneServiceRunner {
public:
_OneServiceRunner(const char* pn, const std::string& hd, unsigned int p) : progname(pn), returnValue(0), port(p), homeDir(hd)
{
}
void threadMain() throw()
{
try {
for (;;) {
zt1Service = OneService::newInstance(homeDir.c_str(), port);
switch (zt1Service->run()) {
case OneService::ONE_STILL_RUNNING: // shouldn't happen, run() won't return until done
case OneService::ONE_NORMAL_TERMINATION:
break;
case OneService::ONE_UNRECOVERABLE_ERROR:
fprintf(stderr, "%s: fatal error: %s" ZT_EOL_S, progname, zt1Service->fatalErrorMessage().c_str());
returnValue = 1;
break;
case OneService::ONE_IDENTITY_COLLISION: {
delete zt1Service;
zt1Service = (OneService*)0;
std::string oldid;
OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret").c_str(), oldid);
if (oldid.length()) {
OSUtils::writeFile((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret.saved_after_collision").c_str(), oldid);
OSUtils::rm((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret").c_str());
OSUtils::rm((homeDir + ZT_PATH_SEPARATOR_S + "identity.public").c_str());
}
}
continue; // restart!
}
break; // terminate loop -- normally we don't keep restarting
}
delete zt1Service;
zt1Service = (OneService*)0;
}
catch (...) {
fprintf(stderr, "%s: unexpected exception starting main OneService instance" ZT_EOL_S, progname);
returnValue = 1;
}
}
const char* progname;
unsigned int returnValue;
unsigned int port;
const std::string& homeDir;
};
#ifdef __WINDOWS__
int __cdecl _tmain(int argc, _TCHAR* argv[])
#else
int main(int argc, char** argv)
#endif
{
#if defined(__LINUX__) && ((! defined(__GLIBC__)) || ((__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 18)))
// This corrects for systems with abnormally small defaults (musl) and also
// shrinks the stack on systems with large defaults to save a bit of memory.
pthread_attr_t tattr;
pthread_attr_init(&tattr);
pthread_attr_setstacksize(&tattr, 1048576);
pthread_setattr_default_np(&tattr);
pthread_attr_destroy(&tattr);
#endif
#ifdef __UNIX_LIKE__
signal(SIGHUP, &_sighandlerHup);
signal(SIGPIPE, SIG_IGN);
signal(SIGIO, SIG_IGN);
signal(SIGUSR1, SIG_IGN);
signal(SIGUSR2, SIG_IGN);
signal(SIGALRM, &_sighandlerReallyQuit);
signal(SIGINT, &_sighandlerQuit);
signal(SIGTERM, &_sighandlerQuit);
signal(SIGQUIT, &_sighandlerQuit);
signal(SIGINT, &_sighandlerQuit);
#ifdef ZT_EXTOSDEP
int extosdepFd1 = -1;
int extosdepFd2 = -1;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-' || argv[i][1] != 'x')
continue;
if (sscanf(argv[i] + 2, "%d,%d", &extosdepFd1, &extosdepFd2) == 2)
break;
fprintf(stderr, "bad extosdepFd\n");
return 1;
}
#endif // ZT_EXTOSDEP
/* Ensure that there are no inherited file descriptors open from a previous
* incarnation. This is a hack to ensure that GitHub issue #61 or variants
* of it do not return, and should not do anything otherwise bad. */
{
int mfd = STDIN_FILENO;
if (STDOUT_FILENO > mfd)
mfd = STDOUT_FILENO;
if (STDERR_FILENO > mfd)
mfd = STDERR_FILENO;
for (int f = mfd + 1; f < 1024; ++f) {
#ifdef ZT_EXTOSDEP
if (f == extosdepFd1 || f == extosdepFd2)
continue;
#endif // ZT_EXTOSDEP
::close(f);
}
}
bool runAsDaemon = false;
#endif // __UNIX_LIKE__
#ifdef __WINDOWS__
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
}
#ifdef ZT_WIN_RUN_IN_CONSOLE
bool winRunFromCommandLine = true;
#else
bool winRunFromCommandLine = false;
#endif
#endif // __WINDOWS__
if ((strstr(argv[0], "zerotier-idtool")) || (strstr(argv[0], "ZEROTIER-IDTOOL")))
return idtool(argc, argv);
if ((strstr(argv[0], "zerotier-cli")) || (strstr(argv[0], "ZEROTIER-CLI")))
return cli(argc, argv);
std::string homeDir;
unsigned int port = ZT_DEFAULT_PORT;
bool skipRootCheck = false;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-') {
switch (argv[i][1]) {
case 'p': // port -- for both UDP and TCP, packets and control plane
port = Utils::strToUInt(argv[i] + 2);
if (port > 0xffff) {
printHelp(argv[0], stdout);
return 1;
}
break;
#ifdef __UNIX_LIKE__
case 'd': // Run in background as daemon
runAsDaemon = true;
break;
#endif // __UNIX_LIKE__
case 'U':
skipRootCheck = true;
break;
case 'v': // Display version
printf("%d.%d.%d" ZT_EOL_S, ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION);
return 0;
case 'i': // Invoke idtool personality
if (argv[i][2]) {
printHelp(argv[0], stdout);
return 0;
}
else
return idtool(argc - 1, argv + 1);
case 'q': // Invoke cli personality
if (argv[i][2]) {
printHelp(argv[0], stdout);
return 0;
}
else
return cli(argc, argv);
#ifdef __WINDOWS__
case 'C': // Run from command line instead of as Windows service
winRunFromCommandLine = true;
break;
case 'I': { // Install this binary as a Windows service
if (IsCurrentUserLocalAdministrator() != TRUE) {
fprintf(stderr, "%s: must be run as a local administrator." ZT_EOL_S, argv[0]);
return 1;
}
std::string ret(InstallService(ZT_SERVICE_NAME, ZT_SERVICE_DISPLAY_NAME, ZT_SERVICE_START_TYPE, ZT_SERVICE_DEPENDENCIES, ZT_SERVICE_ACCOUNT, ZT_SERVICE_PASSWORD));
if (ret.length()) {
fprintf(stderr, "%s: unable to install service: %s" ZT_EOL_S, argv[0], ret.c_str());
return 3;
}
return 0;
} break;
case 'R': { // Uninstall this binary as Windows service
if (IsCurrentUserLocalAdministrator() != TRUE) {
fprintf(stderr, "%s: must be run as a local administrator." ZT_EOL_S, argv[0]);
return 1;
}
std::string ret(UninstallService(ZT_SERVICE_NAME));
if (ret.length()) {
fprintf(stderr, "%s: unable to uninstall service: %s" ZT_EOL_S, argv[0], ret.c_str());
return 3;
}
return 0;
} break;
case 'D': {
std::string err = WindowsEthernetTap::destroyAllPersistentTapDevices();
if (err.length() > 0) {
fprintf(stderr, "%s: unable to uninstall one or more persistent tap devices: %s" ZT_EOL_S, argv[0], err.c_str());
return 3;
}
return 0;
} break;
#endif // __WINDOWS__
#ifdef ZT_EXTOSDEP
case 'x':
break;
#endif
case 'h':
case '?':
default:
printHelp(argv[0], stdout);
return 0;
}
}
else {
if (homeDir.length()) {
printHelp(argv[0], stdout);
return 0;
}
else {
homeDir = argv[i];
}
}
}
if (! homeDir.length())
homeDir = OneService::platformDefaultHomePath();
if (! homeDir.length()) {
fprintf(stderr, "%s: no home path specified and no platform default available" ZT_EOL_S, argv[0]);
return 1;
}
else {
std::vector<std::string> hpsp(OSUtils::split(homeDir.c_str(), ZT_PATH_SEPARATOR_S, "", ""));
std::string ptmp;
if (homeDir[0] == ZT_PATH_SEPARATOR)
ptmp.push_back(ZT_PATH_SEPARATOR);
for (std::vector<std::string>::iterator pi(hpsp.begin()); pi != hpsp.end(); ++pi) {
if (ptmp.length() > 0)
ptmp.push_back(ZT_PATH_SEPARATOR);
ptmp.append(*pi);
if ((*pi != ".") && (*pi != "..")) {
if (! OSUtils::mkdir(ptmp))
throw std::runtime_error("home path does not exist, and could not create. Please verify local system permissions.");
}
}
}
// Check and fix permissions on critical files at startup
try {
char p[4096];
OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "identity.secret", homeDir.c_str());
if (OSUtils::fileExists(p)) {
OSUtils::lockDownFile(p, false);
}
}
catch (...) {
}
try {
char p[4096];
OSUtils::ztsnprintf(p, sizeof(p), "%s" ZT_PATH_SEPARATOR_S "authtoken.secret", homeDir.c_str());
if (OSUtils::fileExists(p)) {
OSUtils::lockDownFile(p, false);
}
}
catch (...) {
}
// This can be removed once the new controller code has been around for many versions
if (OSUtils::fileExists((homeDir + ZT_PATH_SEPARATOR_S + "controller.db").c_str(), true)) {
fprintf(stderr, "%s: FATAL: an old controller.db exists in %s -- see instructions in controller/README.md for how to migrate!" ZT_EOL_S, argv[0], homeDir.c_str());
return 1;
}
#ifdef __UNIX_LIKE__
#ifndef ZT_ONE_NO_ROOT_CHECK
if ((! skipRootCheck) && (getuid() != 0)) {
fprintf(stderr, "%s: must be run as root (uid 0)" ZT_EOL_S, argv[0]);
return 1;
}
#endif // !ZT_ONE_NO_ROOT_CHECK
if (runAsDaemon) {
prometheus::simpleapi::saver.stop();
long p = (long)fork();
if (p < 0) {
fprintf(stderr, "%s: could not fork" ZT_EOL_S, argv[0]);
return 1;
}
else if (p > 0)
_Exit(0); // forked
// else p == 0, so we are daemonized
prometheus::simpleapi::saver.restart();
}
#endif // __UNIX_LIKE__
#ifdef __WINDOWS__
// Uninstall legacy tap devices. New devices will automatically be installed and configured
// when tap instances are created.
WindowsEthernetTap::destroyAllLegacyPersistentTapDevices();
if (winRunFromCommandLine) {
// Running in "interactive" mode (mostly for debugging)
if (IsCurrentUserLocalAdministrator() != TRUE) {
if (! skipRootCheck) {
fprintf(stderr, "%s: must be run as a local administrator." ZT_EOL_S, argv[0]);
return 1;
}
}
else {
_winPokeAHole();
}
SetConsoleCtrlHandler(&_winConsoleCtrlHandler, TRUE);
// continues on to ordinary command line execution code below...
}
else {
// Running from service manager
_winPokeAHole();
ZeroTierOneService zt1WindowsService;
if (CServiceBase::Run(zt1WindowsService) == TRUE) {
return 0;
}
else {
fprintf(stderr, "%s: unable to start service (try -h for help)" ZT_EOL_S, argv[0]);
return 1;
}
}
#endif // __WINDOWS__
#ifdef __UNIX_LIKE__
#ifdef ZT_HAVE_DROP_PRIVILEGES
if (! skipRootCheck)
dropPrivileges(argv[0], homeDir);
#endif
std::string pidPath(homeDir + ZT_PATH_SEPARATOR_S + ZT_PID_PATH);
{
// Write .pid file to home folder
FILE* pf = fopen(pidPath.c_str(), "w");
if (pf) {
fprintf(pf, "%ld", (long)getpid());
fclose(pf);
}
}
#endif // __UNIX_LIKE__
#ifdef ZT_EXTOSDEP
if (extosdepFd1 < 0) {
fprintf(stderr, "no extosdepFd specified\n");
OSUtils::rm(pidPath.c_str());
return 1;
}
ExtOsdep::init(extosdepFd1, extosdepFd2);
#endif
_OneServiceRunner thr(argv[0], homeDir, port);
thr.threadMain();
// Thread::join(Thread::start(&thr));
#ifdef __UNIX_LIKE__
OSUtils::rm(pidPath.c_str());
#endif
return thr.returnValue;
}