Промежуточный коммит
This commit is contained in:
parent
3c41ed288f
commit
3a3f6edaa0
|
|
@ -0,0 +1 @@
|
|||
/config.json
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
# protected-resources-list
|
||||
# Protected Resources
|
||||
Список ресурсов для настройки родительского контроля чтобы заблокировать доступ детям до запрещённых в РФ вражеских сервисов.
|
||||
|
||||
Список ресурсов для настройки родительского контроля чтобы заблокировать доступ детям до вражеских сервисов.
|
||||
## networks
|
||||
Каталог с файлами сервисов и их подсетей
|
||||
|
||||
## utils
|
||||
Различные скрипты и утилиты
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"password": {
|
||||
"type": "plaintext",
|
||||
"data": "admin"
|
||||
},
|
||||
"networks": [
|
||||
"google"
|
||||
],
|
||||
"zebra_restart_cmd": "/etc/init.d/zebra restart"
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"102.132.96.0/20",
|
||||
"129.134.0.0/19",
|
||||
"103.4.96.0/22",
|
||||
"157.240.0.0/16",
|
||||
"163.70.128.0/19",
|
||||
"173.252.64.0/18",
|
||||
"179.60.192.0/21",
|
||||
"185.60.216.0/21",
|
||||
"204.15.20.0/22",
|
||||
"31.13.24.0/17",
|
||||
"45.64.40.0/22",
|
||||
"57.141.0.0/16",
|
||||
"66.220.144.0/20",
|
||||
"69.171.224.0/19",
|
||||
"69.63.176.0/19",
|
||||
"74.119.76.0/22"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"188.114.98.0/23"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"179.43.150.83/32"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"104.237.160.0/19",
|
||||
"108.177.14.0/24",
|
||||
"136.22.132.0/23",
|
||||
"142.250.0.0/15",
|
||||
"172.110.32.0/21",
|
||||
"172.217.0.0/16",
|
||||
"208.117.224.0/19",
|
||||
"208.65.152.0/22",
|
||||
"209.85.0.0/16",
|
||||
"216.73.80.0/20",
|
||||
"35.186.232.0/24",
|
||||
"64.15.112.0/20",
|
||||
"64.233.0.0/16",
|
||||
"74.125.0.0/16",
|
||||
"178.66.83.0/24",
|
||||
"173.194.0.0/16",
|
||||
"173.194.221.198/32",
|
||||
"216.58.206.0/24",
|
||||
"8.8.8.8/32"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"18.245.46.0/24",
|
||||
"52.85.49.0/24"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"104.18.39.102/32",
|
||||
"172.64.148.154/32"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"domains": [],
|
||||
"networks": [
|
||||
"104.21.32.39/32",
|
||||
"172.67.182.196/32",
|
||||
"188.114.97.0/24",
|
||||
"188.114.96.0/24",
|
||||
"104.21.50.150/32",
|
||||
"172.67.163.237/32"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Routing config</title>
|
||||
<script src="jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<span id="loading">Loading...</span>
|
||||
<div style="display: none" id="panel">
|
||||
|
||||
<div id="update-panel">Checking for updates...</div>
|
||||
|
||||
Selected networks:
|
||||
<table id="net-table">
|
||||
<tr>
|
||||
<td><input type="checkbox"></td>
|
||||
<td><span>Network name</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button id="save">Save</button>
|
||||
<button id="restart-quagga">Restart quagga</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import {JSONRPC} from "./jrpc.js";
|
||||
|
||||
let config = {};
|
||||
let networks = {};
|
||||
|
||||
async function auth() {
|
||||
let authorized = await JSONRPC.__invoke("auth");
|
||||
if (!authorized) {
|
||||
do {
|
||||
let pass = prompt("Password");
|
||||
authorized = await JSONRPC.__invoke("auth", {
|
||||
"password": pass
|
||||
});
|
||||
|
||||
if (!authorized) {
|
||||
alert("Wrong password");
|
||||
}
|
||||
} while (!authorized);
|
||||
}
|
||||
|
||||
config = await JSONRPC.__invoke("getConfig");
|
||||
networks = await JSONRPC.__invoke("getNetworks");
|
||||
|
||||
$("#loading").hide();
|
||||
|
||||
fillNetworks();
|
||||
|
||||
$("#panel").show();
|
||||
}
|
||||
|
||||
|
||||
function fillNetworks() {
|
||||
let proto = $("#net-table tr");
|
||||
proto.detach();
|
||||
|
||||
for (const net in networks) {
|
||||
let item = proto.clone();
|
||||
item.find("input").prop('checked', config.networks.indexOf(net) !== -1).change(function () {
|
||||
if ($(this).prop('checked')) {
|
||||
config.networks.push(net);
|
||||
} else {
|
||||
config.networks = config.networks.filter(e => e !== net);
|
||||
}
|
||||
});
|
||||
item.find("span").text(net);
|
||||
$("#net-table").append(item);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
auth();
|
||||
$("#save").click(function () {
|
||||
const self = $(this);
|
||||
self.prop("disabled", true);
|
||||
(async function () {
|
||||
await JSONRPC.__invoke("setConfig", config);
|
||||
alert("Config saved!");
|
||||
self.prop("disabled", false);
|
||||
})();
|
||||
});
|
||||
$("#restart-quagga").click(function () {
|
||||
if (confirm("Are you sure?")) {
|
||||
const self = $(this);
|
||||
self.prop("disabled", true);
|
||||
(async function () {
|
||||
try {
|
||||
alert( await JSONRPC.__invoke("restart_quagga"));
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
self.prop("disabled", false);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
});
|
||||
(async function(){
|
||||
alert(await JSONRPC.__invoke("checkUpdates"));
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,59 @@
|
|||
export const JSONRPC = {
|
||||
url: "/rpc",
|
||||
__id: 1,
|
||||
/**
|
||||
*
|
||||
* @param method
|
||||
* @param params
|
||||
* @returns Object
|
||||
* @private
|
||||
*/
|
||||
__invoke: async function (method, params) {
|
||||
if(params === undefined){
|
||||
params = {};
|
||||
}
|
||||
const request = await JSONRPC.__performRequest(method, params);
|
||||
|
||||
if (!request.success) {
|
||||
console.error(request.result);
|
||||
throw new Error("Failed to invoke method " + method + " with params " + JSON.stringify(params));
|
||||
}
|
||||
|
||||
return request.result;
|
||||
},
|
||||
__performRequest: async function (method, params) {
|
||||
const __this = this;
|
||||
const resp = await fetch(
|
||||
__this.url,
|
||||
{
|
||||
method: "POST",
|
||||
mode: "cors",
|
||||
cache: "no-cache",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
redirect: "follow",
|
||||
referrerPolicy: "no-referrer",
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
id: __this.__id++
|
||||
})
|
||||
}
|
||||
);
|
||||
const success = resp.status === 200;
|
||||
const result = (success ? (await resp.json()).result : {
|
||||
"error": true,
|
||||
"code": resp.status,
|
||||
"status": resp.statusText,
|
||||
"body": await resp.text()
|
||||
});
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"success": success
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
class Config implements ArrayAccess
|
||||
{
|
||||
private string $path;
|
||||
|
||||
public function asArray(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function fromArray($a)
|
||||
{
|
||||
$this->data = $a;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->path = dirname(__DIR__, 2) . "/config.json";
|
||||
}
|
||||
|
||||
public function read(): void
|
||||
{
|
||||
$this->data = @json_decode(@file_get_contents($this->path), true);
|
||||
if ($this->data == null) {
|
||||
throw new RuntimeException("Failed to read or parse config file");
|
||||
}
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
file_put_contents($this->path, json_encode($this->data));
|
||||
}
|
||||
|
||||
private mixed $data = [];
|
||||
|
||||
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return $this->data[$offset];
|
||||
}
|
||||
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
class NetworkConfigReader
|
||||
{
|
||||
private array $configs = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$path = dirname(__DIR__, 2) . "/networks";
|
||||
foreach (new IteratorIterator(new DirectoryIterator($path)) as $file) {
|
||||
/**
|
||||
* @var SplFileInfo $file
|
||||
*/
|
||||
|
||||
if ($file->getExtension() === "json") {
|
||||
$key = $file->getBasename(".json");
|
||||
$value = @json_decode(@file_get_contents($file->getPathname()), true);
|
||||
|
||||
if ($value === null) {
|
||||
throw new RuntimeException("Network file " . $file->getBasename() . " is invalid or cannot be read");
|
||||
}
|
||||
|
||||
$this->configs[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getConfigs(): array
|
||||
{
|
||||
return $this->configs;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
class RoutingTableReader
|
||||
{
|
||||
private $routes = [];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRoutes(): array
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$result = @shell_exec("ip --json route show");
|
||||
|
||||
if (!$result) {
|
||||
throw new RuntimeException("Failed to read routing table");
|
||||
}
|
||||
|
||||
|
||||
$this->routes = @json_decode($result, true);
|
||||
|
||||
if ($this->routes === null) {
|
||||
throw new RuntimeException("Failed to parse json output");
|
||||
}
|
||||
|
||||
foreach ($this->routes as $key => &$route) {
|
||||
if ($route["dst"] === "default") {
|
||||
$route["dst"] = "0.0.0.0/0";
|
||||
} elseif (!str_contains($route["dst"], "/")) {
|
||||
$route["dst"] .= "/32";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
if (!function_exists('mime_content_type')) {
|
||||
|
||||
function mime_content_type($filename): bool|string
|
||||
{
|
||||
$mime_types = array(
|
||||
'txt' => 'text/plain',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'php' => 'text/html',
|
||||
'css' => 'text/css',
|
||||
'js' => 'application/javascript',
|
||||
'json' => 'application/json',
|
||||
'xml' => 'application/xml',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'flv' => 'video/x-flv',
|
||||
|
||||
// images
|
||||
'png' => 'image/png',
|
||||
'jpe' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'bmp' => 'image/bmp',
|
||||
'ico' => 'image/vnd.microsoft.icon',
|
||||
'tiff' => 'image/tiff',
|
||||
'tif' => 'image/tiff',
|
||||
'svg' => 'image/svg+xml',
|
||||
'svgz' => 'image/svg+xml',
|
||||
|
||||
// archives
|
||||
'zip' => 'application/zip',
|
||||
'rar' => 'application/x-rar-compressed',
|
||||
'exe' => 'application/x-msdownload',
|
||||
'msi' => 'application/x-msdownload',
|
||||
'cab' => 'application/vnd.ms-cab-compressed',
|
||||
|
||||
// audio/video
|
||||
'mp3' => 'audio/mpeg',
|
||||
'qt' => 'video/quicktime',
|
||||
'mov' => 'video/quicktime',
|
||||
|
||||
// adobe
|
||||
'pdf' => 'application/pdf',
|
||||
'psd' => 'image/vnd.adobe.photoshop',
|
||||
'ai' => 'application/postscript',
|
||||
'eps' => 'application/postscript',
|
||||
'ps' => 'application/postscript',
|
||||
|
||||
// ms office
|
||||
'doc' => 'application/msword',
|
||||
'rtf' => 'application/rtf',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
|
||||
// open office
|
||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
);
|
||||
|
||||
$parts = explode('.', $filename);
|
||||
$ext = strtolower(array_pop($parts));
|
||||
if (array_key_exists($ext, $mime_types)) {
|
||||
return $mime_types[$ext];
|
||||
} elseif (function_exists('finfo_open')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME);
|
||||
$mimetype = finfo_file($finfo, $filename);
|
||||
finfo_close($finfo);
|
||||
return $mimetype;
|
||||
} else {
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/php
|
||||
<?php
|
||||
require_once __DIR__ . "/common.inc.php";
|
||||
spl_autoload_register(function ($classname) {
|
||||
require_once __DIR__ . "/classes/" . $classname . ".php";
|
||||
});
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
require_once __DIR__ . "/loader.php";
|
||||
|
||||
if (str_starts_with($_SERVER["REQUEST_URI"], "/assets")) {
|
||||
$file = __DIR__ . $_SERVER["REQUEST_URI"];
|
||||
if (!file_exists($file)) {
|
||||
http_response_code(404);
|
||||
echo "File not found: " . $_SERVER["REQUEST_URI"];
|
||||
exit();
|
||||
}
|
||||
header("content-type: " . mime_content_type($file));
|
||||
echo file_get_contents($file);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!str_starts_with($_SERVER["REQUEST_URI"], "/rpc")) {
|
||||
http_response_code(302);
|
||||
header("location: /assets/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$request = json_decode(@file_get_contents('php://input'), true);
|
||||
|
||||
try {
|
||||
if ($request === null) {
|
||||
throw new RuntimeException("Failed to parse JRPC");
|
||||
}
|
||||
|
||||
foreach (["id", "jsonrpc", "method", "params"] as $param) {
|
||||
if (!isset($request[$param])) {
|
||||
throw new RuntimeException("Bad JRPC structure");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* {"jsonrpc":"2.0","method":"auth","params":{},"id":1}
|
||||
*/
|
||||
|
||||
@session_start();
|
||||
|
||||
class RPC
|
||||
{
|
||||
private Config $config;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = new Config();
|
||||
$this->config->read();
|
||||
}
|
||||
|
||||
private function hash($what): string
|
||||
{
|
||||
return md5(sha1($what) . md5($what));
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
$this->checkAuth();
|
||||
return $this->config->asArray();
|
||||
}
|
||||
|
||||
|
||||
public function restart_quagga()
|
||||
{
|
||||
return shell_exec("./zebracfg.php");
|
||||
}
|
||||
|
||||
public function checkUpdates()
|
||||
{
|
||||
$this->checkAuth();
|
||||
$data = shell_exec("git --no-pager fetch --dry-run --porcelain --verbose 2>&1");
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getNetworks(): array
|
||||
{
|
||||
$this->checkAuth();
|
||||
return (new NetworkConfigReader())->getConfigs();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function __invoke($method, $args)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
$rpc = new RPC();
|
||||
$response = $rpc($request["method"], $request["params"]);
|
||||
|
||||
header("content-type: application/json");
|
||||
@session_write_close();
|
||||
|
||||
echo json_encode([
|
||||
"jsonrpc" => "2.0",
|
||||
"id" => $request["id"],
|
||||
"result" => $response
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo $e->getMessage();
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
php -S 0.0.0.0:8000 server.php
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/php
|
||||
<?php
|
||||
require_once __DIR__ . "/loader.php";
|
||||
const CFGFILE = "/etc/quagga/zebra.conf";
|
||||
const REM_PREFIX = "! routes from file ";
|
||||
try {
|
||||
$networks = (new NetworkConfigReader())->getConfigs();
|
||||
$config = new Config();
|
||||
$config->read();
|
||||
|
||||
$routeParser = new RoutingTableReader();
|
||||
$routes = $routeParser->getRoutes();
|
||||
$defGatewayInterface = "";
|
||||
$defGateway = "";
|
||||
|
||||
foreach ($routes as $route) {
|
||||
if ($route["dst"] === "0.0.0.0/0") {
|
||||
$defGatewayInterface = $route["dev"];
|
||||
$defGateway = $route["gateway"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$defGatewayInterface) {
|
||||
throw new RuntimeException("Failed to detect default gateway interface");
|
||||
}
|
||||
|
||||
$contents = file_get_contents(CFGFILE);
|
||||
$lines = explode("\n", $contents);
|
||||
|
||||
//remove existing routes
|
||||
foreach ($lines as $key => $line) {
|
||||
if (str_starts_with($line, REM_PREFIX) or str_starts_with($line, "ip route ") and str_contains($line . " ", $defGateway . " ")) {
|
||||
unset($lines[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
//add new routes
|
||||
foreach ($config["networks"] as $key) {
|
||||
$lines[] = REM_PREFIX . $key;
|
||||
if (isset($networks[$key])) {
|
||||
foreach ($networks[$key]["networks"] as $route) {
|
||||
$lines[] = "ip route " . $route . " " . $defGateway;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($lines as $key => $line) {
|
||||
if (trim($line) === "") {
|
||||
unset($lines[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$backupFile = CFGFILE . ".sav";
|
||||
|
||||
unlink($backupFile);
|
||||
rename(CFGFILE, $backupFile);
|
||||
file_put_contents(CFGFILE, implode("\n", $lines));
|
||||
|
||||
//restart zebra
|
||||
echo shell_exec($config["zebra_restart_cmd"]);
|
||||
} catch (Exception $e) {
|
||||
echo "\nError:" . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
Loading…
Reference in New Issue