284 lines
9.1 KiB
C++
284 lines
9.1 KiB
C++
/*
|
|
* Copyright (c)2019 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.
|
|
*/
|
|
/****/
|
|
|
|
#include "AddonHost.hpp"
|
|
|
|
#include "../node/Constants.hpp"
|
|
#include "../osdep/OSUtils.hpp"
|
|
|
|
namespace ZeroTier {
|
|
|
|
AddonHost::AddonHost(const std::string &homePath) :
|
|
_homePath(homePath),
|
|
_systemAddonsPath(OSUtils::platformDefaultSystemAddonsPath()),
|
|
_enabledRegistryPath(homePath + ZT_PATH_SEPARATOR_S "addons.d" + ZT_PATH_SEPARATOR_S "enabled.json")
|
|
{
|
|
}
|
|
|
|
bool AddonHost::isAddonEnabled(const std::string &id, const nlohmann::json &localConfig) const
|
|
{
|
|
try {
|
|
if (localConfig.contains("settings") && localConfig["settings"].is_object()) {
|
|
const nlohmann::json &settings = localConfig["settings"];
|
|
if (settings.contains("enabledAddons") && settings["enabledAddons"].is_array()) {
|
|
for (const auto &entry : settings["enabledAddons"]) {
|
|
if (entry.is_string() && entry.get<std::string>() == id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
} catch ( ... ) {
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AddonHost::loadFromDirectory(const std::string &dir, bool systemDir)
|
|
{
|
|
if (!OSUtils::fileExists(dir.c_str(), false)) {
|
|
return;
|
|
}
|
|
const std::vector<std::string> entries(OSUtils::listDirectory(dir.c_str()));
|
|
for (std::vector<std::string>::const_iterator i(entries.begin()); i != entries.end(); ++i) {
|
|
if (*i == "." || *i == "..") {
|
|
continue;
|
|
}
|
|
const std::string manifestPath = dir + ZT_PATH_SEPARATOR_S + *i + ZT_PATH_SEPARATOR_S "addon.json";
|
|
std::string manifestRaw;
|
|
if (!OSUtils::readFile(manifestPath.c_str(), manifestRaw)) {
|
|
continue;
|
|
}
|
|
try {
|
|
nlohmann::json manifest = OSUtils::jsonParse(manifestRaw);
|
|
if (!manifest.contains("id") || !manifest["id"].is_string()) {
|
|
continue;
|
|
}
|
|
AddonEntry entry;
|
|
entry.id = manifest["id"].get<std::string>();
|
|
entry.name = manifest.contains("name") && manifest["name"].is_string()
|
|
? manifest["name"].get<std::string>() : entry.id;
|
|
entry.version = manifest.contains("version") && manifest["version"].is_string()
|
|
? manifest["version"].get<std::string>() : "0.0.0";
|
|
entry.manifest = manifest;
|
|
entry.systemPath = dir + ZT_PATH_SEPARATOR_S + *i;
|
|
if (systemDir) {
|
|
entry.appPath = _homePath + ZT_PATH_SEPARATOR_S "app" + ZT_PATH_SEPARATOR_S "addons"
|
|
+ ZT_PATH_SEPARATOR_S + entry.id;
|
|
} else {
|
|
entry.appPath = entry.systemPath + ZT_PATH_SEPARATOR_S "frontend";
|
|
}
|
|
for (std::vector<AddonEntry>::iterator j(_addons.begin()); j != _addons.end(); ++j) {
|
|
if (j->id == entry.id) {
|
|
* j = entry;
|
|
goto next_entry;
|
|
}
|
|
}
|
|
_addons.push_back(entry);
|
|
} catch ( ... ) {
|
|
}
|
|
next_entry:;
|
|
}
|
|
}
|
|
|
|
void AddonHost::load(const nlohmann::json &localConfig)
|
|
{
|
|
_addons.clear();
|
|
loadFromDirectory(_systemAddonsPath, true);
|
|
loadFromDirectory(_homePath + ZT_PATH_SEPARATOR_S "addons.d", false);
|
|
std::vector<AddonEntry> filtered;
|
|
for (std::vector<AddonEntry>::const_iterator i(_addons.begin()); i != _addons.end(); ++i) {
|
|
if (isAddonEnabled(i->id, localConfig)) {
|
|
filtered.push_back(*i);
|
|
}
|
|
}
|
|
_addons.swap(filtered);
|
|
}
|
|
|
|
nlohmann::json AddonHost::mergedManifest() const
|
|
{
|
|
nlohmann::json out = nlohmann::json::array();
|
|
for (std::vector<AddonEntry>::const_iterator i(_addons.begin()); i != _addons.end(); ++i) {
|
|
if (!i->manifest.contains("frontend") || !i->manifest["frontend"].is_object()) {
|
|
continue;
|
|
}
|
|
nlohmann::json item = nlohmann::json::object();
|
|
item["id"] = i->id;
|
|
item["name"] = i->name;
|
|
item["version"] = i->version;
|
|
if (i->manifest.contains("frontend") && i->manifest["frontend"].is_object()) {
|
|
item["frontend"] = i->manifest["frontend"];
|
|
}
|
|
if (i->manifest.contains("requires") && i->manifest["requires"].is_array()) {
|
|
item["requires"] = i->manifest["requires"];
|
|
}
|
|
out.push_back(item);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void AddonHost::configureStaticMounts(httplib::Server &s, httplib::Server &sv6)
|
|
{
|
|
for (std::vector<AddonEntry>::const_iterator i(_addons.begin()); i != _addons.end(); ++i) {
|
|
if (!OSUtils::fileExists((i->appPath + ZT_PATH_SEPARATOR_S "index.js").c_str())
|
|
&& !OSUtils::fileExists((i->appPath + ZT_PATH_SEPARATOR_S "index.html").c_str())) {
|
|
continue;
|
|
}
|
|
const std::string mountPath = std::string("/app/addons/") + i->id;
|
|
s.set_mount_point(mountPath, i->appPath);
|
|
sv6.set_mount_point(mountPath, i->appPath);
|
|
}
|
|
}
|
|
|
|
void AddonHost::configureRoutes(
|
|
httplib::Server &s,
|
|
httplib::Server &sv6,
|
|
const std::function<void(const httplib::Request&, httplib::Response&, std::string)> &setContent)
|
|
{
|
|
auto manifestGet = [this, setContent](const httplib::Request &req, httplib::Response &res) {
|
|
setContent(req, res, mergedManifest().dump());
|
|
};
|
|
s.Get("/app/addons/manifest.json", manifestGet);
|
|
sv6.Get("/app/addons/manifest.json", manifestGet);
|
|
|
|
const std::string contactsPath = _homePath + ZT_PATH_SEPARATOR_S "contacts.json";
|
|
|
|
auto contactsGet = [contactsPath, setContent](const httplib::Request &req, httplib::Response &res) {
|
|
std::string raw;
|
|
if (!OSUtils::readFile(contactsPath.c_str(), raw)) {
|
|
setContent(req, res, "{}");
|
|
return;
|
|
}
|
|
try {
|
|
nlohmann::json c = OSUtils::jsonParse(raw);
|
|
if (!c.is_object()) {
|
|
setContent(req, res, "{}");
|
|
return;
|
|
}
|
|
setContent(req, res, c.dump());
|
|
} catch ( ... ) {
|
|
setContent(req, res, "{}");
|
|
}
|
|
};
|
|
s.Get("/app/api/contacts", contactsGet);
|
|
sv6.Get("/app/api/contacts", contactsGet);
|
|
s.Get("/api/backone/contacts", contactsGet);
|
|
sv6.Get("/api/backone/contacts", contactsGet);
|
|
|
|
auto contactsPut = [contactsPath, setContent](const httplib::Request &req, httplib::Response &res) {
|
|
try {
|
|
nlohmann::json incoming = OSUtils::jsonParse(req.body);
|
|
if (!incoming.is_object()) {
|
|
res.status = 400;
|
|
setContent(req, res, "{\"error\":\"body must be a JSON object\"}");
|
|
return;
|
|
}
|
|
nlohmann::json merged = nlohmann::json::object();
|
|
std::string existing;
|
|
if (OSUtils::readFile(contactsPath.c_str(), existing)) {
|
|
nlohmann::json cur = OSUtils::jsonParse(existing);
|
|
if (cur.is_object()) {
|
|
merged = cur;
|
|
}
|
|
}
|
|
for (auto it = incoming.begin(); it != incoming.end(); ++it) {
|
|
merged[it.key()] = it.value();
|
|
}
|
|
const std::string out = merged.dump();
|
|
if (!OSUtils::writeFile(contactsPath.c_str(), out.data(), (unsigned int)out.length())) {
|
|
res.status = 500;
|
|
setContent(req, res, "{\"error\":\"could not write contacts\"}");
|
|
return;
|
|
}
|
|
OSUtils::lockDownFile(contactsPath.c_str(), false);
|
|
setContent(req, res, merged.dump());
|
|
} catch ( ... ) {
|
|
res.status = 400;
|
|
setContent(req, res, "{\"error\":\"invalid JSON\"}");
|
|
}
|
|
};
|
|
s.Put("/app/api/contacts", contactsPut);
|
|
s.Post("/app/api/contacts", contactsPut);
|
|
sv6.Put("/app/api/contacts", contactsPut);
|
|
sv6.Post("/app/api/contacts", contactsPut);
|
|
s.Put("/api/backone/contacts", contactsPut);
|
|
s.Post("/api/backone/contacts", contactsPut);
|
|
sv6.Put("/api/backone/contacts", contactsPut);
|
|
sv6.Post("/api/backone/contacts", contactsPut);
|
|
|
|
const std::string authTokenPath = _homePath + ZT_PATH_SEPARATOR_S "authtoken.secret";
|
|
|
|
auto authtokenGet = [authTokenPath, setContent](const httplib::Request &req, httplib::Response &res) {
|
|
std::string token;
|
|
if (!OSUtils::readFile(authTokenPath.c_str(), token)) {
|
|
res.status = 404;
|
|
setContent(req, res, "{\"error\":\"authtoken not found\"}");
|
|
return;
|
|
}
|
|
while (!token.empty() && (token.back() == '\n' || token.back() == '\r' || token.back() == ' ' || token.back() == '\t')) {
|
|
token.pop_back();
|
|
}
|
|
size_t start = 0;
|
|
while (start < token.size() && (token[start] == ' ' || token[start] == '\t' || token[start] == '\n' || token[start] == '\r')) {
|
|
++start;
|
|
}
|
|
if (start > 0) {
|
|
token = token.substr(start);
|
|
}
|
|
nlohmann::json out = nlohmann::json::object();
|
|
out["token"] = token;
|
|
out["source"] = "authtoken.secret";
|
|
setContent(req, res, out.dump());
|
|
};
|
|
s.Get("/app/api/authtoken", authtokenGet);
|
|
sv6.Get("/app/api/authtoken", authtokenGet);
|
|
s.Get("/api/backone/authtoken", authtokenGet);
|
|
sv6.Get("/api/backone/authtoken", authtokenGet);
|
|
}
|
|
|
|
bool AddonHost::hasUiAddons(const std::string &homePath)
|
|
{
|
|
const std::string manifestPath = homePath + ZT_PATH_SEPARATOR_S "app" + ZT_PATH_SEPARATOR_S "addons"
|
|
+ ZT_PATH_SEPARATOR_S "manifest.json";
|
|
std::string manifestRaw;
|
|
if (OSUtils::readFile(manifestPath.c_str(), manifestRaw)) {
|
|
try {
|
|
nlohmann::json manifest = OSUtils::jsonParse(manifestRaw);
|
|
if (manifest.is_array() && !manifest.empty()) {
|
|
return true;
|
|
}
|
|
} catch ( ... ) {
|
|
}
|
|
}
|
|
const std::string addonsDir(OSUtils::platformDefaultSystemAddonsPath());
|
|
if (!OSUtils::fileExists(addonsDir.c_str(), false)) {
|
|
return false;
|
|
}
|
|
const std::vector<std::string> entries(OSUtils::listDirectory(addonsDir.c_str()));
|
|
for (std::vector<std::string>::const_iterator i(entries.begin()); i != entries.end(); ++i) {
|
|
if (*i == "." || *i == "..") {
|
|
continue;
|
|
}
|
|
std::string manifestRaw2;
|
|
const std::string path = addonsDir + ZT_PATH_SEPARATOR_S + *i + ZT_PATH_SEPARATOR_S "addon.json";
|
|
if (!OSUtils::readFile(path.c_str(), manifestRaw2)) {
|
|
continue;
|
|
}
|
|
try {
|
|
nlohmann::json m = OSUtils::jsonParse(manifestRaw2);
|
|
if (m.contains("frontend") && m["frontend"].is_object()) {
|
|
return true;
|
|
}
|
|
} catch ( ... ) {
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace ZeroTier
|