Merge branch 'zeroidc' into dev

This commit is contained in:
Adam Ierymenko 2022-01-20 10:43:37 -05:00
commit f8d7796099
No known key found for this signature in database
GPG Key ID: C8877CF2D7A5D7F3
32 changed files with 3559 additions and 408 deletions

2
.gitignore vendored
View File

@ -122,6 +122,8 @@ attic/world/*.c25519
attic/world/mkworld
workspace/
workspace2/
zeroidc/target/
tmp/
#snapcraft specifics
/parts/

View File

@ -95,7 +95,7 @@ public:
if(m_pool.size()==0){
if ((m_pool.size() + m_borrowed.size()) <= m_maxPoolSize) {
if ((m_pool.size() + m_borrowed.size()) < m_maxPoolSize) {
try {
std::shared_ptr<Connection> conn = m_factory->create();
m_borrowed.insert(conn);

View File

@ -40,6 +40,32 @@
namespace ZeroTier
{
struct AuthInfo
{
public:
AuthInfo()
: enabled(false)
, version(0)
, authenticationURL()
, authenticationExpiryTime(0)
, issuerURL()
, centralAuthURL()
, ssoNonce()
, ssoState()
, ssoClientID()
{}
bool enabled;
uint64_t version;
std::string authenticationURL;
uint64_t authenticationExpiryTime;
std::string issuerURL;
std::string centralAuthURL;
std::string ssoNonce;
std::string ssoState;
std::string ssoClientID;
};
/**
* Base class with common infrastructure for all controller DB implementations
*/
@ -108,7 +134,7 @@ public:
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId) = 0;
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) = 0;
virtual std::string getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL) { return ""; }
virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL) { return AuthInfo(); }
virtual void networkMemberSSOHasExpired(uint64_t nwid, int64_t ts);
inline void addListener(DB::ChangeListener *const listener)

View File

@ -125,16 +125,16 @@ bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vect
return false;
}
std::string DBMirrorSet::getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL)
AuthInfo DBMirrorSet::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL)
{
std::lock_guard<std::mutex> l(_dbs_l);
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
std::string url = (*d)->getSSOAuthURL(member, redirectURL);
if (!url.empty()) {
return url;
AuthInfo info = (*d)->getSSOAuthInfo(member, redirectURL);
if (info.enabled) {
return info;
}
}
return "";
return AuthInfo();
}
void DBMirrorSet::networkMemberSSOHasExpired(uint64_t nwid, int64_t ts)

View File

@ -51,7 +51,7 @@ public:
virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member);
virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId);
std::string getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL);
AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
void networkMemberSSOHasExpired(uint64_t nwid, int64_t ts);
inline void addDB(const std::shared_ptr<DB> &db)

View File

