/* * 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 "../node/Utils.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() == 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 entries(OSUtils::listDirectory(dir.c_str())); for (std::vector::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(); entry.name = manifest.contains("name") && manifest["name"].is_string() ? manifest["name"].get() : entry.id; entry.version = manifest.contains("version") && manifest["version"].is_string() ? manifest["version"].get() : "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::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 filtered; for (std::vector::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::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::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 &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 entries(OSUtils::listDirectory(addonsDir.c_str())); for (std::vector::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