plugins; } public function __construct() { $this->config = new Config(); $this->config->read(); foreach ($this->config["plugins"] as $plugin) { try { $meta = $this->getPluginMetadata($plugin); $inst = $this->loadPlugin($plugin, $meta["class"]); $inst->onInit(new PluginContext($this, $this->config, $meta, $plugin)); } catch (Error $e) { continue; } } } private function getPluginMetadata($name): array { $root = dirname(__DIR__) . "/plugins/" . $name; if (!file_exists($root)) { throw new RuntimeException("Plugin $name dir not found"); } $metafile = $root . "/metadata.json"; if (!file_exists($metafile)) { throw new RuntimeException("Plugin $name metadata not found"); } $meta = @json_decode(@file_get_contents($metafile), true); if ($meta === null) { throw new RuntimeException("Unable to parse $name plugin metadata"); } return $meta; } private function loadPlugin($name, $classname): IPluggable { $file = dirname(__DIR__) . "/plugins/" . $name . "/" . $classname . ".php"; if (!file_exists($file)) { throw new RuntimeException("Plugin $name class $classname not found"); } require_once $file; $instance = new $classname(); if (!($instance instanceof IPluggable)) { throw new RuntimeException("Class $classname have to implement IPluggable"); } return $this->plugins[$name] = $instance; } public function getConfig(): array { $this->checkAuth(); return $this->config->asArray(); } public function setConfig($config): bool { $this->config->fromArray($config); $this->config->save(); return true; } private function checkAuth(): void { $auth = $_SESSION["auth"] ?? false; if (!$auth) { throw new RuntimeException("Unauthorized"); } } public function getNetworks(): array { $this->checkAuth(); return (new NetworkConfigReader())->getConfigs(); } public function getInvalidNetworks(): array { $this->checkAuth(); $invalid = []; foreach ((new NetworkConfigReader())->getConfigs() as $config) { foreach ($config["networks"] as $network) { if (!RouteUtil::validateSubnet($network)) { $invalid[] = $network; } } } return $invalid; } public function logout(): void { $_SESSION["auth"] = false; } public function auth($params): bool { if (isset($params["password"])) { if ($this->comparePassword($params["password"])) { return $_SESSION["auth"] = true; } else { return false; } } return $_SESSION["auth"] ?? false; } private function comparePassword($passwd): bool { $pass = $this->config["password"]; if ($pass["type"] == "plaintext") { return $pass["data"] == $passwd; } else if ($pass["type"] == "hash") { return $this->hash($passwd) == $pass["data"]; } return false; } private function hash($what): string { return md5(sha1($what) . md5($what)); } private function isInternalMethod($name) { $cls = new ReflectionClass(IPluggable::class); return $cls->hasMethod($name); } public function __invoke($method, $args) { $parts = explode("::", $method); $isPlugin = count($parts) > 1; if ($isPlugin) { $this->checkAuth(); $plugin = $this->plugins[$parts[0]]; $methodname = $parts[1]; if ($this->isInternalMethod($methodname)) { throw new RuntimeException("Unable to invoke internal methods"); } return call_user_func([$plugin, $methodname], $args); } else { $cls = new ReflectionClass(__CLASS__); $method = $cls->getMethod($method); if (!$method or !$method->isPublic()) { throw new RuntimeException("Unable to find method"); } return $method->invoke($this, $args); } } }