@ -63,29 +63,6 @@ namespace ZeroTier {
namespace {
std::string url_encode(const std::string &value) {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
static json _renderRule(ZT_VirtualNetworkRule &rule)
{
char tmp[128];
@ -503,7 +480,7 @@ EmbeddedNetworkController::~EmbeddedNetworkController()
}
void EmbeddedNetworkController::setSSORedirectURL(const std::string &url) {
_ssoRedirectURL = url_encode(url);
_ssoRedirectURL = url;
}
void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
@ -1360,29 +1337,61 @@ void EmbeddedNetworkController::_request(
// Otherwise no, we use standard auth logic.
bool networkSSOEnabled = OSUtils::jsonBool(network["ssoEnabled"], false);
bool memberSSOExempt = OSUtils::jsonBool(member["ssoExempt"], false);
std::string authenticationURL;
AuthInfo info;
if (networkSSOEnabled && !memberSSOExempt) {
authenticationURL = _db.getSSOAuthURL(member, _ssoRedirectURL);
// TODO: Get expiry time if auth is still valid
// else get new auth info & stuff
info = _db.getSSOAuthInfo(member, _ssoRedirectURL);
assert(info.enabled == networkSSOEnabled);
std::string memberId = member["id"];
//fprintf(stderr, "ssoEnabled && !ssoExempt %s-%s\n", nwids, memberId.c_str());
uint64_t authenticationExpiryTime = (int64_t)OSUtils::jsonInt(member["authenticationExpiryTime"], 0);
//fprintf(stderr, "authExpiryTime: %lld\n", authenticationExpiryTime);
fprintf(stderr, "authExpiryTime: %lld\n", authenticationExpiryTime);
if (authenticationExpiryTime < now) {
if (!authenticationURL.empty()) {
fprintf(stderr, "Handling expired member\n");
if (info.version == 0) {
if (!info.authenticationURL.empty()) {
_db.networkMemberSSOHasExpired(nwid, now);
onNetworkMemberDeauthorize(&_db, nwid, identity.address().toInt());
Dictionary<4096> authInfo;
authInfo.add(ZT_AUTHINFO_DICT_KEY_VERSION, (uint64_t)0ULL);
authInfo.add(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, info.authenticationURL.c_str());
//fprintf(stderr, "sending auth URL: %s\n", authenticationURL.c_str());
DB::cleanMember(member);
_db.save(member,true);
_sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, authInfo.data(), authInfo.sizeBytes());
return;
}
}
else if (info.version == 1) {
_db.networkMemberSSOHasExpired(nwid, now);
onNetworkMemberDeauthorize(&_db, nwid, identity.address().toInt());
Dictionary<3072> authInfo;
authInfo.add("aU", authenticationURL.c_str());
//fprintf(stderr, "sending auth URL: %s\n", authenticationURL.c_str());
Dictionary<8192> authInfo;
authInfo.add(ZT_AUTHINFO_DICT_KEY_VERSION, info.version);
authInfo.add(ZT_AUTHINFO_DICT_KEY_ISSUER_URL, info.issuerURL.c_str());
authInfo.add(ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL, info.centralAuthURL.c_str());
authInfo.add(ZT_AUTHINFO_DICT_KEY_NONCE, info.ssoNonce.c_str());
authInfo.add(ZT_AUTHINFO_DICT_KEY_STATE, info.ssoState.c_str());
authInfo.add(ZT_AUTHINFO_DICT_KEY_CLIENT_ID, info.ssoClientID.c_str());
DB::cleanMember(member);
_db.save(member,true);
_db.save(member, true);
fprintf(stderr, "Sending NC_ERROR_AUTHENTICATION_REQUIRED\n");
_sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED, authInfo.data(), authInfo.sizeBytes());
return;
}
else {
fprintf(stderr, "invalid sso info.version %llu\n", info.version);
}
} else if (authorized) {
fprintf(stderr, "Setting member will expire to: %lld\n", authenticationExpiryTime);
_db.memberWillExpire(authenticationExpiryTime, nwid, identity.address().toInt());
}
}
@ -1452,9 +1461,36 @@ void EmbeddedNetworkController::_request(
nc->multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL);
nc->ssoEnabled = OSUtils::jsonBool(network["ssoEnabled"], false);
nc->authenticationExpiryTime = OSUtils::jsonInt(member["authenticationExpiryTime"], 0LL);
if (!authenticationURL.empty())
Utils::scopy(nc->authenticationURL, sizeof(nc->authenticationURL), authenticationURL.c_str());
nc->ssoVersion = info.version;
if (info.version == 0) {
nc->authenticationExpiryTime = OSUtils::jsonInt(member["authenticationExpiryTime"], 0LL);
if (!info.authenticationURL.empty()) {
Utils::scopy(nc->authenticationURL, sizeof(nc->authenticationURL), info.authenticationURL.c_str());
}
}
else if (info.version == 1) {
nc->authenticationExpiryTime = OSUtils::jsonInt(member["authenticationExpiryTime"], 0LL);
if (!info.authenticationURL.empty()) {
Utils::scopy(nc->authenticationURL, sizeof(nc->authenticationURL), info.authenticationURL.c_str());
}
if (!info.centralAuthURL.empty()) {
Utils::scopy(nc->centralAuthURL, sizeof(nc->centralAuthURL), info.centralAuthURL.c_str());
}
if (!info.issuerURL.empty()) {
fprintf(stderr, "copying issuerURL to nc: %s\n", info.issuerURL.c_str());
Utils::scopy(nc->issuerURL, sizeof(nc->issuerURL), info.issuerURL.c_str());
}
if (!info.ssoNonce.empty()) {
Utils::scopy(nc->ssoNonce, sizeof(nc->ssoNonce), info.ssoNonce.c_str());
}
if (!info.ssoState.empty()) {
Utils::scopy(nc->ssoState, sizeof(nc->ssoState), info.ssoState.c_str());
}
if (!info.ssoClientID.empty()) {
Utils::scopy(nc->ssoClientID, sizeof(nc->ssoClientID), info.ssoClientID.c_str());
}
}
std::string rtt(OSUtils::jsonString(member["remoteTraceTarget"],""));
if (rtt.length() == 10) {

View File

@ -23,6 +23,7 @@
#include <libpq-fe.h>
#include <sstream>
#include <iomanip>
#include <climits>
#include <chrono>
@ -80,6 +81,28 @@ std::vector<std::string> split(std::string str, char delim){
return tokens;
}
std::string url_encode(const std::string &value) {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
} // anonymous namespace
@ -336,7 +359,7 @@ void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId,
}
}
std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL)
AuthInfo PostgreSQL::getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL)
{
// NONCE is just a random character string. no semantic meaning
// state = HMAC SHA384 of Nonce based on shared sso key
@ -347,10 +370,12 @@ std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::s
// how do we tell when a nonce is used? if auth_expiration_time is set
std::string networkId = member["nwid"];
std::string memberId = member["id"];
char authenticationURL[4096] = {0};
//fprintf(stderr, "PostgreSQL::updateMemberOnLoad: %s-%s\n", networkId.c_str(), memberId.c_str());
bool have_auth = false;
char authenticationURL[4096] = {0};
AuthInfo info;
info.enabled = true;
// fprintf(stderr, "PostgreSQL::updateMemberOnLoad: %s-%s\n", networkId.c_str(), memberId.c_str());
try {
auto c = _pool->borrow();
pqxx::work w(*c->c);
@ -390,38 +415,61 @@ std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::s
exit(6);
}
r = w.exec_params("SELECT org.client_id, org.authorization_endpoint "
r = w.exec_params("SELECT org.client_id, org.authorization_endpoint, org.issuer, org.sso_impl_version "
"FROM ztc_network AS nw, ztc_org AS org "
"WHERE nw.id = $1 AND nw.sso_enabled = true AND org.owner_id = nw.owner_id", networkId);
std::string client_id = "";
std::string authorization_endpoint = "";
std::string issuer = "";
uint64_t sso_version = 0;
if (r.size() == 1) {
client_id = r.at(0)[0].as<std::string>();
authorization_endpoint = r.at(0)[1].as<std::string>();
issuer = r.at(0)[2].as<std::string>();
sso_version = r.at(0)[3].as<uint64_t>();
} else if (r.size() > 1) {
fprintf(stderr, "ERROR: More than one auth endpoint for an organization?!?!? NetworkID: %s\n", networkId.c_str());
} else {
fprintf(stderr, "No client or auth endpoint?!?\n");
}
info.version = sso_version;
// no catch all else because we don't actually care if no records exist here. just continue as normal.
if ((!client_id.empty())&&(!authorization_endpoint.empty())) {
have_auth = true;
uint8_t state[48];
HMACSHA384(_ssoPsk, nonceBytes, sizeof(nonceBytes), state);
char state_hex[256];
Utils::hex(state, 48, state_hex);
OSUtils::ztsnprintf(authenticationURL, sizeof(authenticationURL),
"%s?response_type=id_token&response_mode=form_post&scope=openid+email+profile&redirect_uri=%s&nonce=%s&state=%s&client_id=%s",
authorization_endpoint.c_str(),
redirectURL.c_str(),
nonce.c_str(),
state_hex,
client_id.c_str());
if (info.version == 0) {
char url[2048] = {0};
OSUtils::ztsnprintf(url, sizeof(authenticationURL),
"%s?response_type=id_token&response_mode=form_post&scope=openid+email+profile&redirect_uri=%s&nonce=%s&state=%s&client_id=%s",
authorization_endpoint.c_str(),
url_encode(redirectURL).c_str(),
nonce.c_str(),
state_hex,
client_id.c_str());
info.authenticationURL = std::string(url);
} else if (info.version == 1) {
info.ssoClientID = client_id;
info.issuerURL = issuer;
info.ssoNonce = nonce;
info.ssoState = std::string(state_hex) + "_" +networkId;
info.centralAuthURL = redirectURL;
fprintf(
stderr,
"ssoClientID: %s\nissuerURL: %s\nssoNonce: %s\nssoState: %s\ncentralAuthURL: %s\n",
info.ssoClientID.c_str(),
info.issuerURL.c_str(),
info.ssoNonce.c_str(),
info.ssoState.c_str(),
info.centralAuthURL.c_str());
}
} else {
fprintf(stderr, "client_id: %s\nauthorization_endpoint: %s\n", client_id.c_str(), authorization_endpoint.c_str());
}
@ -432,7 +480,7 @@ std::string PostgreSQL::getSSOAuthURL(const nlohmann::json &member, const std::s
fprintf(stderr, "ERROR: Error updating member on load: %s\n", e.what());
}
return std::string(authenticationURL);
return info; //std::string(authenticationURL);
}
void PostgreSQL::initializeNetworks()
@ -1025,23 +1073,47 @@ void PostgreSQL::commitThread()
fprintf(stderr, "not an object\n");
continue;
}
std::shared_ptr<PostgresConnection> c;
try {
c = _pool->borrow();
} catch (std::exception &e) {
fprintf(stderr, "ERROR: %s\n", e.what());
continue;
}
if (!c) {
fprintf(stderr, "Error getting database connection\n");
continue;
}
try {
nlohmann::json *config = &(qitem.first);
const std::string objtype = (*config)["objtype"];
nlohmann::json &config = (qitem.first);
const std::string objtype = config["objtype"];
if (objtype == "member") {
// fprintf(stderr, "%s: commitThread: member\n", _myAddressStr.c_str());
std::string memberId;
std::string networkId;
try {
auto c = _pool->borrow();
pqxx::work w(*c->c);
std::string memberId = (*config)["id"];
std::string networkId = (*config)["nwid"];
memberId = config["id"];
networkId = config["nwid"];
std::string target = "NULL";
if (!(*config)["remoteTraceTarget"].is_null()) {
target = (*config)["remoteTraceTarget"];
if (!config["remoteTraceTarget"].is_null()) {
target = config["remoteTraceTarget"];
}
pqxx::row nwrow = w.exec_params1("SELECT COUNT(id) FROM ztc_network WHERE id = $1", networkId);
int nwcount = nwrow[0].as<int>();
if (nwcount != 1) {
fprintf(stderr, "network %s does not exist. skipping member upsert\n", networkId.c_str());
w.abort();
_pool->unborrow(c);
continue;
}
pqxx::result res = w.exec_params0(
"INSERT INTO ztc_member (id, network_id, active_bridge, authorized, capabilities, "
@ -1058,21 +1130,21 @@ void PostgreSQL::commitThread()
"v_minor = EXCLUDED.v_minor, v_rev = EXCLUDED.v_rev, v_proto = EXCLUDED.v_proto",
memberId,
networkId,
(bool)(*config)["activeBridge"],
(bool)(*config)["authorized"],
OSUtils::jsonDump((*config)["capabilities"], -1),
OSUtils::jsonString((*config)["identity"], ""),
(uint64_t)(*config)["lastAuthorizedTime"],
(uint64_t)(*config)["lastDeauthorizedTime"],
(bool)(*config)["noAutoAssignIps"],
(int)(*config)["remoteTraceLevel"],
(bool)config["activeBridge"],
(bool)config["authorized"],
OSUtils::jsonDump(config["capabilities"], -1),
OSUtils::jsonString(config["identity"], ""),
(uint64_t)config["lastAuthorizedTime"],
(uint64_t)config["lastDeauthorizedTime"],
(bool)config["noAutoAssignIps"],
(int)config["remoteTraceLevel"],
target,
(uint64_t)(*config)["revision"],
OSUtils::jsonDump((*config)["tags"], -1),
(int)(*config)["vMajor"],
(int)(*config)["vMinor"],
(int)(*config)["vRev"],
(int)(*config)["vProto"]);
(uint64_t)config["revision"],
OSUtils::jsonDump(config["tags"], -1),
(int)config["vMajor"],
(int)config["vMinor"],
(int)config["vRev"],
(int)config["vProto"]);
res = w.exec_params0("DELETE FROM ztc_member_ip_assignment WHERE member_id = $1 AND network_id = $2",
@ -1080,7 +1152,7 @@ void PostgreSQL::commitThread()
std::vector<std::string> assignments;
bool ipAssignError = false;
for (auto i = (*config)["ipAssignments"].begin(); i != (*config)["ipAssignments"].end(); ++i) {
for (auto i = config["ipAssignments"].begin(); i != config["ipAssignments"].end(); ++i) {
std::string addr = *i;
if (std::find(assignments.begin(), assignments.end(), addr) != assignments.end()) {
@ -1095,21 +1167,21 @@ void PostgreSQL::commitThread()
}
if (ipAssignError) {
fprintf(stderr, "%s: ipAssignError\n", _myAddressStr.c_str());
delete config;
config = nullptr;
w.abort();
_pool->unborrow(c);
c.reset();
continue;
}
w.commit();
_pool->unborrow(c);
const uint64_t nwidInt = OSUtils::jsonIntHex((*config)["nwid"], 0ULL);
const uint64_t memberidInt = OSUtils::jsonIntHex((*config)["id"], 0ULL);
const uint64_t nwidInt = OSUtils::jsonIntHex(config["nwid"], 0ULL);
const uint64_t memberidInt = OSUtils::jsonIntHex(config["id"], 0ULL);
if (nwidInt && memberidInt) {
nlohmann::json nwOrig;
nlohmann::json memOrig;
nlohmann::json memNew(*config);
nlohmann::json memNew(config);
get(nwidInt, nwOrig, memberidInt, memOrig);
@ -1117,24 +1189,22 @@ void PostgreSQL::commitThread()
} else {
fprintf(stderr, "%s: Can't notify of change. Error parsing nwid or memberid: %llu-%llu\n", _myAddressStr.c_str(), (unsigned long long)nwidInt, (unsigned long long)memberidInt);
}
} catch (std::exception &e) {
fprintf(stderr, "%s ERROR: Error updating member: %s\n", _myAddressStr.c_str(), e.what());
fprintf(stderr, "%s ERROR: Error updating member %s-%s: %s\n", _myAddressStr.c_str(), networkId.c_str(), memberId.c_str(), e.what());
}
} else if (objtype == "network") {
try {
// fprintf(stderr, "%s: commitThread: network\n", _myAddressStr.c_str());
auto c = _pool->borrow();
pqxx::work w(*c->c);
std::string id = (*config)["id"];
std::string id = config["id"];
std::string remoteTraceTarget = "";
if(!(*config)["remoteTraceTarget"].is_null()) {
remoteTraceTarget = (*config)["remoteTraceTarget"];
if(!config["remoteTraceTarget"].is_null()) {
remoteTraceTarget = config["remoteTraceTarget"];
}
std::string rulesSource = "";
if ((*config)["rulesSource"].is_string()) {
rulesSource = (*config)["rulesSource"];
if (config["rulesSource"].is_string()) {
rulesSource = config["rulesSource"];
}
// This ugly query exists because when we want to mirror networks to/from
@ -1163,25 +1233,25 @@ void PostgreSQL::commitThread()
"sso_enabled = EXCLUDED.sso_enabled",
id,
_myAddressStr,
OSUtils::jsonDump((*config)["capabilitles"], -1),
(bool)(*config)["enableBroadcast"],
OSUtils::jsonDump(config["capabilitles"], -1),
(bool)config["enableBroadcast"],
OSUtils::now(),
(int)(*config)["mtu"],
(int)(*config)["multicastLimit"],
OSUtils::jsonString((*config)["name"],""),
(bool)(*config)["private"],
(int)(*config)["remoteTraceLevel"],
(int)config["mtu"],
(int)config["multicastLimit"],
OSUtils::jsonString(config["name"],""),
(bool)config["private"],
(int)config["remoteTraceLevel"],
remoteTraceTarget,
OSUtils::jsonDump((*config)["rules"], -1),
OSUtils::jsonDump(config["rules"], -1),
rulesSource,
OSUtils::jsonDump((*config)["tags"], -1),
OSUtils::jsonDump((*config)["v4AssignMode"],-1),
OSUtils::jsonDump((*config)["v6AssignMode"], -1),
OSUtils::jsonBool((*config)["ssoEnabled"], false));
OSUtils::jsonDump(config["tags"], -1),
OSUtils::jsonDump(config["v4AssignMode"],-1),
OSUtils::jsonDump(config["v6AssignMode"], -1),
OSUtils::jsonBool(config["ssoEnabled"], false));
res = w.exec_params0("DELETE FROM ztc_network_assignment_pool WHERE network_id = $1", 0);
auto pool = (*config)["ipAssignmentPools"];
auto pool = config["ipAssignmentPools"];
bool err = false;
for (auto i = pool.begin(); i != pool.end(); ++i) {
std::string start = (*i)["ipRangeStart"];
@ -1194,7 +1264,7 @@ void PostgreSQL::commitThread()
res = w.exec_params0("DELETE FROM ztc_network_route WHERE network_id = $1", id);
auto routes = (*config)["routes"];
auto routes = config["routes"];
err = false;
for (auto i = routes.begin(); i != routes.end(); ++i) {
std::string t = (*i)["target"];
@ -1221,12 +1291,10 @@ void PostgreSQL::commitThread()
fprintf(stderr, "%s: route add error\n", _myAddressStr.c_str());
w.abort();
_pool->unborrow(c);
delete config;
config = nullptr;
continue;
}
auto dns = (*config)["dns"];
auto dns = config["dns"];
std::string domain = dns["domain"];
std::stringstream servers;
servers << "{";
@ -1244,12 +1312,11 @@ void PostgreSQL::commitThread()
id, domain, s);
w.commit();
_pool->unborrow(c);
const uint64_t nwidInt = OSUtils::jsonIntHex((*config)["nwid"], 0ULL);
const uint64_t nwidInt = OSUtils::jsonIntHex(config["nwid"], 0ULL);
if (nwidInt) {
nlohmann::json nwOrig;
nlohmann::json nwNew(*config);
nlohmann::json nwNew(config);
get(nwidInt, nwOrig);
@ -1257,23 +1324,20 @@ void PostgreSQL::commitThread()
} else {
fprintf(stderr, "%s: Can't notify network changed: %llu\n", _myAddressStr.c_str(), (unsigned long long)nwidInt);
}
} catch (std::exception &e) {
fprintf(stderr, "%s ERROR: Error updating network: %s\n", _myAddressStr.c_str(), e.what());
}
} else if (objtype == "_delete_network") {
// fprintf(stderr, "%s: commitThread: delete network\n", _myAddressStr.c_str());
try {
auto c = _pool->borrow();
pqxx::work w(*c->c);
std::string networkId = (*config)["nwid"];
std::string networkId = config["nwid"];
pqxx::result res = w.exec_params0("UPDATE ztc_network SET deleted = true WHERE id = $1",
networkId);
w.commit();
_pool->unborrow(c);
} catch (std::exception &e) {
fprintf(stderr, "%s ERROR: Error deleting network: %s\n", _myAddressStr.c_str(), e.what());
}
@ -1281,18 +1345,16 @@ void PostgreSQL::commitThread()
} else if (objtype == "_delete_member") {
// fprintf(stderr, "%s commitThread: delete member\n", _myAddressStr.c_str());
try {
auto c = _pool->borrow();
pqxx::work w(*c->c);
std::string memberId = (*config)["id"];
std::string networkId = (*config)["nwid"];
std::string memberId = config["id"];
std::string networkId = config["nwid"];
pqxx::result res = w.exec_params0(
"UPDATE ztc_member SET hidden = true, deleted = true WHERE id = $1 AND network_id = $2",
memberId, networkId);
w.commit();
_pool->unborrow(c);
} catch (std::exception &e) {
fprintf(stderr, "%s ERROR: Error deleting member: %s\n", _myAddressStr.c_str(), e.what());
}
@ -1302,6 +1364,8 @@ void PostgreSQL::commitThread()
} catch (std::exception &e) {
fprintf(stderr, "%s ERROR: Error getting objtype: %s\n", _myAddressStr.c_str(), e.what());
}
_pool->unborrow(c);
c.reset();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
@ -1321,6 +1385,7 @@ void PostgreSQL::onlineNotification_Postgres()
nlohmann::json jtmp1, jtmp2;
while (_run == 1) {
auto c = _pool->borrow();
auto c2 = _pool->borrow();
try {
fprintf(stderr, "%s onlineNotification_Postgres\n", _myAddressStr.c_str());
std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > lastOnline;
@ -1330,15 +1395,16 @@ void PostgreSQL::onlineNotification_Postgres()
}
pqxx::work w(*c->c);
pqxx::work w2(*c2->c);
// using pqxx::stream_to would be a really nice alternative here, but
// unfortunately it doesn't support upserts.
// fprintf(stderr, "online notification tick\n");
std::stringstream memberUpdate;
memberUpdate << "INSERT INTO ztc_member_status (network_id, member_id, address, last_updated) VALUES ";
fprintf(stderr, "online notification tick\n");
bool firstRun = true;
bool memberAdded = false;
int updateCount = 0;
pqxx::pipeline pipe(w);
for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) {
updateCount += 1;
uint64_t nwid_i = i->first.first;
@ -1355,16 +1421,10 @@ void PostgreSQL::onlineNotification_Postgres()
std::string networkId(nwidTmp);
std::string memberId(memTmp);
const char *qvals[2] = {
networkId.c_str(),
memberId.c_str()
};
try {
pqxx::row r = w.exec_params1("SELECT id, network_id FROM ztc_member WHERE network_id = $1 AND id = $2",
pqxx::row r = w2.exec_params1("SELECT id, network_id FROM ztc_member WHERE network_id = $1 AND id = $2",
networkId, memberId);
} catch (pqxx::unexpected_rows &e) {
// fprintf(stderr, "Member count failed: %s\n", e.what());
continue;
}
@ -1372,32 +1432,30 @@ void PostgreSQL::onlineNotification_Postgres()
std::string ipAddr = i->second.second.toIpString(ipTmp);
std::string timestamp = std::to_string(ts);
if (firstRun) {
firstRun = false;
} else {
memberUpdate << ", ";
}
memberUpdate << "('" << networkId << "', '" << memberId << "', ";
std::stringstream memberUpdate;
memberUpdate << "INSERT INTO ztc_member_status (network_id, member_id, address, last_updated) VALUES "
<< "('" << networkId << "', '" << memberId << "', ";
if (ipAddr.empty()) {
memberUpdate << "NULL, ";
} else {
memberUpdate << "'" << ipAddr << "', ";
}
memberUpdate << "TO_TIMESTAMP(" << timestamp << "::double precision/1000))";
memberAdded = true;
}
memberUpdate << " ON CONFLICT (network_id, member_id) DO UPDATE SET address = EXCLUDED.address, last_updated = EXCLUDED.last_updated;";
memberUpdate << "TO_TIMESTAMP(" << timestamp << "::double precision/1000)) "
<< " ON CONFLICT (network_id, member_id) DO UPDATE SET address = EXCLUDED.address, last_updated = EXCLUDED.last_updated";
if (memberAdded) {
//fprintf(stderr, "%s\n", memberUpdate.str().c_str());
pqxx::result res = w.exec0(memberUpdate.str());
w.commit();
pipe.insert(memberUpdate.str());
}
while(!pipe.empty()) {
pipe.retrieve();
}
pipe.complete();
w.commit();
fprintf(stderr, "%s: Updated online status of %d members\n", _myAddressStr.c_str(), updateCount);
} catch (std::exception &e) {
fprintf(stderr, "%s: error in onlinenotification thread: %s\n", _myAddressStr.c_str(), e.what());
}
_pool->unborrow(c2);
_pool->unborrow(c);
ConnectionPoolStats stats = _pool->get_stats();

View File

@ -107,7 +107,7 @@ public:
virtual void eraseNetwork(const uint64_t networkId);
virtual void eraseMember(const uint64_t networkId, const uint64_t memberId);
virtual void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress);
virtual std::string getSSOAuthURL(const nlohmann::json &member, const std::string &redirectURL);
virtual AuthInfo getSSOAuthInfo(const nlohmann::json &member, const std::string &redirectURL);
protected:
struct _PairHasher

View File

@ -2,7 +2,7 @@
FROM registry.zerotier.com/zerotier/controller-builder:latest as builder
MAINTAINER Adam Ierymekno <adam.ierymenko@zerotier.com>, Grant Limberg <grant.limberg@zerotier.com>
ADD . /ZeroTierOne
RUN cd ZeroTierOne && make clean && make central-controller -j8
RUN export PATH=$PATH:~/.cargo/bin && cd ZeroTierOne && make clean && make central-controller -j8
FROM registry.zerotier.com/zerotier/controller-run:latest
COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one

View File

@ -9,4 +9,5 @@ RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x
RUN dnf -qy module disable postgresql
RUN yum -y install epel-release && yum -y update && yum clean all
RUN yum groupinstall -y "Development Tools" && yum clean all
RUN yum install -y bash cmake postgresql10 postgresql10-devel clang jemalloc jemalloc-devel libpqxx libpqxx-devel && yum clean all
RUN yum install -y bash cmake postgresql10 postgresql10-devel clang jemalloc jemalloc-devel libpqxx libpqxx-devel openssl-devel && yum clean all
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

View File

@ -2,4 +2,4 @@ FROM centos:8
RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
RUN dnf -qy module disable postgresql
RUN yum -y install epel-release && yum -y update && yum clean all
RUN yum install -y jemalloc jemalloc-devel postgresql10 libpqxx && yum clean all
RUN yum install -y jemalloc jemalloc-devel postgresql10 libpqxx libpqxx-devel && yum clean all

View File

@ -1194,11 +1194,18 @@ typedef struct
*/
ZT_VirtualNetworkDNS dns;
/**
* sso enabled
*/
bool ssoEnabled;
/**
* SSO verison
*/
uint64_t ssoVersion;
/**
* If the status us AUTHENTICATION_REQUIRED, this may contain a URL for authentication.
*/
@ -1208,6 +1215,31 @@ typedef struct
* Time that current authentication expires. only valid if ssoEnabled is true
*/
uint64_t authenticationExpiryTime;
/**
* OIDC issuer URL.
*/
char issuerURL[2048];
/**
* central base URL.
*/
char centralAuthURL[2048];
/**
* sso nonce
*/
char ssoNonce[128];
/**
* sso state
*/
char ssoState[256];
/**
* oidc client id
*/
char ssoClientID[256];
} ZT_VirtualNetworkConfig;
/**

View File

@ -9,7 +9,7 @@ ifeq ($(origin CXX),default)
CXX:=$(shell if [ -e /opt/rh/devtoolset-8/root/usr/bin/g++ ]; then echo /opt/rh/devtoolset-8/root/usr/bin/g++; else echo $(CXX); fi)
endif
INCLUDES?=
INCLUDES?=-Izeroidc/target
DEFS?=
LDLIBS?=
DESTDIR?=
@ -18,7 +18,7 @@ include objects.mk
ONE_OBJS+=osdep/LinuxEthernetTap.o
ONE_OBJS+=osdep/LinuxNetLink.o
# for central controller builds
# for central controller buildsk
TIMESTAMP=$(shell date +"%Y%m%d%H%M")
# Auto-detect miniupnpc and nat-pmp as well and use system libs if present,
@ -41,6 +41,12 @@ else
override DEFS+=-DZT_USE_SYSTEM_NATPMP
endif
ifeq ($(ZT_DEBUG),1)
LDLIBS+=zeroidc/target/debug/libzeroidc.a -ldl
else
LDLIBS+=zeroidc/target/release/libzeroidc.a -ldl
endif
# Use bundled http-parser since distribution versions are NOT API-stable or compatible!
# Trying to use dynamically linked libhttp-parser causes tons of compatibility problems.
ONE_OBJS+=ext/http-parser/http_parser.o
@ -64,6 +70,7 @@ ifeq ($(ZT_DEBUG),1)
override CFLAGS+=-Wall -Wno-deprecated -g -O -pthread $(INCLUDES) $(DEFS)
override CXXFLAGS+=-Wall -Wno-deprecated -g -O -std=c++11 -pthread $(INCLUDES) $(DEFS)
ZT_TRACE=1
RUSTFLAGS=
# The following line enables optimization for the crypto code, since
# C25519 in particular is almost UNUSABLE in -O0 even on a 3ghz box!
node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CXXFLAGS=-Wall -O2 -g -pthread $(INCLUDES) $(DEFS)
@ -73,6 +80,7 @@ else
CXXFLAGS?=-O3 -fstack-protector -fPIE
override CXXFLAGS+=-Wall -Wno-deprecated -std=c++11 -pthread $(INCLUDES) -DNDEBUG $(DEFS)
LDFLAGS=-pie -Wl,-z,relro,-z,now
RUSTFLAGS=--release
endif
ifeq ($(ZT_QNAP), 1)
@ -274,7 +282,7 @@ endif
ifeq ($(ZT_CONTROLLER),1)
override CXXFLAGS+=-Wall -Wno-deprecated -std=c++17 -pthread $(INCLUDES) -DNDEBUG $(DEFS)
override LDLIBS+=-L/usr/pgsql-10/lib/ -lpqxx -lpq ext/hiredis-0.14.1/lib/centos8/libhiredis.a ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a
override LDLIBS+=-L/usr/pgsql-10/lib/ -lpqxx -lpq ext/hiredis-0.14.1/lib/centos8/libhiredis.a ext/redis-plus-plus-1.1.1/install/centos8/lib/libredis++.a -lssl -lcrypto
override DEFS+=-DZT_CONTROLLER_USE_LIBPQ
override INCLUDES+=-I/usr/pgsql-10/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/centos8/include/sw/
endif
@ -321,6 +329,8 @@ zerotier-idtool: zerotier-one
zerotier-cli: zerotier-one
ln -sf zerotier-one zerotier-cli
$(ONE_OBJS): zeroidc
libzerotiercore.a: FORCE
make CFLAGS="-O3 -fstack-protector -fPIC" CXXFLAGS="-O3 -std=c++11 -fstack-protector -fPIC" $(CORE_OBJS)
ar rcs libzerotiercore.a $(CORE_OBJS)
@ -339,7 +349,7 @@ manpages: FORCE
doc: manpages
clean: FORCE
rm -rf *.a *.so *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o ext/miniupnpc/*.o ext/libnatpmp/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-cli zerotier-selftest build-* ZeroTierOneInstaller-* *.deb *.rpm .depend debian/files debian/zerotier-one*.debhelper debian/zerotier-one.substvars debian/*.log debian/zerotier-one doc/node_modules ext/misc/*.o debian/.debhelper debian/debhelper-build-stamp docker/zerotier-one
rm -rf *.a *.so *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o ext/miniupnpc/*.o ext/libnatpmp/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-cli zerotier-selftest build-* ZeroTierOneInstaller-* *.deb *.rpm .depend debian/files debian/zerotier-one*.debhelper debian/zerotier-one.substvars debian/*.log debian/zerotier-one doc/node_modules ext/misc/*.o debian/.debhelper debian/debhelper-build-stamp docker/zerotier-one zeroidc/target
distclean: clean
@ -361,6 +371,9 @@ debug: FORCE
make ZT_DEBUG=1 one
make ZT_DEBUG=1 selftest
zeroidc: FORCE
cd zeroidc && cargo build $(RUSTFLAGS)
# Note: keep the symlinks in /var/lib/zerotier-one to the binaries since these
# provide backward compatibility with old releases where the binaries actually
# lived here. Folks got scripts.

View File

@ -1,6 +1,8 @@
CC=clang
CXX=clang++
INCLUDES=
TOPDIR=$(shell PWD)
INCLUDES=-I$(shell PWD)/zeroidc/target
DEFS=
LIBS=
ARCH_FLAGS=-arch x86_64 -arch arm64
@ -26,7 +28,7 @@ DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_BUIL
include objects.mk
ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o osdep/MacDNSHelper.o ext/http-parser/http_parser.o
LIBS+=-framework CoreServices -framework SystemConfiguration -framework CoreFoundation
LIBS+=-framework CoreServices -framework SystemConfiguration -framework CoreFoundation -framework Security
# Official releases are signed with our Apple cert and apply software updates by default
ifeq ($(ZT_OFFICIAL_RELEASE),1)
@ -73,6 +75,8 @@ ifeq ($(ZT_DEBUG),1)
ARCH_FLAGS=
CFLAGS+=-Wall -g $(INCLUDES) $(DEFS) $(ARCH_FLAGS)
STRIP=echo
RUSTFLAGS=
RUST_VARIANT=debug
# The following line enables optimization for the crypto code, since
# C25519 in particular is almost UNUSABLE in heavy testing without it.
node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g $(INCLUDES) $(DEFS)
@ -80,6 +84,8 @@ else
CFLAGS?=-Ofast -fstack-protector-strong
CFLAGS+=$(ARCH_FLAGS) -Wall -flto -fPIE -mmacosx-version-min=$(MACOS_VERSION_MIN) -DNDEBUG -Wno-unused-private-field $(INCLUDES) $(DEFS)
STRIP=strip
RUSTFLAGS=--release
RUST_VARIANT=release
endif
ifeq ($(ZT_TRACE),1)
@ -103,8 +109,8 @@ mac-agent: FORCE
osdep/MacDNSHelper.o: osdep/MacDNSHelper.mm
$(CXX) $(CXXFLAGS) -c osdep/MacDNSHelper.mm -o osdep/MacDNSHelper.o
one: $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent
$(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS)
one: zeroidc $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent
$(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS) zeroidc/target/libzeroidc.a
# $(STRIP) zerotier-one
ln -sf zerotier-one zerotier-idtool
ln -sf zerotier-one zerotier-cli
@ -112,6 +118,13 @@ one: $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent
zerotier-one: one
zeroidc: zeroidc/target/libzeroidc.a
zeroidc/target/libzeroidc.a: FORCE
cd zeroidc && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build --target=x86_64-apple-darwin $(RUSTFLAGS)
cd zeroidc && MACOSX_DEPLOYMENT_TARGET=$(MACOS_VERSION_MIN) cargo build --target=aarch64-apple-darwin $(RUSTFLAGS)
cd zeroidc && lipo -create target/x86_64-apple-darwin/$(RUST_VARIANT)/libzeroidc.a target/aarch64-apple-darwin/$(RUST_VARIANT)/libzeroidc.a -output target/libzeroidc.a
central-controller:
make ARCH_FLAGS="-arch x86_64" ZT_CONTROLLER=1 one
@ -119,6 +132,8 @@ zerotier-idtool: one
zerotier-cli: one
$(ONE_OBJS): zeroidc
libzerotiercore.a: $(CORE_OBJS)
ar rcs libzerotiercore.a $(CORE_OBJS)
ranlib libzerotiercore.a
@ -130,7 +145,7 @@ core: libzerotiercore.a
# $(STRIP) zerotier
selftest: $(CORE_OBJS) $(ONE_OBJS) selftest.o
$(CXX) $(CXXFLAGS) -o zerotier-selftest selftest.o $(CORE_OBJS) $(ONE_OBJS) $(LIBS)
$(CXX) $(CXXFLAGS) -o zerotier-selftest selftest.o $(CORE_OBJS) $(ONE_OBJS) $(LIBS) zeroidc/target/libzeroidc.a
$(STRIP) zerotier-selftest
zerotier-selftest: selftest
@ -157,7 +172,7 @@ central-controller-docker: FORCE
docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) .
clean:
rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_*
rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* zeroidc/target/
distclean: clean

View File

@ -142,7 +142,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar
if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setNotFound();
network->setNotFound(tPtr);
}
break;
@ -153,7 +153,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar
if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setNotFound();
network->setNotFound(tPtr);
}
break;
@ -176,7 +176,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar
// Network controller: network access denied.
const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address()))
network->setAccessDenied();
network->setAccessDenied(tPtr);
} break;
case Packet::ERROR_UNWANTED_MULTICAST: {
@ -191,25 +191,55 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const Shar
} break;
case Packet::ERROR_NETWORK_AUTHENTICATION_REQUIRED: {
fprintf(stderr, "\nPacket::ERROR_NETWORK_AUTHENTICATION_REQUIRED\n\n");
const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
if ((network)&&(network->controller() == peer->address())) {
bool noUrl = true;
int s = (int)size() - (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
if (s > 2) {
const uint16_t errorDataSize = at<uint16_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8);
s -= 2;
if (s >= (int)errorDataSize) {
Dictionary<3072> authInfo(((const char *)this->data()) + (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 10), errorDataSize);
char authenticationURL[2048];
if (authInfo.get("aU", authenticationURL, sizeof(authenticationURL)) > 0) {
authenticationURL[sizeof(authenticationURL) - 1] = 0; // ensure always zero terminated
network->setAuthenticationRequired(authenticationURL);
noUrl = false;
Dictionary<8192> authInfo(((const char *)this->data()) + (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 10), errorDataSize);
uint64_t authVer = authInfo.getUI(ZT_AUTHINFO_DICT_KEY_VERSION, 0ULL);
if (authVer == 0) {
char authenticationURL[2048];
if (authInfo.get(ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL, authenticationURL, sizeof(authenticationURL)) > 0) {
authenticationURL[sizeof(authenticationURL) - 1] = 0; // ensure always zero terminated
network->setAuthenticationRequired(tPtr, authenticationURL);
}
} else if (authVer == 1) {
char issuerURL[2048] = { 0 };
char centralAuthURL[2048] = { 0 };
char ssoNonce[64] = { 0 };
char ssoState[128] = {0};
char ssoClientID[256] = { 0 };
if (authInfo.get(ZT_AUTHINFO_DICT_KEY_ISSUER_URL, issuerURL, sizeof(issuerURL)) > 0) {
issuerURL[sizeof(issuerURL) - 1] = 0;
}
if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL, centralAuthURL, sizeof(centralAuthURL))>0) {
centralAuthURL[sizeof(centralAuthURL) - 1] = 0;
}
if (authInfo.get(ZT_AUTHINFO_DICT_KEY_NONCE, ssoNonce, sizeof(ssoNonce)) > 0) {
ssoNonce[sizeof(ssoNonce) - 1] = 0;
}
if (authInfo.get(ZT_AUTHINFO_DICT_KEY_STATE, ssoState, sizeof(ssoState)) > 0) {
ssoState[sizeof(ssoState) - 1] = 0;
}
if (authInfo.get(ZT_AUTHINFO_DICT_KEY_CLIENT_ID, ssoClientID, sizeof(ssoClientID)) > 0) {
ssoClientID[sizeof(ssoClientID) - 1] = 0;
}
network->setAuthenticationRequired(tPtr, issuerURL, centralAuthURL, ssoClientID, ssoNonce, ssoState);
}
}
} else {
fprintf(stderr, "authinfo??????\n");
network->setAuthenticationRequired(tPtr, "");
}
if (noUrl)
network->setAuthenticationRequired("");
}
} break;

View File

@ -1115,7 +1115,7 @@ void Network::requestConfiguration(void *tPtr)
this->setConfiguration(tPtr,*nconf,false);
delete nconf;
} else {
this->setNotFound();
this->setNotFound(tPtr);
}
} else if ((_id & 0xff) == 0x01) {
// ffAAaaaaaaaaaa01 -- where AA is the IPv4 /8 to use and aaaaaaaaaa is the anchor node for multicast gather and replication
@ -1199,7 +1199,7 @@ void Network::requestConfiguration(void *tPtr)
if (RR->localNetworkController) {
RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd);
} else {
this->setNotFound();
this->setNotFound(tPtr);
}
return;
}
@ -1434,8 +1434,14 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS));
Utils::scopy(ec->authenticationURL, sizeof(ec->authenticationURL), _authenticationURL.c_str());
ec->ssoVersion = _config.ssoVersion;
ec->authenticationExpiryTime = _config.authenticationExpiryTime;
ec->ssoEnabled = _config.ssoEnabled;
Utils::scopy(ec->centralAuthURL, sizeof(ec->centralAuthURL), _config.centralAuthURL);
Utils::scopy(ec->issuerURL, sizeof(ec->issuerURL), _config.issuerURL);
Utils::scopy(ec->ssoNonce, sizeof(ec->ssoNonce), _config.ssoNonce);
Utils::scopy(ec->ssoState, sizeof(ec->ssoState), _config.ssoState);
Utils::scopy(ec->ssoClientID, sizeof(ec->ssoClientID), _config.ssoClientID);
}
void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup)
@ -1542,4 +1548,25 @@ Membership &Network::_membership(const Address &a)
return _memberships[a];
}
void Network::setAuthenticationRequired(void *tPtr, const char* issuerURL, const char* centralEndpoint, const char* clientID, const char* nonce, const char* state)
{
Mutex::Lock _l(_lock);
_netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
_config.ssoEnabled = true;
_config.ssoVersion = 1;
Utils::scopy(_config.issuerURL, sizeof(_config.issuerURL), issuerURL);
Utils::scopy(_config.centralAuthURL, sizeof(_config.centralAuthURL), centralEndpoint);
Utils::scopy(_config.ssoClientID, sizeof(_config.ssoClientID), clientID);
Utils::scopy(_config.ssoNonce, sizeof(_config.ssoNonce), nonce);
Utils::scopy(_config.ssoState, sizeof(_config.ssoState), state);
_sendUpdateEvent(tPtr);
}
void Network::_sendUpdateEvent(void *tPtr) {
ZT_VirtualNetworkConfig ctmp;
_externalConfig(&ctmp);
RR->node->configureVirtualNetworkPort(tPtr, _id, &_uPtr, (_portInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, &ctmp);
}
} // namespace ZeroTier

View File

@ -205,31 +205,43 @@ public:
/**
* Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this
*/
inline void setAccessDenied()
inline void setAccessDenied(void *tPtr)
{
Mutex::Lock _l(_lock);
_netconfFailure = NETCONF_FAILURE_ACCESS_DENIED;
_sendUpdateEvent(tPtr);
}
/**
* Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this
*/
inline void setNotFound()
inline void setNotFound(void *tPtr)
{
Mutex::Lock _l(_lock);
_netconfFailure = NETCONF_FAILURE_NOT_FOUND;
_sendUpdateEvent(tPtr);
}
/**
* Set netconf failure to 'authentication required' possibly with an authorization URL
*/
inline void setAuthenticationRequired(const char *url)
inline void setAuthenticationRequired(void *tPtr, const char *url)
{
Mutex::Lock _l(_lock);
_netconfFailure = NETCONF_FAILURE_AUTHENTICATION_REQUIRED;
_authenticationURL = (url) ? url : "";
_config.ssoEnabled = true;
}
_config.ssoVersion = 0;
_sendUpdateEvent(tPtr);
}
/**
* set netconf failure to 'authentication required' along with info needed
* for sso full flow authentication.
*/
void setAuthenticationRequired(void *tPtr, const char* issuerURL, const char* centralEndpoint, const char* clientID, const char* nonce, const char* state);
/**
* Causes this network to request an updated configuration from its master node now
@ -413,6 +425,7 @@ private:
void _announceMulticastGroupsTo(void *tPtr,const Address &peer,const std::vector<MulticastGroup> &allMulticastGroups);
std::vector<MulticastGroup> _allMulticastGroups() const;
Membership &_membership(const Address &a);
void _sendUpdateEvent(void *tPtr);
const RuntimeEnvironment *const RR;
void *_uPtr;
@ -459,6 +472,6 @@ private:
AtomicCounter __refCount;
};
} // namespace ZeroTier
} // namespace ZeroTier
#endif

View File

@ -182,12 +182,25 @@ bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,b
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false;
}
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false;
if (this->ssoEnabled) {
if (this->authenticationURL[0]) {
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
if (this->ssoVersion == 0) {
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false;
if (this->ssoEnabled) {
if (this->authenticationURL[0]) {
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
}
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false;
}
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, this->authenticationExpiryTime)) return false;
} else if(this->ssoVersion == 1) {
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, this->ssoVersion)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, this->ssoEnabled)) return false;
//if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState)) return false;
if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID)) return false;
}
delete tmp;
@ -374,18 +387,52 @@ bool NetworkConfig::fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACI
DNS::deserializeDNS(*tmp, p, &dns);
}
this->ssoVersion = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION, 0ULL);
this->ssoEnabled = d.getB(ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED, false);
if (this->ssoEnabled) {
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0; // ensure null terminated
if (this->ssoVersion == 0) {
// implicit flow
if (this->ssoEnabled) {
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0; // ensure null terminated
} else {
this->authenticationURL[0] = 0;
}
this->authenticationExpiryTime = d.getI(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, 0);
} else {
this->authenticationURL[0] = 0;
this->authenticationExpiryTime = 0;
}
} else if (this->ssoVersion == 1) {
// full flow
if (this->ssoEnabled) {
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL, this->authenticationURL, (unsigned int)sizeof(this->authenticationURL)) > 0) {
this->authenticationURL[sizeof(this->authenticationURL) - 1] = 0;
}
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL, this->issuerURL, (unsigned int)sizeof(this->issuerURL)) > 0) {
this->issuerURL[sizeof(this->issuerURL) - 1] = 0;
}
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL, this->centralAuthURL, (unsigned int)sizeof(this->centralAuthURL)) > 0) {
this->centralAuthURL[sizeof(this->centralAuthURL) - 1] = 0;
}
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_NONCE, this->ssoNonce, (unsigned int)sizeof(this->ssoNonce)) > 0) {
this->ssoNonce[sizeof(this->ssoNonce) - 1] = 0;
}
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATE, this->ssoState, (unsigned int)sizeof(this->ssoState)) > 0) {
this->ssoState[sizeof(this->ssoState) - 1] = 0;
}
if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID, this->ssoClientID, (unsigned int)sizeof(this->ssoClientID)) > 0) {
this->ssoClientID[sizeof(this->ssoClientID) - 1] = 0;
}
} else {
this->authenticationURL[0] = 0;
this->authenticationExpiryTime = 0;
this->centralAuthURL[0] = 0;
this->ssoNonce[0] = 0;
this->ssoState[0] = 0;
this->ssoClientID[0] = 0;
this->issuerURL[0] = 0;
}
this->authenticationExpiryTime = d.getI(ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME, 0);
} else {
this->authenticationURL[0] = 0;
this->authenticationExpiryTime = 0;
}
}

View File

@ -94,7 +94,7 @@
namespace ZeroTier {
// Dictionary capacity needed for max size network config
#define ZT_NETWORKCONFIG_DICT_CAPACITY (4096 + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS) + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP))
#define ZT_NETWORKCONFIG_DICT_CAPACITY (4096 + (sizeof(ZT_VirtualNetworkConfig)) + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS) + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP))
// Dictionary capacity needed for max size network meta-data
#define ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY 1024
@ -180,10 +180,39 @@ namespace ZeroTier {
#define ZT_NETWORKCONFIG_DICT_KEY_DNS "DNS"
// sso enabld
#define ZT_NETWORKCONFIG_DICT_KEY_SSO_ENABLED "ssoe"
// so version
#define ZT_NETWORKCONFIG_DICT_KEY_SSO_VERSION "ssov"
// authentication URL
#define ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_URL "aurl"
// authentication expiry
#define ZT_NETWORKCONFIG_DICT_KEY_AUTHENTICATION_EXPIRY_TIME "aexpt"
// oidc issuer URL
#define ZT_NETWORKCONFIG_DICT_KEY_ISSUER_URL "iurl"
// central endpoint
#define ZT_NETWORKCONFIG_DICT_KEY_CENTRAL_ENDPOINT_URL "ssoce"
// nonce
#define ZT_NETWORKCONFIG_DICT_KEY_NONCE "sson"
// state
#define ZT_NETWORKCONFIG_DICT_KEY_STATE "ssos"
// client ID
#define ZT_NETWORKCONFIG_DICT_KEY_CLIENT_ID "ssocid"
// AuthInfo fields -- used by ncSendError for sso
// AuthInfo Version
#define ZT_AUTHINFO_DICT_KEY_VERSION "aV"
// authenticaiton URL
#define ZT_AUTHINFO_DICT_KEY_AUTHENTICATION_URL "aU"
// issuer URL
#define ZT_AUTHINFO_DICT_KEY_ISSUER_URL "iU"
// Central endpoint URL
#define ZT_AUTHINFO_DICT_KEY_CENTRAL_ENDPOINT_URL "aCU"
// Nonce
#define ZT_AUTHINFO_DICT_KEY_NONCE "aN"
// State
#define ZT_AUTHINFO_DICT_KEY_STATE "aS"
// Client ID
#define ZT_AUTHINFO_DICT_KEY_CLIENT_ID "aCID"
// Legacy fields -- these are obsoleted but are included when older clients query
@ -242,7 +271,12 @@ public:
dnsCount(0),
ssoEnabled(false),
authenticationURL(),
authenticationExpiryTime(0)
authenticationExpiryTime(0),
issuerURL(),
centralAuthURL(),
ssoNonce(),
ssoState(),
ssoClientID()
{
name[0] = 0;
memset(specialists, 0, sizeof(uint64_t)*ZT_MAX_NETWORK_SPECIALISTS);
@ -250,6 +284,12 @@ public:
memset(staticIps, 0, sizeof(InetAddress)*ZT_MAX_ZT_ASSIGNED_ADDRESSES);
memset(rules, 0, sizeof(ZT_VirtualNetworkRule)*ZT_MAX_NETWORK_RULES);
memset(&dns, 0, sizeof(ZT_VirtualNetworkDNS));
memset(authenticationURL, 0, sizeof(authenticationURL));
memset(issuerURL, 0, sizeof(issuerURL));
memset(centralAuthURL, 0, sizeof(centralAuthURL));
memset(ssoNonce, 0, sizeof(ssoNonce));
memset(ssoState, 0, sizeof(ssoState));
memset(ssoClientID, 0, sizeof(ssoClientID));
}
/**
@ -619,6 +659,11 @@ public:
*/
bool ssoEnabled;
/**
* SSO verison
*/
uint64_t ssoVersion;
/**
* Authentication URL if authentication is required
*/
@ -626,8 +671,35 @@ public:
/**
* Time current authentication expires or 0 if external authentication is disabled
*
* Not used if authVersion >= 1
*/
uint64_t authenticationExpiryTime;
/**
* OIDC issuer URL
*/
char issuerURL[2048];
/**
* central base URL.
*/
char centralAuthURL[2048];
/**
* sso nonce
*/
char ssoNonce[128];
/**
* sso state
*/
char ssoState[256];
/**
* oidc client id
*/
char ssoClientID[256];
};
} // namespace ZeroTier

View File

@ -735,14 +735,16 @@ void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &des
switch(errorCode) {
case NetworkController::NC_ERROR_OBJECT_NOT_FOUND:
case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR:
n->setNotFound();
n->setNotFound(nullptr);
break;
case NetworkController::NC_ERROR_ACCESS_DENIED:
n->setAccessDenied();
n->setAccessDenied(nullptr);
break;
case NetworkController::NC_ERROR_AUTHENTICATION_REQUIRED: {
}
fprintf(stderr, "\n\nGot auth required\n\n");
break;
}
default: break;
}

View File

@ -53,6 +53,8 @@
#include "OneService.hpp"
#include "SoftwareUpdater.hpp"
#include <zeroidc.h>
#ifdef __WINDOWS__
#include <winsock2.h>
#include <windows.h>
@ -145,6 +147,205 @@ size_t curlResponseWrite(void *ptr, size_t size, size_t nmemb, std::string *data
namespace ZeroTier {
// Configured networks
class NetworkState
{
public:
NetworkState()
: _webPort(9993)
, _tap((EthernetTap *)0)
, _idc(nullptr)
{
// Real defaults are in network 'up' code in network event handler
_settings.allowManaged = true;
_settings.allowGlobal = false;
_settings.allowDefault = false;
_settings.allowDNS = false;
memset(&_config, 0, sizeof(ZT_VirtualNetworkConfig));
}
~NetworkState()
{
this->_managedRoutes.clear();
this->_tap.reset();
if (_idc) {
zeroidc::zeroidc_stop(_idc);
zeroidc::zeroidc_delete(_idc);
_idc = nullptr;
}
}
void setWebPort(unsigned int port) {
_webPort = port;
}
void setTap(std::shared_ptr<EthernetTap> tap) {
this->_tap = tap;
}
std::shared_ptr<EthernetTap> tap() const {
return _tap;
}
OneService::NetworkSettings settings() const {
return _settings;
}
void setSettings(const OneService::NetworkSettings &settings) {
_settings = settings;
}
void setAllowManaged(bool allow) {
_settings.allowManaged = allow;
}
bool allowManaged() const {
return _settings.allowManaged;
}
void setAllowGlobal(bool allow) {
_settings.allowGlobal = allow;
}
bool allowGlobal() const {
return _settings.allowGlobal;
}
void setAllowDefault(bool allow) {
_settings.allowDefault = allow;
}
bool allowDefault() const {
return _settings.allowDefault;
}
void setAllowDNS(bool allow) {
_settings.allowDNS = allow;
}
bool allowDNS() const {
return _settings.allowDNS;
}
std::vector<InetAddress> allowManagedWhitelist() const {
return _settings.allowManagedWhitelist;
}
void addToAllowManagedWhiteList(const InetAddress& addr) {
_settings.allowManagedWhitelist.push_back(addr);
}
const ZT_VirtualNetworkConfig& config() {
return _config;
}
void setConfig(const ZT_VirtualNetworkConfig *nwc) {
char nwbuf[17] = {};
const char* nwid = Utils::hex(nwc->nwid, nwbuf);
// fprintf(stderr, "NetworkState::setConfig(%s)\n", nwid);
memcpy(&_config, nwc, sizeof(ZT_VirtualNetworkConfig));
// fprintf(stderr, "ssoEnabled: %s, ssoVersion: %d\n",
// _config.ssoEnabled ? "true" : "false", _config.ssoVersion);
if (_config.ssoEnabled && _config.ssoVersion == 1) {
// fprintf(stderr, "ssoEnabled for %s\n", nwid);
if (_idc == nullptr)
{
assert(_config.issuerURL != nullptr);
assert(_config.ssoClientID != nullptr);
assert(_config.centralAuthURL != nullptr);
// fprintf(stderr, "Issuer URL: %s\n", _config.issuerURL);
// fprintf(stderr, "Client ID: %s\n", _config.ssoClientID);
// fprintf(stderr, "Central Auth URL: %s\n", _config.centralAuthURL);
_idc = zeroidc::zeroidc_new(
_config.issuerURL,
_config.ssoClientID,
_config.centralAuthURL,
_webPort
);
if (_idc == nullptr) {
fprintf(stderr, "idc is null\n");
return;
}
// fprintf(stderr, "idc created (%s, %s, %s)\n", _config.issuerURL, _config.ssoClientID, _config.centralAuthURL);
}
zeroidc::zeroidc_set_nonce_and_csrf(
_idc,
_config.ssoState,
_config.ssoNonce
);
const char* url = zeroidc::zeroidc_get_auth_url(_idc);
memcpy(_config.authenticationURL, url, strlen(url));
_config.authenticationURL[strlen(url)] = 0;
}
}
std::vector<InetAddress>& managedIps() {
return _managedIps;
}
void setManagedIps(const std::vector<InetAddress> &managedIps) {
_managedIps = managedIps;
}
std::map< InetAddress, SharedPtr<ManagedRoute> >& managedRoutes() {
return _managedRoutes;
}
const char* getAuthURL() {
if (_idc != nullptr) {
return zeroidc::zeroidc_get_auth_url(_idc);
}
fprintf(stderr, "_idc is null\n");
return "";
}
const char* doTokenExchange(const char *code) {
if (_idc == nullptr) {
fprintf(stderr, "ainfo or idc null\n");
return "";
}
const char *ret = zeroidc::zeroidc_token_exchange(_idc, code);
zeroidc::zeroidc_set_nonce_and_csrf(
_idc,
_config.ssoState,
_config.ssoNonce
);
const char* url = zeroidc::zeroidc_get_auth_url(_idc);
memcpy(_config.authenticationURL, url, strlen(url));
_config.authenticationURL[strlen(url)] = 0;
return ret;
}
uint64_t getExpiryTime() {
if (_idc == nullptr) {
fprintf(stderr, "idc is null\n");
return 0;
}
return zeroidc::zeroidc_get_exp_time(_idc);
}
private:
unsigned int _webPort;
std::shared_ptr<EthernetTap> _tap;
ZT_VirtualNetworkConfig _config; // memcpy() of raw config from core
std::vector<InetAddress> _managedIps;
std::map< InetAddress, SharedPtr<ManagedRoute> > _managedRoutes;
OneService::NetworkSettings _settings;
zeroidc::ZeroIDC *_idc;
};
namespace {
static const InetAddress NULL_INET_ADDR;
@ -171,12 +372,12 @@ static std::string _trimString(const std::string &s)
return s.substr(start,end - start);
}
static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc,const std::string &portDeviceName,const OneService::NetworkSettings &localSettings)
static void _networkToJson(nlohmann::json &nj,NetworkState &ns)
{
char tmp[256];
const char *nstatus = "",*ntype = "";
switch(nc->status) {
switch(ns.config().status) {
case ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION: nstatus = "REQUESTING_CONFIGURATION"; break;
case ZT_NETWORK_STATUS_OK: nstatus = "OK"; break;
case ZT_NETWORK_STATUS_ACCESS_DENIED: nstatus = "ACCESS_DENIED"; break;
@ -185,75 +386,81 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc,
case ZT_NETWORK_STATUS_CLIENT_TOO_OLD: nstatus = "CLIENT_TOO_OLD"; break;
case ZT_NETWORK_STATUS_AUTHENTICATION_REQUIRED: nstatus = "AUTHENTICATION_REQUIRED"; break;
}
switch(nc->type) {
switch(ns.config().type) {
case ZT_NETWORK_TYPE_PRIVATE: ntype = "PRIVATE"; break;
case ZT_NETWORK_TYPE_PUBLIC: ntype = "PUBLIC"; break;
}
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",nc->nwid);
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",ns.config().nwid);
nj["id"] = tmp;
nj["nwid"] = tmp;
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)((nc->mac >> 40) & 0xff),(unsigned int)((nc->mac >> 32) & 0xff),(unsigned int)((nc->mac >> 24) & 0xff),(unsigned int)((nc->mac >> 16) & 0xff),(unsigned int)((nc->mac >> 8) & 0xff),(unsigned int)(nc->mac & 0xff));
OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)((ns.config().mac >> 40) & 0xff),(unsigned int)((ns.config().mac >> 32) & 0xff),(unsigned int)((ns.config().mac >> 24) & 0xff),(unsigned int)((ns.config().mac >> 16) & 0xff),(unsigned int)((ns.config().mac >> 8) & 0xff),(unsigned int)(ns.config().mac & 0xff));
nj["mac"] = tmp;
nj["name"] = nc->name;
nj["name"] = ns.config().name;
nj["status"] = nstatus;
nj["type"] = ntype;
nj["mtu"] = nc->mtu;
nj["dhcp"] = (bool)(nc->dhcp != 0);
nj["bridge"] = (bool)(nc->bridge != 0);
nj["broadcastEnabled"] = (bool)(nc->broadcastEnabled != 0);
nj["portError"] = nc->portError;
nj["netconfRevision"] = nc->netconfRevision;
nj["portDeviceName"] = portDeviceName;
nj["mtu"] = ns.config().mtu;
nj["dhcp"] = (bool)(ns.config().dhcp != 0);
nj["bridge"] = (bool)(ns.config().bridge != 0);
nj["broadcastEnabled"] = (bool)(ns.config().broadcastEnabled != 0);
nj["portError"] = ns.config().portError;
nj["netconfRevision"] = ns.config().netconfRevision;
nj["portDeviceName"] = ns.tap()->deviceName();
OneService::NetworkSettings localSettings = ns.settings();
nj["allowManaged"] = localSettings.allowManaged;
nj["allowGlobal"] = localSettings.allowGlobal;
nj["allowDefault"] = localSettings.allowDefault;
nj["allowDNS"] = localSettings.allowDNS;
nlohmann::json aa = nlohmann::json::array();
for(unsigned int i=0;i<nc->assignedAddressCount;++i) {
aa.push_back(reinterpret_cast<const InetAddress *>(&(nc->assignedAddresses[i]))->toString(tmp));
for(unsigned int i=0;i<ns.config().assignedAddressCount;++i) {
aa.push_back(reinterpret_cast<const InetAddress *>(&(ns.config().assignedAddresses[i]))->toString(tmp));
}
nj["assignedAddresses"] = aa;
nlohmann::json ra = nlohmann::json::array();
for(unsigned int i=0;i<nc->routeCount;++i) {
for(unsigned int i=0;i<ns.config().routeCount;++i) {
nlohmann::json rj;
rj["target"] = reinterpret_cast<const InetAddress *>(&(nc->routes[i].target))->toString(tmp);
if (nc->routes[i].via.ss_family == nc->routes[i].target.ss_family)
rj["via"] = reinterpret_cast<const InetAddress *>(&(nc->routes[i].via))->toIpString(tmp);
rj["target"] = reinterpret_cast<const InetAddress *>(&(ns.config().routes[i].target))->toString(tmp);
if (ns.config().routes[i].via.ss_family == ns.config().routes[i].target.ss_family)
rj["via"] = reinterpret_cast<const InetAddress *>(&(ns.config().routes[i].via))->toIpString(tmp);
else rj["via"] = nlohmann::json();
rj["flags"] = (int)nc->routes[i].flags;
rj["metric"] = (int)nc->routes[i].metric;
rj["flags"] = (int)ns.config().routes[i].flags;
rj["metric"] = (int)ns.config().routes[i].metric;
ra.push_back(rj);
}
nj["routes"] = ra;
nlohmann::json mca = nlohmann::json::array();
for(unsigned int i=0;i<nc->multicastSubscriptionCount;++i) {
for(unsigned int i=0;i<ns.config().multicastSubscriptionCount;++i) {
nlohmann::json m;
m["mac"] = MAC(nc->multicastSubscriptions[i].mac).toString(tmp);
m["adi"] = nc->multicastSubscriptions[i].adi;
m["mac"] = MAC(ns.config().multicastSubscriptions[i].mac).toString(tmp);
m["adi"] = ns.config().multicastSubscriptions[i].adi;
mca.push_back(m);
}
nj["multicastSubscriptions"] = mca;
nlohmann::json m;
m["domain"] = nc->dns.domain;
m["domain"] = ns.config().dns.domain;
m["servers"] = nlohmann::json::array();
for(int j=0;j<ZT_MAX_DNS_SERVERS;++j) {
InetAddress a(nc->dns.server_addr[j]);
InetAddress a(ns.config().dns.server_addr[j]);
if (a.isV4() || a.isV6()) {
char buf[256];
m["servers"].push_back(a.toIpString(buf));
}
}
nj["dns"] = m;
nj["authenticationURL"] = nc->authenticationURL;
nj["authenticationExpiryTime"] = nc->authenticationExpiryTime;
nj["ssoEnabled"] = nc->ssoEnabled;
if (ns.config().ssoEnabled) {
const char* authURL = ns.getAuthURL();
//fprintf(stderr, "Auth URL: %s\n", authURL);
nj["authenticationURL"] = authURL;
nj["authenticationExpiryTime"] = (ns.getExpiryTime()*1000);
nj["ssoEnabled"] = ns.config().ssoEnabled;
}
}
static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer)
@ -525,32 +732,8 @@ public:
// Deadline for the next background task service function
volatile int64_t _nextBackgroundTaskDeadline;
// Configured networks
struct NetworkState
{
NetworkState() :
tap((EthernetTap *)0)
{
// Real defaults are in network 'up' code in network event handler
settings.allowManaged = true;
settings.allowGlobal = false;
settings.allowDefault = false;
settings.allowDNS = false;
memset(&config, 0, sizeof(ZT_VirtualNetworkConfig));
}
~NetworkState()
{
this->managedRoutes.clear();
this->tap.reset();
}
std::shared_ptr<EthernetTap> tap;
ZT_VirtualNetworkConfig config; // memcpy() of raw config from core
std::vector<InetAddress> managedIps;
std::map< InetAddress, SharedPtr<ManagedRoute> > managedRoutes;
NetworkSettings settings;
};
std::map<uint64_t,NetworkState> _nets;
Mutex _nets_m;
@ -888,7 +1071,7 @@ public:
{
Mutex::Lock _l(_nets_m);
for(std::map<uint64_t,NetworkState>::iterator n(_nets.begin());n!=_nets.end();++n) {
if (n->second.tap)
if (n->second.tap())
syncManagedStuff(n->second,false,true,false);
}
}
@ -913,9 +1096,9 @@ public:
Mutex::Lock _l(_nets_m);
mgChanges.reserve(_nets.size() + 1);
for(std::map<uint64_t,NetworkState>::const_iterator n(_nets.begin());n!=_nets.end();++n) {
if (n->second.tap) {
if (n->second.tap()) {
mgChanges.push_back(std::pair< uint64_t,std::pair< std::vector<MulticastGroup>,std::vector<MulticastGroup> > >(n->first,std::pair< std::vector<MulticastGroup>,std::vector<MulticastGroup> >()));
n->second.tap->scanMulticastGroups(mgChanges.back().second.first,mgChanges.back().second.second);
n->second.tap()->scanMulticastGroups(mgChanges.back().second.first,mgChanges.back().second.second);
}
}
}
@ -1114,8 +1297,8 @@ public:
{
Mutex::Lock _l(_nets_m);
std::map<uint64_t,NetworkState>::const_iterator n(_nets.find(nwid));
if ((n != _nets.end())&&(n->second.tap))
return n->second.tap->deviceName();
if ((n != _nets.end())&&(n->second.tap()))
return n->second.tap()->deviceName();
else return std::string();
}
@ -1129,10 +1312,10 @@ public:
{
Mutex::Lock _l(_nets_m);
NetworkState &n = _nets[nwid];
*numRoutes = *numRoutes < n.config.routeCount ? *numRoutes : n.config.routeCount;
*numRoutes = *numRoutes < n.config().routeCount ? *numRoutes : n.config().routeCount;
for(unsigned int i=0; i<*numRoutes; i++) {
ZT_VirtualNetworkRoute *vnr = (ZT_VirtualNetworkRoute*)routeArray;
memcpy(&vnr[i], &(n.config.routes[i]), sizeof(ZT_VirtualNetworkRoute));
memcpy(&vnr[i], &(n.config().routes[i]), sizeof(ZT_VirtualNetworkRoute));
}
}
@ -1157,32 +1340,23 @@ public:
std::map<uint64_t,NetworkState>::const_iterator n(_nets.find(nwid));
if (n == _nets.end())
return false;
settings = n->second.settings;
settings = n->second.settings();
return true;
}
virtual bool setNetworkSettings(const uint64_t nwid,const NetworkSettings &settings)
{
Mutex::Lock _l(_nets_m);
std::map<uint64_t,NetworkState>::iterator n(_nets.find(nwid));
if (n == _nets.end())
return false;
n->second.settings = settings;
char nlcpath[4096];
OSUtils::ztsnprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_networksPath.c_str(),nwid);
FILE *out = fopen(nlcpath,"w");
if (out) {
fprintf(out,"allowManaged=%d\n",(int)n->second.settings.allowManaged);
fprintf(out,"allowGlobal=%d\n",(int)n->second.settings.allowGlobal);
fprintf(out,"allowDefault=%d\n",(int)n->second.settings.allowDefault);
fprintf(out,"allowDNS=%d\n",(int)n->second.settings.allowDNS);
fprintf(out,"allowManaged=%d\n",(int)settings.allowManaged);
fprintf(out,"allowGlobal=%d\n",(int)settings.allowGlobal);
fprintf(out,"allowDefault=%d\n",(int)settings.allowDefault);
fprintf(out,"allowDNS=%d\n",(int)settings.allowDNS);
fclose(out);
}
if (n->second.tap)
syncManagedStuff(n->second,true,true,true);
return true;
}
@ -1386,17 +1560,17 @@ public:
}
} else if (ps[0] == "network") {
ZT_VirtualNetworkList *nws = _node->networks();
if (nws) {
Mutex::Lock _l(_nets_m);
if (!_nets.empty()) {
if (ps.size() == 1) {
// Return [array] of all networks
res = nlohmann::json::array();
for(unsigned long i=0;i<nws->networkCount;++i) {
OneService::NetworkSettings localSettings;
getNetworkSettings(nws->networks[i].nwid,localSettings);
for (auto it = _nets.begin(); it != _nets.end(); ++it) {
NetworkState &ns = it->second;
nlohmann::json nj;
_networkToJson(nj,&(nws->networks[i]),portDeviceName(nws->networks[i].nwid),localSettings);
_networkToJson(nj, ns);
res.push_back(nj);
}
@ -1405,19 +1579,20 @@ public:
// Return a single network by ID or 404 if not found
const uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str());
for(unsigned long i=0;i<nws->networkCount;++i) {
if (nws->networks[i].nwid == wantnw) {
OneService::NetworkSettings localSettings;
getNetworkSettings(nws->networks[i].nwid,localSettings);
_networkToJson(res,&(nws->networks[i]),portDeviceName(nws->networks[i].nwid),localSettings);
scode = 200;
break;
}
if (_nets.find(wantnw) != _nets.end()) {
res = json::object();
NetworkState& ns = _nets[wantnw];
_networkToJson(res, ns);
scode = 200;
}
} else scode = 404;
_node->freeQueryResult((void *)nws);
} else scode = 500;
} else {
fprintf(stderr, "not found\n");
scode = 404;
}
} else {
fprintf(stderr, "_nets is empty??\n");
scode = 500;
}
} else if (ps[0] == "peer") {
ZT_PeerList *pl = _node->peers();
if (pl) {
@ -1482,7 +1657,63 @@ public:
} else scode = 404;
}
} else scode = 401; // isAuth == false
} else if (ps[0] == "sso") {
// SSO redirect handling
const char* state = zeroidc::zeroidc_get_url_param_value("state", path.c_str());
const char* nwid = zeroidc::zeroidc_network_id_from_state(state);
const uint64_t id = Utils::hexStrToU64(nwid);
Mutex::Lock l(_nets_m);
if (_nets.find(id) != _nets.end()) {
NetworkState& ns = _nets[id];
const char* code = zeroidc::zeroidc_get_url_param_value("code", path.c_str());
ns.doTokenExchange(code);
scode = 200;
responseBody = "<html>\
<head>\
<style type=\"text/css\">\
html,body {\
background: #eeeeee;\
margin: 0;\
padding: 0;\
font-family: \"Helvetica\";\
font-weight: bold;\
font-size: 12pt;\
height: 100%;\
width: 100%;\
}\
div.icon {\
background: #ffb354;\
color: #000000;\
font-size: 120pt;\
border-radius: 2.5rem;\
display: inline-block;\
width: 1.3em;\
height: 1.3em;\
padding: 0;\
margin: 15;\
line-height: 1.4em;\
vertical-align: middle;\
text-align: center;\
}\
</style>\
</head>\
<body>\
<br><br><br><br><br><br>\
<center>\
<div class=\"icon\">&#x23c1;</div>\
<div class=\"text\">Authentication Successful. You may now access the network.</div>\
</center>\
</body>\
</html>";
responseContentType = "text/html";
return scode;
} else {
scode = 404;
}
} else {
scode = 401; // isAuth == false && !sso
}
} else if ((httpMethod == HTTP_POST)||(httpMethod == HTTP_PUT)) {
if (isAuth) {
if (ps[0] == "bond") {
@ -1579,37 +1810,41 @@ public:
uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str());
_node->join(wantnw,(void *)0,(void *)0); // does nothing if we are a member
ZT_VirtualNetworkList *nws = _node->networks();
if (nws) {
for(unsigned long i=0;i<nws->networkCount;++i) {
if (nws->networks[i].nwid == wantnw) {
OneService::NetworkSettings localSettings;
getNetworkSettings(nws->networks[i].nwid,localSettings);
try {
json j(OSUtils::jsonParse(body));
if (j.is_object()) {
json &allowManaged = j["allowManaged"];
if (allowManaged.is_boolean()) localSettings.allowManaged = (bool)allowManaged;
json &allowGlobal = j["allowGlobal"];
if (allowGlobal.is_boolean()) localSettings.allowGlobal = (bool)allowGlobal;
json &allowDefault = j["allowDefault"];
if (allowDefault.is_boolean()) localSettings.allowDefault = (bool)allowDefault;
json &allowDNS = j["allowDNS"];
if (allowDNS.is_boolean()) localSettings.allowDNS = (bool)allowDNS;
}
} catch ( ... ) {
// discard invalid JSON
Mutex::Lock l(_nets_m);
if (!_nets.empty()) {
if (_nets.find(wantnw) != _nets.end()) {
NetworkState& ns = _nets[wantnw];
try {
json j(OSUtils::jsonParse(body));
json &allowManaged = j["allowManaged"];
if (allowManaged.is_boolean()) {
ns.setAllowManaged((bool)allowManaged);
}
setNetworkSettings(nws->networks[i].nwid,localSettings);
_networkToJson(res,&(nws->networks[i]),portDeviceName(nws->networks[i].nwid),localSettings);
scode = 200;
break;
json& allowGlobal = j["allowGlobal"];
if (allowGlobal.is_boolean()) {
ns.setAllowGlobal((bool)allowGlobal);
}
json& allowDefault = j["allowDefault"];
if (allowDefault.is_boolean()) {
ns.setAllowDefault((bool)allowDefault);
}
json& allowDNS = j["allowDNS"];
if (allowDNS.is_boolean()) {
ns.setAllowDNS((bool)allowDNS);
}
} catch (...) {
// discard invalid JSON
}
setNetworkSettings(wantnw, ns.settings());
if (ns.tap()) {
syncManagedStuff(ns,true,true,true);
}
_networkToJson(res, ns);
scode = 200;
}
_node->freeQueryResult((void *)nws);
} else scode = 500;
} else scode = 404;
@ -1618,8 +1853,10 @@ public:
scode = _controller->handleControlPlaneHttpPOST(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
else scode = 404;
}
} else scode = 401; // isAuth == false
}
else {
scode = 401; // isAuth == false
}
} else if (httpMethod == HTTP_DELETE) {
if (isAuth) {
@ -1650,7 +1887,6 @@ public:
scode = _controller->handleControlPlaneHttpDELETE(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
else scode = 404;
}
} else scode = 401; // isAuth = false
} else {
scode = 400;
@ -1971,12 +2207,12 @@ public:
// Checks if a managed IP or route target is allowed
bool checkIfManagedIsAllowed(const NetworkState &n,const InetAddress &target)
{
if (!n.settings.allowManaged)
if (!n.allowManaged())
return false;
if (!n.settings.allowManagedWhitelist.empty()) {
if (!n.allowManagedWhitelist().empty()) {
bool allowed = false;
for (InetAddress addr : n.settings.allowManagedWhitelist) {
for (InetAddress addr : n.allowManagedWhitelist()) {
if (addr.containsAddress(target) && addr.netmaskBits() <= target.netmaskBits()) {
allowed = true;
break;
@ -1986,7 +2222,7 @@ public:
}
if (target.isDefaultRoute())
return n.settings.allowDefault;
return n.allowDefault();
switch(target.ipScope()) {
case InetAddress::IP_SCOPE_NONE:
case InetAddress::IP_SCOPE_MULTICAST:
@ -1994,7 +2230,7 @@ public:
case InetAddress::IP_SCOPE_LINK_LOCAL:
return false;
case InetAddress::IP_SCOPE_GLOBAL:
return n.settings.allowGlobal;
return n.allowGlobal();
default:
return true;
}
@ -2018,18 +2254,18 @@ public:
// assumes _nets_m is locked
if (syncIps) {
std::vector<InetAddress> newManagedIps;
newManagedIps.reserve(n.config.assignedAddressCount);
for(unsigned int i=0;i<n.config.assignedAddressCount;++i) {
const InetAddress *ii = reinterpret_cast<const InetAddress *>(&(n.config.assignedAddresses[i]));
newManagedIps.reserve(n.config().assignedAddressCount);
for(unsigned int i=0;i<n.config().assignedAddressCount;++i) {
const InetAddress *ii = reinterpret_cast<const InetAddress *>(&(n.config().assignedAddresses[i]));
if (checkIfManagedIsAllowed(n,*ii))
newManagedIps.push_back(*ii);
}
std::sort(newManagedIps.begin(),newManagedIps.end());
newManagedIps.erase(std::unique(newManagedIps.begin(),newManagedIps.end()),newManagedIps.end());
for(std::vector<InetAddress>::iterator ip(n.managedIps.begin());ip!=n.managedIps.end();++ip) {
for(std::vector<InetAddress>::iterator ip(n.managedIps().begin());ip!=n.managedIps().end();++ip) {
if (std::find(newManagedIps.begin(),newManagedIps.end(),*ip) == newManagedIps.end()) {
if (!n.tap->removeIp(*ip))
if (!n.tap()->removeIp(*ip))
fprintf(stderr,"ERROR: unable to remove ip address %s" ZT_EOL_S, ip->toString(ipbuf));
}
}
@ -2038,39 +2274,39 @@ public:
fprintf(stderr,"ERROR: unable to add ip addresses to ifcfg" ZT_EOL_S);
#else
for(std::vector<InetAddress>::iterator ip(newManagedIps.begin());ip!=newManagedIps.end();++ip) {
if (std::find(n.managedIps.begin(),n.managedIps.end(),*ip) == n.managedIps.end()) {
if (!n.tap->addIp(*ip))
if (std::find(n.managedIps().begin(),n.managedIps().end(),*ip) == n.managedIps().end()) {
if (!n.tap()->addIp(*ip))
fprintf(stderr,"ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString(ipbuf));
}
}
#ifdef __APPLE__
if (!MacDNSHelper::addIps(n.config.nwid, n.config.mac, n.tap->deviceName().c_str(), newManagedIps))
if (!MacDNSHelper::addIps(n.config().nwid, n.config().mac, n.tap()->deviceName().c_str(), newManagedIps))
fprintf(stderr, "ERROR: unable to add v6 addresses to system configuration" ZT_EOL_S);
#endif
#endif
n.managedIps.swap(newManagedIps);
n.setManagedIps(newManagedIps);
}
if (syncRoutes) {
// Get tap device name (use LUID in hex on Windows) and IP addresses.
#if defined(__WINDOWS__) && !defined(ZT_SDK)
char tapdevbuf[64];
OSUtils::ztsnprintf(tapdevbuf,sizeof(tapdevbuf),"%.16llx",(unsigned long long)((WindowsEthernetTap *)(n.tap.get()))->luid().Value);
OSUtils::ztsnprintf(tapdevbuf,sizeof(tapdevbuf),"%.16llx",(unsigned long long)((WindowsEthernetTap *)(n.tap().get()))->luid().Value);
std::string tapdev(tapdevbuf);
#else
std::string tapdev(n.tap->deviceName());
std::string tapdev(n.tap()->deviceName());
#endif
std::vector<InetAddress> tapIps(n.tap->ips());
std::vector<InetAddress> tapIps(n.tap()->ips());
std::set<InetAddress> myIps(tapIps.begin(), tapIps.end());
for(unsigned int i=0;i<n.config.assignedAddressCount;++i)
myIps.insert(InetAddress(n.config.assignedAddresses[i]));
for(unsigned int i=0;i<n.config().assignedAddressCount;++i)
myIps.insert(InetAddress(n.config().assignedAddresses[i]));
std::set<InetAddress> haveRouteTargets;
for(unsigned int i=0;i<n.config.routeCount;++i) {
const InetAddress *const target = reinterpret_cast<const InetAddress *>(&(n.config.routes[i].target));
const InetAddress *const via = reinterpret_cast<const InetAddress *>(&(n.config.routes[i].via));
for(unsigned int i=0;i<n.config().routeCount;++i) {
const InetAddress *const target = reinterpret_cast<const InetAddress *>(&(n.config().routes[i].target));
const InetAddress *const via = reinterpret_cast<const InetAddress *>(&(n.config().routes[i].via));
// Make sure we are allowed to set this managed route, and that 'via' is not our IP. The latter
// avoids setting routes via the router on the router.
@ -2094,7 +2330,7 @@ public:
// Apple on the other hand seems to need this at least on some versions.
#ifndef __APPLE__
bool haveRoute = false;
for(std::vector<InetAddress>::iterator ip(n.managedIps.begin());ip!=n.managedIps.end();++ip) {
for(std::vector<InetAddress>::iterator ip(n.managedIps().begin());ip!=n.managedIps().end();++ip) {
if ((target->netmaskBits() == ip->netmaskBits())&&(target->containsAddress(*ip))) {
haveRoute = true;
break;
@ -2107,48 +2343,48 @@ public:
haveRouteTargets.insert(*target);
#ifndef ZT_SDK
SharedPtr<ManagedRoute> &mr = n.managedRoutes[*target];
SharedPtr<ManagedRoute> &mr = n.managedRoutes()[*target];
if (!mr)
mr.set(new ManagedRoute(*target, *via, *src, tapdev.c_str()));
#endif
}
for(std::map< InetAddress, SharedPtr<ManagedRoute> >::iterator r(n.managedRoutes.begin());r!=n.managedRoutes.end();) {
for(std::map< InetAddress, SharedPtr<ManagedRoute> >::iterator r(n.managedRoutes().begin());r!=n.managedRoutes().end();) {
if (haveRouteTargets.find(r->first) == haveRouteTargets.end())
n.managedRoutes.erase(r++);
n.managedRoutes().erase(r++);
else ++r;
}
// Sync device-local managed routes first, then indirect results. That way
// we don't get destination unreachable for routes that are via things
// that do not yet have routes in the system.
for(std::map< InetAddress, SharedPtr<ManagedRoute> >::iterator r(n.managedRoutes.begin());r!=n.managedRoutes.end();++r) {
for(std::map< InetAddress, SharedPtr<ManagedRoute> >::iterator r(n.managedRoutes().begin());r!=n.managedRoutes().end();++r) {
if (!r->second->via())
r->second->sync();
}
for(std::map< InetAddress, SharedPtr<ManagedRoute> >::iterator r(n.managedRoutes.begin());r!=n.managedRoutes.end();++r) {
for(std::map< InetAddress, SharedPtr<ManagedRoute> >::iterator r(n.managedRoutes().begin());r!=n.managedRoutes().end();++r) {
if (r->second->via())
r->second->sync();
}
}
if (syncDns) {
if (n.settings.allowDNS) {
if (strlen(n.config.dns.domain) != 0) {
if (n.allowDNS()) {
if (strlen(n.config().dns.domain) != 0) {
std::vector<InetAddress> servers;
for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) {
InetAddress a(n.config.dns.server_addr[j]);
InetAddress a(n.config().dns.server_addr[j]);
if (a.isV4() || a.isV6()) {
servers.push_back(a);
}
}
n.tap->setDns(n.config.dns.domain, servers);
n.tap()->setDns(n.config().dns.domain, servers);
}
} else {
#ifdef __APPLE__
MacDNSHelper::removeDNS(n.config.nwid);
MacDNSHelper::removeDNS(n.config().nwid);
#elif defined(__WINDOWS__)
WinDNSHelper::removeDNS(n.config.nwid);
WinDNSHelper::removeDNS(n.config().nwid);
#endif
}
@ -2417,16 +2653,16 @@ public:
{
Mutex::Lock _l(_nets_m);
NetworkState &n = _nets[nwid];
n.setWebPort(_primaryPort);
switch(op) {
switch (op) {
case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP:
if (!n.tap) {
if (!n.tap()) {
try {
char friendlyName[128];
OSUtils::ztsnprintf(friendlyName,sizeof(friendlyName),"ZeroTier One [%.16llx]",nwid);
n.tap = EthernetTap::newInstance(
n.setTap(EthernetTap::newInstance(
nullptr,
_homePath.c_str(),
MAC(nwc->mac),
@ -2435,7 +2671,7 @@ public:
nwid,
friendlyName,
StapFrameHandler,
(void *)this);
(void *)this));
*nuptr = (void *)&n;
char nlcpath[256];
@ -2449,28 +2685,28 @@ public:
std::string addresses (allowManaged.begin(), allowManaged.size());
if (allowManaged.size() <= 5) { // untidy parsing for backward compatibility
if (allowManaged[0] == '1' || allowManaged[0] == 't' || allowManaged[0] == 'T') {
n.settings.allowManaged = true;
n.setAllowManaged(true);
} else {
n.settings.allowManaged = false;
n.setAllowManaged(false);
}
} else {
// this should be a list of IP addresses
n.settings.allowManaged = true;
n.setAllowManaged(true);
size_t pos = 0;
while (true) {
size_t nextPos = addresses.find(',', pos);
std::string address = addresses.substr(pos, (nextPos == std::string::npos ? addresses.size() : nextPos) - pos);
n.settings.allowManagedWhitelist.push_back(InetAddress(address.c_str()));
n.addToAllowManagedWhiteList(InetAddress(address.c_str()));
if (nextPos == std::string::npos) break;
pos = nextPos + 1;
}
}
} else {
n.settings.allowManaged = true;
n.setAllowManaged(true);
}
n.settings.allowGlobal = nc.getB("allowGlobal", false);
n.settings.allowDefault = nc.getB("allowDefault", false);
n.settings.allowDNS = nc.getB("allowDNS", false);
n.setAllowGlobal(nc.getB("allowGlobal", false));
n.setAllowDefault(nc.getB("allowDefault", false));
n.setAllowDNS(nc.getB("allowDNS", false));
}
} catch (std::exception &exc) {
#ifdef __WINDOWS__
@ -2491,20 +2727,21 @@ public:
// After setting up tap, fall through to CONFIG_UPDATE since we also want to do this...
case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE:
memcpy(&(n.config),nwc,sizeof(ZT_VirtualNetworkConfig));
if (n.tap) { // sanity check
n.setConfig(nwc);
if (n.tap()) { // sanity check
#if defined(__WINDOWS__) && !defined(ZT_SDK)
// wait for up to 5 seconds for the WindowsEthernetTap to actually be initialized
//
// without WindowsEthernetTap::isInitialized() returning true, the won't actually
// be online yet and setting managed routes on it will fail.
const int MAX_SLEEP_COUNT = 500;
for (int i = 0; !((WindowsEthernetTap *)(n.tap.get()))->isInitialized() && i < MAX_SLEEP_COUNT; i++) {
for (int i = 0; !((WindowsEthernetTap *)(n.tap().get()))->isInitialized() && i < MAX_SLEEP_COUNT; i++) {
Sleep(10);
}
#endif
syncManagedStuff(n,true,true,true);
n.tap->setMtu(nwc->mtu);
n.tap()->setMtu(nwc->mtu);
} else {
_nets.erase(nwid);
return -999; // tap init failed
@ -2513,12 +2750,12 @@ public:
case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN:
case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY:
if (n.tap) { // sanity check
if (n.tap()) { // sanity check
#if defined(__WINDOWS__) && !defined(ZT_SDK)
std::string winInstanceId(((WindowsEthernetTap *)(n.tap.get()))->instanceId());
std::string winInstanceId(((WindowsEthernetTap *)(n.tap().get()))->instanceId());
#endif
*nuptr = (void *)0;
n.tap.reset();
n.tap().reset();
_nets.erase(nwid);
#if defined(__WINDOWS__) && !defined(ZT_SDK)
if ((op == ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY)&&(winInstanceId.length() > 0))
@ -2533,7 +2770,6 @@ public:
_nets.erase(nwid);
}
break;
}
return 0;
}
@ -2933,9 +3169,9 @@ public:
inline void nodeVirtualNetworkFrameFunction(uint64_t nwid,void **nuptr,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len)
{
NetworkState *n = reinterpret_cast<NetworkState *>(*nuptr);
if ((!n)||(!n->tap))
if ((!n)||(!n->tap()))
return;
n->tap->put(MAC(sourceMac),MAC(destMac),etherType,data,len);
n->tap()->put(MAC(sourceMac),MAC(destMac),etherType,data,len);
}
inline int nodePathCheckFunction(uint64_t ztaddr,const int64_t localSocket,const struct sockaddr_storage *remoteAddr)
@ -2944,8 +3180,8 @@ public:
{
Mutex::Lock _l(_nets_m);
for(std::map<uint64_t,NetworkState>::const_iterator n(_nets.begin());n!=_nets.end();++n) {
if (n->second.tap) {
std::vector<InetAddress> ips(n->second.tap->ips());
if (n->second.tap()) {
std::vector<InetAddress> ips(n->second.tap()->ips());
for(std::vector<InetAddress>::const_iterator i(ips.begin());i!=ips.end();++i) {
if (i->containsAddress(*(reinterpret_cast<const InetAddress *>(remoteAddr)))) {
return 0;
@ -3117,14 +3353,14 @@ public:
{
Mutex::Lock _l(_nets_m);
for(std::map<uint64_t,NetworkState>::const_iterator n(_nets.begin());n!=_nets.end();++n) {
if (n->second.tap) {
std::vector<InetAddress> ips(n->second.tap->ips());
if (n->second.tap()) {
std::vector<InetAddress> ips(n->second.tap()->ips());
for(std::vector<InetAddress>::const_iterator i(ips.begin());i!=ips.end();++i) {
if (i->ipsEqual(ifaddr))
return false;
}
#ifdef _WIN32
if (n->second.tap->friendlyName() == ifname)
if (n->second.tap()->friendlyName() == ifname)
return false;
#endif
}

View File

@ -4,9 +4,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 16.0.30517.126
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZeroTierOne", "ZeroTierOne\ZeroTierOne.vcxproj", "{B00A4957-5977-4AC1-9EF4-571DC27EADA2}"
ProjectSection(ProjectDependencies) = postProject
{175C340F-F5BA-4CB1-88AD-533B102E3799} = {175C340F-F5BA-4CB1-88AD-533B102E3799}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TapDriver6", "TapDriver6\TapDriver6.vcxproj", "{43BA7584-D4DB-4F7C-90FC-E2B18A68A213}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zeroidc", "..\zeroidc\zeroidc.vcxproj", "{175C340F-F5BA-4CB1-88AD-533B102E3799}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View File

@ -328,8 +328,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
@ -338,8 +337,9 @@
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;zeroidc.lib;bcrypt.lib;userenv.lib;crypt32.lib;secur32.lib;ncrypt.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>$(SolutionDir)\..\zeroidc\target\i686-pc-windows-msvc\debug\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">
@ -347,8 +347,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
@ -365,8 +364,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
@ -375,9 +373,10 @@
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;zeroidc.lib;bcrypt.lib;userenv.lib;crypt32.lib;secur32.lib;ncrypt.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalOptions>"notelemetry.obj" %(AdditionalOptions)</AdditionalOptions>
<AdditionalLibraryDirectories>$(SolutionDir)..\zeroidc\target\x86_64-pc-windows-msvc\debug\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
@ -385,8 +384,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_EXPORT;FD_SETSIZE=1024;NOMINMAX;STATICLIB;WIN32;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="disable";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MultiProcessorCompilation>false</MultiProcessorCompilation>
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
@ -407,10 +405,9 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_EXPORT;FD_SETSIZE=1024;STATICLIB;ZT_SALSA20_SSE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;WIN32;NOMINMAX;ZT_SOFTWARE_UPDATE_DEFAULT="apply";ZT_BUILD_PLATFORM=2;ZT_BUILD_ARCHITECTURE=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<StringPooling>true</StringPooling>
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
@ -429,8 +426,9 @@
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;zeroidc.lib;bcrypt.lib;userenv.lib;crypt32.lib;secur32.lib;ncrypt.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>$(SolutionDir)..\zeroidc\target\i686-pc-windows-msvc\release\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -440,10 +438,9 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\..\zeroidc\target;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>ZT_EXPORT;FD_SETSIZE=1024;STATICLIB;ZT_SOFTWARE_UPDATE_DEFAULT="apply";ZT_SALSA20_SSE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;WIN32;NOMINMAX;ZT_BUILD_PLATFORM=2;ZT_BUILD_ARCHITECTURE=2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
<StringPooling>true</StringPooling>
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
@ -452,7 +449,7 @@
<DisableSpecificWarnings>4996</DisableSpecificWarnings>
<ControlFlowGuard>Guard</ControlFlowGuard>
<EnableParallelCodeGeneration>false</EnableParallelCodeGeneration>
<CallingConvention>VectorCall</CallingConvention>
<CallingConvention>Cdecl</CallingConvention>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<LanguageStandard>stdcpp14</LanguageStandard>
<DebugInformationFormat>None</DebugInformationFormat>
@ -464,8 +461,9 @@
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;zeroidc.lib;bcrypt.lib;userenv.lib;crypt32.lib;secur32.lib;ncrypt.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>$(SolutionDir)..\zeroidc\target\x86_64-pc-windows-msvc\release\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -0,0 +1,5 @@
[target.x86_64-apple-darwin]
rustflags=["-C", "link-arg=-mmacosx-version-min=10.13"]
[target.aarch64-apple-darwin]
rustflags=["-C", "link-arg=-mmacosx-version-min=10.13"]

1506
zeroidc/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
zeroidc/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "zeroidc"
version = "0.1.0"
edition = "2018"
build = "build.rs"
publish = false
[lib]
crate-type = ["staticlib","rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
openidconnect = "2.1"
base64 = "0.13"
url = "2.2"
reqwest = "0.11"
jsonwebtoken = "7.2"
serde = "1.0"
time = { version = "0.3", features = ["formatting"] }
bytes = "1.1"
thiserror = "1"
[build-dependencies]
cbindgen = "0.20"

37
zeroidc/build.rs Normal file
View File

@ -0,0 +1,37 @@
extern crate cbindgen;
use std::env;
use std::path::PathBuf;
use cbindgen::{Config, Language};
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let package_name = env::var("CARGO_PKG_NAME").unwrap();
let output_file = target_dir()
.join(format!("{}.h", package_name))
.display()
.to_string();
let config = Config {
language: Language::C,
cpp_compat: true,
namespace: Some(String::from("zeroidc")),
..Default::default()
};
cbindgen::generate_with_config(&crate_dir, config)
.unwrap()
.write_to_file(&output_file);
}
/// Find the location of the `target/` directory. Note that this may be
/// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR`
/// variable.
fn target_dir() -> PathBuf {
if let Ok(target) = env::var("CARGO_TARGET_DIR") {
PathBuf::from(target)
} else {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("target")
}
}

23
zeroidc/src/error.rs Normal file
View File

@ -0,0 +1,23 @@
/*
* Copyright (c)2022 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2025-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ZeroIDCError
{
#[error(transparent)]
DiscoveryError(#[from] openidconnect::DiscoveryError<openidconnect::reqwest::Error<reqwest::Error>>),
#[error(transparent)]
ParseError(#[from] url::ParseError),
}

220
zeroidc/src/ext.rs Normal file
View File

@ -0,0 +1,220 @@
/*
* Copyright (c)2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2025-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use url::{Url};
use crate::ZeroIDC;
#[no_mangle]
pub extern "C" fn zeroidc_new(
issuer: *const c_char,
client_id: *const c_char,
auth_endpoint: *const c_char,
web_listen_port: u16,
) -> *mut ZeroIDC {
if issuer.is_null() {
println!("issuer is null");
return std::ptr::null_mut();
}
if client_id.is_null() {
println!("client_id is null");
return std::ptr::null_mut();
}
if auth_endpoint.is_null() {
println!("auth_endpoint is null");
return std::ptr::null_mut();
}
let issuer = unsafe { CStr::from_ptr(issuer) };
let client_id = unsafe { CStr::from_ptr(client_id) };
let auth_endpoint = unsafe { CStr::from_ptr(auth_endpoint) };
match ZeroIDC::new(
issuer.to_str().unwrap(),
client_id.to_str().unwrap(),
auth_endpoint.to_str().unwrap(),
web_listen_port,
) {
Ok(idc) => {
return Box::into_raw(Box::new(idc));
}
Err(s) => {
println!("Error creating ZeroIDC instance: {}", s);
return std::ptr::null_mut();
}
}
}
#[no_mangle]
pub extern "C" fn zeroidc_delete(ptr: *mut ZeroIDC) {
if ptr.is_null() {
return;
}
unsafe {
Box::from_raw(ptr);
}
}
#[no_mangle]
pub extern "C" fn zeroidc_start(ptr: *mut ZeroIDC) {
let idc = unsafe {
assert!(!ptr.is_null());
&mut *ptr
};
idc.start();
}
#[no_mangle]
pub extern "C" fn zeroidc_stop(ptr: *mut ZeroIDC) {
let idc = unsafe {
assert!(!ptr.is_null());
&mut *ptr
};
idc.stop();
}
#[no_mangle]
pub extern "C" fn zeroidc_is_running(ptr: *mut ZeroIDC) -> bool {
let idc = unsafe {
assert!(!ptr.is_null());
&mut *ptr
};
idc.is_running()
}
#[no_mangle]
pub extern "C" fn zeroidc_get_exp_time(ptr: *mut ZeroIDC) -> u64 {
let id = unsafe {
assert!(!ptr.is_null());
&mut *ptr
};
id.get_exp_time()
}
#[no_mangle]
pub extern "C" fn zeroidc_set_nonce_and_csrf(
ptr: *mut ZeroIDC,
csrf_token: *const c_char,
nonce: *const c_char) {
let idc = unsafe {
assert!(!ptr.is_null());
&mut *ptr
};
if csrf_token.is_null() {
println!("csrf_token is null");
return;
}
if nonce.is_null() {
println!("nonce is null");
return;
}
let csrf_token = unsafe { CStr::from_ptr(csrf_token) }
.to_str()
.unwrap()
.to_string();
let nonce = unsafe { CStr::from_ptr(nonce) }
.to_str()
.unwrap()
.to_string();
idc.set_nonce_and_csrf(csrf_token, nonce);
}
#[no_mangle]
pub extern "C" fn zeroidc_get_auth_url(ptr: *mut ZeroIDC) -> *const c_char {
if ptr.is_null() {
println!("passed a null object");
return std::ptr::null_mut();
}
let idc = unsafe {
&mut *ptr
};
let s = CString::new(idc.auth_url()).unwrap();
return s.into_raw();
}
#[no_mangle]
pub extern "C" fn zeroidc_token_exchange(idc: *mut ZeroIDC, code: *const c_char ) -> *const c_char {
if idc.is_null() {
println!("idc is null");
return std::ptr::null();
}
if code.is_null() {
println!("code is null");
return std::ptr::null();
}
let idc = unsafe {
&mut *idc
};
let code = unsafe{CStr::from_ptr(code)}.to_str().unwrap();
let ret = idc.do_token_exchange( code);
let ret = CString::new(ret).unwrap();
return ret.into_raw();
}
#[no_mangle]
pub extern "C" fn zeroidc_get_url_param_value(param: *const c_char, path: *const c_char) -> *const c_char {
if param.is_null() {
println!("param is null");
return std::ptr::null();
}
if path.is_null() {
println!("path is null");
return std::ptr::null();
}
let param = unsafe {CStr::from_ptr(param)}.to_str().unwrap();
let path = unsafe {CStr::from_ptr(path)}.to_str().unwrap();
let url = "http://localhost:9993".to_string() + path;
let url = Url::parse(&url).unwrap();
let pairs = url.query_pairs();
for p in pairs {
if p.0 == param {
let s = CString::new(p.1.into_owned()).unwrap();
return s.into_raw()
}
}
return std::ptr::null();
}
#[no_mangle]
pub extern "C" fn zeroidc_network_id_from_state(state: *const c_char) -> *const c_char {
if state.is_null() {
println!("state is null");
return std::ptr::null();
}
let state = unsafe{CStr::from_ptr(state)}.to_str().unwrap();
let split = state.split("_");
let split = split.collect::<Vec<&str>>();
if split.len() != 2 {
return std::ptr::null();
}
let s = CString::new(split[1]).unwrap();
return s.into_raw();
}

582
zeroidc/src/lib.rs Normal file
View File

@ -0,0 +1,582 @@
/*
* Copyright (c)2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2025-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
pub mod error;
pub mod ext;
extern crate base64;
extern crate bytes;
extern crate openidconnect;
extern crate time;
extern crate url;
use crate::error::ZeroIDCError;
use bytes::Bytes;
use jsonwebtoken::{dangerous_insecure_decode};
use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType};
use openidconnect::reqwest::http_client;
use openidconnect::{AccessToken, AccessTokenHash, AuthorizationCode, AuthenticationFlow, ClientId, CsrfToken, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, RefreshToken, Scope, TokenResponse};
use serde::{Deserialize, Serialize};
use std::str::from_utf8;
use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn, JoinHandle};
use std::time::{SystemTime, UNIX_EPOCH, Duration};
use time::{OffsetDateTime, format_description};
use url::Url;
pub struct ZeroIDC {
inner: Arc<Mutex<Inner>>,
}
struct Inner {
running: bool,
auth_endpoint: String,
oidc_thread: Option<JoinHandle<()>>,
oidc_client: Option<openidconnect::core::CoreClient>,
access_token: Option<AccessToken>,
refresh_token: Option<RefreshToken>,
exp_time: u64,
url: Option<Url>,
csrf_token: Option<CsrfToken>,
nonce: Option<Nonce>,
pkce_verifier: Option<PkceCodeVerifier>,
}
impl Inner {
#[inline]
fn as_opt(&mut self) -> Option<&mut Inner> {
Some(self)
}
}
#[derive(Debug, Serialize, Deserialize)]
struct Exp {
exp: u64
}
fn csrf_func(csrf_token: String) -> Box<dyn Fn() -> CsrfToken> {
return Box::new(move || CsrfToken::new(csrf_token.to_string()));
}
fn nonce_func(nonce: String) -> Box<dyn Fn() -> Nonce> {
return Box::new(move || Nonce::new(nonce.to_string()));
}
#[cfg(debug_assertions)]
fn systemtime_strftime<T>(dt: T, format: &str) -> String
where T: Into<OffsetDateTime>
{
let f = format_description::parse(format);
match f {
Ok(f) => {
match dt.into().format(&f) {
Ok(s) => s,
Err(_e) => "".to_string(),
}
},
Err(_e) => {
"".to_string()
},
}
}
impl ZeroIDC {
pub fn new(
issuer: &str,
client_id: &str,
auth_ep: &str,
local_web_port: u16,
) -> Result<ZeroIDC, ZeroIDCError> {
let idc = ZeroIDC {
inner: Arc::new(Mutex::new(Inner {
running: false,
auth_endpoint: auth_ep.to_string(),
oidc_thread: None,
oidc_client: None,
access_token: None,
refresh_token: None,
exp_time: 0,
url: None,
csrf_token: None,
nonce: None,
pkce_verifier: None,
})),
};
let iss = IssuerUrl::new(issuer.to_string())?;
let provider_meta = CoreProviderMetadata::discover(&iss, http_client)?;
let r = format!("http://localhost:{}/sso", local_web_port);
let redir_url = Url::parse(&r)?;
let redirect = RedirectUrl::new(redir_url.to_string())?;
(*idc.inner.lock().unwrap()).oidc_client = Some(
CoreClient::from_provider_metadata(
provider_meta,
ClientId::new(client_id.to_string()),
None,
)
.set_redirect_uri(redirect),
);
Ok(idc)
}
fn start(&mut self) {
let local = Arc::clone(&self.inner);
if !(*local.lock().unwrap()).running {
let inner_local = Arc::clone(&self.inner);
(*local.lock().unwrap()).oidc_thread = Some(spawn(move || {
(*inner_local.lock().unwrap()).running = true;
let mut running = true;
// Keep a copy of the initial nonce used to get the tokens
// Will be needed later when verifying the responses from refresh tokens
let nonce = (*inner_local.lock().unwrap()).nonce.clone();
while running {
let exp = UNIX_EPOCH + Duration::from_secs((*inner_local.lock().unwrap()).exp_time);
let now = SystemTime::now();
#[cfg(debug_assertions)] {
println!("refresh token thread tick, now: {}, exp: {}", systemtime_strftime(now, "[year]-[month]-[day] [hour]:[minute]:[second]"), systemtime_strftime(exp, "[year]-[month]-[day] [hour]:[minute]:[second]"));
}
let refresh_token = (*inner_local.lock().unwrap()).refresh_token.clone();
if let Some(refresh_token) = refresh_token {
if now >= (exp - Duration::from_secs(30)) {
let token_response = (*inner_local.lock().unwrap()).oidc_client.as_ref().map(|c| {
let res = c.exchange_refresh_token(&refresh_token)
.request(http_client);
res
});
if let Some(res) = token_response {
match res {
Ok(res) => {
let n = match nonce.clone() {
Some(n) => n,
None => {
println!("err: no nonce");
continue;
}
};
let id = match res.id_token() {
Some(t) => t,
None => {
println!("err: no id_token");
continue;
}
};
// verify & validate claims
let verified = (*inner_local.lock().unwrap()).oidc_client.as_ref().map(|c| {
let claims = match id.claims(&c.id_token_verifier(), &n) {
Ok(c) => c,
Err(e) => {
println!("claims err: {}", e);
return false;
}
};
let signing_algo = match id.signing_alg() {
Ok(s) => s,
Err(e) => {
println!("alg err: {}", e);
return false;
}
};
if let Some(expected_hash) = claims.access_token_hash() {
let actual_hash = match AccessTokenHash::from_token(res.access_token(), &signing_algo) {
Ok(h) => h,
Err(e) => {
println!("Error hashing access token: {}", e);
return false;
}
};
if actual_hash != *expected_hash {
println!("token hash error");
return false;
}
}
return true;
});
let v = match verified {
Some(verified) => {
if !verified {
println!("not verified.");
(*inner_local.lock().unwrap()).running = false;
false
} else {
true
}
},
None => {
println!("no verification performed?");
(*inner_local.lock().unwrap()).running = false;
false
}
};
if v {
match res.id_token() {
Some(id_token) => {
let params = [("id_token", id_token.to_string()),("state", "refresh".to_string())];
#[cfg(debug_assertions)] {
println!("New ID token: {}", id_token.to_string());
}
let client = reqwest::blocking::Client::new();
let r = client.post((*inner_local.lock().unwrap()).auth_endpoint.clone())
.form(&params)
.send();
match r {
Ok(r) => {
if r.status().is_success() {
#[cfg(debug_assertions)] {
println!("hit url: {}", r.url().as_str());
println!("status: {}", r.status());
}
let access_token = res.access_token();
let at = access_token.secret();
// yes this function is called `dangerous_insecure_decode`
// and it doesn't validate the jwt token signature,
// but if we've gotten this far, our claims have already
// been validated up above
let exp = dangerous_insecure_decode::<Exp>(&at);
if let Ok(e) = exp {
(*inner_local.lock().unwrap()).exp_time = e.claims.exp
}
(*inner_local.lock().unwrap()).access_token = Some(access_token.clone());
if let Some(t) = res.refresh_token() {
// println!("New Refresh Token: {}", t.secret());
(*inner_local.lock().unwrap()).refresh_token = Some(t.clone());
}
#[cfg(debug_assertions)] {
println!("Central post succeeded");
}
} else {
println!("Central post failed: {}", r.status().to_string());
println!("hit url: {}", r.url().as_str());
println!("Status: {}", r.status());
(*inner_local.lock().unwrap()).exp_time = 0;
(*inner_local.lock().unwrap()).running = false;
}
},
Err(e) => {
println!("Central post failed: {}", e.to_string());
println!("hit url: {}", e.url().unwrap().as_str());
println!("Status: {}", e.status().unwrap());
(*inner_local.lock().unwrap()).exp_time = 0;
(*inner_local.lock().unwrap()).running = false;
}
}
},
None => {
println!("no id token?!?");
}
}
} else {
println!("claims not verified");
}
},
Err(e) => {
println!("token error: {}", e);
}
}
} else {
println!("token response??");
}
} else {
#[cfg(debug_assertions)]
println!("waiting to refresh");
}
} else {
println!("no refresh token?");
}
sleep(Duration::from_secs(1));
running = (*inner_local.lock().unwrap()).running;
}
println!("thread done!")
}));
}
}
pub fn stop(&mut self) {
let local = self.inner.clone();
if (*local.lock().unwrap()).running {
if let Some(u) = (*local.lock().unwrap()).oidc_thread.take() {
u.join().expect("join failed");
}
}
}
pub fn is_running(&mut self) -> bool {
let local = Arc::clone(&self.inner);
if (*local.lock().unwrap()).running {
true
} else {
false
}
}
pub fn get_exp_time(&mut self) -> u64 {
return (*self.inner.lock().unwrap()).exp_time;
}
pub fn set_nonce_and_csrf(&mut self, csrf_token: String, nonce: String) {
let local = Arc::clone(&self.inner);
(*local.lock().expect("can't lock inner")).as_opt().map(|i| {
let need_verifier = match i.pkce_verifier {
None => true,
_ => false,
};
let csrf_diff = if let Some(csrf) = i.csrf_token.clone() {
if *csrf.secret() != csrf_token {
true
} else {
false
}
} else {
false
};
let nonce_diff = if let Some(n) = i.nonce.clone() {
if *n.secret() != nonce {
true
} else {
false
}
} else {
false
};
if need_verifier || csrf_diff || nonce_diff {
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let r = i.oidc_client.as_ref().map(|c| {
let (auth_url, csrf_token, nonce) = c
.authorize_url(
AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
csrf_func(csrf_token),
nonce_func(nonce),
)
.add_scope(Scope::new("profile".to_string()))
.add_scope(Scope::new("email".to_string()))
.add_scope(Scope::new("offline_access".to_string()))
.add_scope(Scope::new("openid".to_string()))
.set_pkce_challenge(pkce_challenge)
.url();
(auth_url, csrf_token, nonce)
});
if let Some(r) = r {
i.url = Some(r.0);
i.csrf_token = Some(r.1);
i.nonce = Some(r.2);
i.pkce_verifier = Some(pkce_verifier);
}
}
});
}
pub fn auth_url(&self) -> String {
let url = (*self.inner.lock().expect("can't lock inner")).as_opt().map(|i| {
match i.url.clone() {
Some(u) => u.to_string(),
_ => "".to_string(),
}
});
match url {
Some(url) => url.to_string(),
None => "".to_string(),
}
}
pub fn do_token_exchange(&mut self, code: &str) -> String {
let local = Arc::clone(&self.inner);
let mut should_start = false;
let res = (*local.lock().unwrap()).as_opt().map(|i| {
if let Some(verifier) = i.pkce_verifier.take() {
let token_response = i.oidc_client.as_ref().map(|c| {
let r = c.exchange_code(AuthorizationCode::new(code.to_string()))
.set_pkce_verifier(verifier)
.request(http_client);
// validate the token hashes
match r {
Ok(res) =>{
let n = match i.nonce.clone() {
Some(n) => n,
None => {
return None;
}
};
let id = match res.id_token() {
Some(t) => t,
None => {
return None;
}
};
let claims = match id.claims(&c.id_token_verifier(), &n) {
Ok(c) => c,
Err(_e) => {
return None;
}
};
let signing_algo = match id.signing_alg() {
Ok(s) => s,
Err(_) => {
return None;
}
};
if let Some(expected_hash) = claims.access_token_hash() {
let actual_hash = match AccessTokenHash::from_token(res.access_token(), &signing_algo) {
Ok(h) => h,
Err(e) => {
println!("Error hashing access token: {}", e);
return None;
}
};
if actual_hash != *expected_hash {
println!("token hash error");
return None;
}
}
Some(res)
},
Err(_e) => {
#[cfg(debug_assertions)] {
println!("token response error: {}", _e.to_string());
}
return None;
},
}
});
if let Some(Some(tok)) = token_response {
let id_token = tok.id_token().unwrap();
#[cfg(debug_assertions)] {
println!("ID token: {}", id_token.to_string());
}
let mut split = "".to_string();
match i.csrf_token.clone() {
Some(csrf_token) => {
split = csrf_token.secret().to_owned();
},
_ => (),
}
let split = split.split("_").collect::<Vec<&str>>();
if split.len() == 2 {
let params = [("id_token", id_token.to_string()),("state", split[0].to_string())];
let client = reqwest::blocking::Client::new();
let res = client.post(i.auth_endpoint.clone())
.form(&params)
.send();
match res {
Ok(res) => {
#[cfg(debug_assertions)] {
println!("hit url: {}", res.url().as_str());
println!("Status: {}", res.status());
}
let at = tok.access_token().secret();
// see previous note about this function's use
let exp = dangerous_insecure_decode::<Exp>(&at);
if let Ok(e) = exp {
i.exp_time = e.claims.exp
}
i.access_token = Some(tok.access_token().clone());
if let Some(t) = tok.refresh_token() {
i.refresh_token = Some(t.clone());
should_start = true;
}
#[cfg(debug_assertions)] {
let access_token = tok.access_token();
println!("Access Token: {}", access_token.secret());
let refresh_token = tok.refresh_token();
println!("Refresh Token: {}", refresh_token.unwrap().secret());
}
let bytes = match res.bytes() {
Ok(bytes) => bytes,
Err(_) => Bytes::from(""),
};
let bytes = match from_utf8(bytes.as_ref()) {
Ok(bytes) => bytes.to_string(),
Err(_) => "".to_string(),
};
return bytes;
},
Err(res) => {
println!("hit url: {}", res.url().unwrap().as_str());
println!("Status: {}", res.status().unwrap());
println!("Post error: {}", res.to_string());
i.exp_time = 0;
}
}
} else {
println!("invalid split length?!?");
}
}
}
"".to_string()
});
if should_start {
self.start();
}
return match res {
Some(res) => res,
_ => "".to_string(),
};
}
}

109
zeroidc/zeroidc.vcxproj Normal file
View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{175C340F-F5BA-4CB1-88AD-533B102E3799}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<NMakeBuildCommandLine>cargo build --release --target=x86_64-pc-windows-msvc</NMakeBuildCommandLine>
<NMakeOutput>
</NMakeOutput>
<NMakeCleanCommandLine>cargo clean</NMakeCleanCommandLine>
<NMakeReBuildCommandLine>cargo clean &amp; cargo build --release --target=x86_64-pc-windows-msvc</NMakeReBuildCommandLine>
<NMakePreprocessorDefinitions>NDEBUG;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<NMakeBuildCommandLine>cargo build --target=i686-pc-windows-msvc</NMakeBuildCommandLine>
<NMakeOutput>
</NMakeOutput>
<NMakeCleanCommandLine>cargo clean</NMakeCleanCommandLine>
<NMakeReBuildCommandLine>cargo clean &amp; cargo build --target=i686-pc-windows-msvc</NMakeReBuildCommandLine>
<NMakePreprocessorDefinitions>WIN32;_DEBUG;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<NMakeBuildCommandLine>cargo build --target=x86_64-pc-windows-msvc</NMakeBuildCommandLine>
<NMakeOutput>
</NMakeOutput>
<NMakeCleanCommandLine>cargo clean</NMakeCleanCommandLine>
<NMakeReBuildCommandLine>cargo clean &amp; cargo build --target=x86_64-pc-windows-msvc</NMakeReBuildCommandLine>
<NMakePreprocessorDefinitions>_DEBUG;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<NMakeBuildCommandLine>cargo build --release --target=i686-pc-windows-msvc</NMakeBuildCommandLine>
<NMakeOutput>
</NMakeOutput>
<NMakeCleanCommandLine>cargo clean</NMakeCleanCommandLine>
<NMakeReBuildCommandLine>cargo clean &amp; cargo build --release --target=i686-pc-windows-msvc</NMakeReBuildCommandLine>
<NMakePreprocessorDefinitions>WIN32;NDEBUG;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
</PropertyGroup>
<ItemDefinitionGroup>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="src\ext.rs" />
<None Include="src\lib.rs" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="target\zeroidc.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="src\ext.rs">
<Filter>Source Files</Filter>
</None>
<None Include="src\lib.rs">
<Filter>Source Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ClInclude Include="target\zeroidc.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>