mirror of
https://git.dn42.dev/dn42/pingfinder.git
synced 2025-07-08 05:54:29 -07:00
554 lines
16 KiB
PHP
554 lines
16 KiB
PHP
<?php
|
|
|
|
require 'medoo.php';
|
|
require 'uuid.php';
|
|
|
|
function smarty_init ($rq) {
|
|
require_once('smarty/libs/Smarty.class.php');
|
|
|
|
$smarty = new Smarty();
|
|
$smarty->setTemplateDir('sm/templates/');
|
|
$smarty->setCompileDir('sm/compile/');
|
|
$smarty->setConfigDir('sm/configs/');
|
|
$smarty->setCacheDir('sm/cache/');
|
|
$smarty->assign('rq', $rq);
|
|
|
|
return function ($opt, $type, $tpl) use ($smarty) {
|
|
$smarty->assign($opt);
|
|
|
|
$template = $type .'.'. $tpl .'.tpl';
|
|
if (!$smarty->templateExists($template))
|
|
$template = "default.tpl";
|
|
|
|
return $smarty->fetch($template);
|
|
};
|
|
}
|
|
|
|
|
|
$script_version = '1.2.1';
|
|
|
|
// Misc functions.
|
|
|
|
function mime($m)
|
|
{
|
|
switch ($m) {
|
|
case strpos($m, 'application/json'):
|
|
return 'application/json';
|
|
case strpos($m, 'application/x-www-form-urlencoded'):
|
|
return 'application/x-www-form-urlencoded';
|
|
case strpos($m, 'text/html'):
|
|
return 'text/html';
|
|
case strpos($m, 'text/csv'):
|
|
return 'text/csv';
|
|
case strpos($m, 'text/environment'):
|
|
return 'text/environment';
|
|
default:
|
|
return 'text/plain';
|
|
}
|
|
}
|
|
|
|
function type($e)
|
|
{
|
|
switch ($e) {
|
|
case strpos($e, 'json'):
|
|
return 'application/json';
|
|
case strpos($e, 'post'):
|
|
return 'application/x-www-form-urlencoded';
|
|
case strpos($e, 'html'):
|
|
return 'text/html';
|
|
case strpos($e, 'csv'):
|
|
return 'text/csv';
|
|
case strpos($e, 'env'):
|
|
return 'text/environment';
|
|
default:
|
|
return 'text/plain';
|
|
}
|
|
}
|
|
|
|
function flatten($arr) {
|
|
if (empty($arr)) return [];
|
|
$string = http_build_query($arr);
|
|
$string = urldecode($string);
|
|
$string = str_replace(['[', ']'], ['_', ''], $string);
|
|
parse_str($string, $arr);
|
|
return $arr;
|
|
}
|
|
|
|
function isSequential($arr) {
|
|
return array_keys($arr) !== range(0, count($arr) - 1);
|
|
}
|
|
|
|
function send($rq, $data, $code = 400, $opt = [])
|
|
{
|
|
$proto = $rq->proto;
|
|
$accept = $rq->accept;
|
|
|
|
header("$proto $code");
|
|
header("content-type: $accept");
|
|
|
|
switch ($accept) {
|
|
case 'application/json':
|
|
|
|
echo json_encode($data);
|
|
break;
|
|
|
|
case 'text/environment':
|
|
|
|
$arr = flatten($data);
|
|
foreach($arr as $k => $v) echo strtoupper($k)."=\"".$v."\"\n";
|
|
break;
|
|
|
|
case 'text/csv':
|
|
|
|
if (gettype($data) == 'object') $data = [$data];
|
|
$data = array_values($data);
|
|
|
|
$head = flatten($data[0]);
|
|
echo implode(',', array_keys($head)) . "\n";
|
|
foreach($data as $v) echo implode(',', array_values(flatten($v))) . "\n";
|
|
break;
|
|
|
|
case 'text/plain':
|
|
|
|
$sm = smarty_init($rq);
|
|
$opt['o'] = $data;
|
|
echo $sm($opt, 'text', $rq->mode);
|
|
break;
|
|
|
|
case 'text/html':
|
|
|
|
$sm = smarty_init($rq);
|
|
$opt['o'] = $data;
|
|
echo $sm($opt, 'html', $rq->mode);
|
|
break;
|
|
|
|
default:
|
|
|
|
echo json_encode($data, JSON_PRETTY_PRINT);
|
|
break;
|
|
}
|
|
|
|
die;
|
|
}
|
|
|
|
|
|
function get_request() {
|
|
// Decode environment.
|
|
$env = $_SERVER;
|
|
$parms = $_REQUEST;
|
|
|
|
$remote_ip = $env['REMOTE_ADDR'] ?? 'NONE';
|
|
$time = $env['REQUEST_TIME'] ?? time();
|
|
$method = $env['REQUEST_METHOD'] ?? 'GET';
|
|
$proto = $env['SERVER_PROTOCOL'] ?? 'HTTP';
|
|
$server = $env['SERVER_NAME'] ?? 'UNKN';
|
|
$content_type = mime($env['HTTP_CONTENT_TYPE'] ?? 'application/json');
|
|
$content_type = mime($_GET['content_type'] ?? $content_type);
|
|
$admin = $_GET['admin'] == "jurisdiction-distance-fade-bless";
|
|
$accept = mime($parms['accept'] ?? $env['HTTP_ACCEPT'] ?? 'text/plain');
|
|
$accept = mime($_GET['accept'] ?? $accept);
|
|
|
|
// Decode request path.
|
|
$path = $env['PATH_INFO'] ?? '/';
|
|
$path = strpos($path, '?') === false ? $path : strstr($path, '?', true);
|
|
$path = trim($path, '/');
|
|
$args = explode('/', $path);
|
|
|
|
// Parse JSON input data.
|
|
if ($content_type == 'application/json')
|
|
if (in_array($method, ['PUT', 'POST'])) {
|
|
$payload = file_get_contents('php://input');
|
|
$payload = json_decode($payload, true) ?? [];
|
|
$parms = array_replace($parms, $payload);
|
|
}
|
|
|
|
// Parse Form Data
|
|
if ($content_type == 'application/x-www-form-urlencoded')
|
|
if ($method == 'PUT') {
|
|
$payload = file_get_contents('php://input');
|
|
parse_str($payload, $arr);
|
|
$parms = array_replace($parms, $arr);
|
|
}
|
|
|
|
// Decode routing
|
|
$app = array_shift($args);
|
|
$mode = array_shift($args) ?? 'root';
|
|
$id = array_shift($args);
|
|
|
|
|
|
// Create request object.
|
|
return (object)[
|
|
'time' => $time,
|
|
'proto' => $proto,
|
|
'method' => $method,
|
|
'server' => $server,
|
|
'accept' => $accept,
|
|
'app' => $app,
|
|
'id' => $id,
|
|
'mode' => $mode,
|
|
'env' => $env,
|
|
'parms' => $parms,
|
|
'content_type' => $content_type,
|
|
'remote_ip' => $remote_ip,
|
|
'admin' => $admin,
|
|
];
|
|
};
|
|
|
|
$rq = get_request();
|
|
|
|
// Convenience functions.
|
|
$err = function ($msg, $code = 400) use ($rq) {
|
|
send($rq, ["err" => $msg], $code);
|
|
};
|
|
$ok = function ($msg, $out = null, $code = 200) use ($rq) {
|
|
send($rq, $msg, $code, $out);
|
|
};
|
|
|
|
// Setup database
|
|
$db = $rq->env[$rq->app . '_db'] ?? false;
|
|
if ($db === false) return $err("No Database Defined");
|
|
|
|
list($db_user, $db_pass, $db_host, $db_port) = explode(':', $db);
|
|
$db = new medoo([
|
|
'database_type' => 'mysql',
|
|
'database_name' => $rq->app,
|
|
'server' => $db_host,
|
|
'username' => $db_user,
|
|
'password' => $db_pass,
|
|
'charset' => 'utf8'
|
|
]);
|
|
|
|
// Helper functions
|
|
function chk_ip($addr) {
|
|
if (empty($addr)) return [0, null];
|
|
|
|
switch($addr) {
|
|
case is_ipv4($addr):
|
|
return [1, inet_ntop(inet_pton($addr))];
|
|
|
|
case is_ipv6($addr):
|
|
return [2, inet_ntop(inet_pton($addr))];
|
|
|
|
default:
|
|
return [0, null];
|
|
|
|
}
|
|
}
|
|
function is_ipv4($addr) {
|
|
return false !== filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
|
}
|
|
function is_ipv6($addr) {
|
|
return false !== filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
|
}
|
|
|
|
// Data Models
|
|
$fetch_peer = function ($peer_id) use ($db) {
|
|
$o = $db->get('peers', [
|
|
'peer_id',
|
|
'peer_name',
|
|
'peer_note',
|
|
'peer_family',
|
|
'peer_country',
|
|
'peer_nick',
|
|
'peer_type',
|
|
'peer_active',
|
|
'peer_scriptver',
|
|
'peer_created',
|
|
], ['peer_id' => $peer_id]);
|
|
if ($o === false) return false;
|
|
|
|
$o['peer_type'] = explode(',', $o['peer_type']);
|
|
|
|
return $o;
|
|
};
|
|
$fetch_pending = function ($peer_id) use ($db) {
|
|
$o = $db->get('requests_pending', [
|
|
'req_id',
|
|
'req_ip',
|
|
'req_family',
|
|
'req_created',
|
|
], ['peer_id' => $peer_id]);
|
|
if ($o === false) return false;
|
|
|
|
return $o;
|
|
};
|
|
$fetch_completed = function ($req_id) use ($db) {
|
|
$o = $db->select('requests_completed', [
|
|
'req_id',
|
|
'peer_name',
|
|
'peer_note',
|
|
'req_ip',
|
|
'res_latency',
|
|
'peer_country',
|
|
'peer_family',
|
|
'peer_type',
|
|
'peer_nick',
|
|
'req_created',
|
|
'res_created',
|
|
], ['req_id' => $req_id]);
|
|
if ($o === false) return false;
|
|
|
|
return $o;
|
|
};
|
|
$fetch_requests = function () use ($db) {
|
|
$o = $db->select('requests_view', [
|
|
'req_id',
|
|
'req_ip',
|
|
'req_family',
|
|
'req_created',
|
|
'req_count',
|
|
'req_success',
|
|
],['req_hidden' => 0, 'ORDER' => 'req_created DESC', 'LIMIT' => 15]);
|
|
if ($o === false) return $db->error();
|
|
|
|
return $o;
|
|
};
|
|
$fetch_status = function ($show_id) use ($db) {
|
|
$cols = [
|
|
'peer_nick',
|
|
'peer_name',
|
|
'peer_country',
|
|
'peer_scriptver',
|
|
'last_ping',
|
|
];
|
|
|
|
if ($show_id) array_push($cols, 'peer_id', 'peer_note', 'peer_family', 'peer_type', 'peer_created', 'peer_owner');
|
|
|
|
$o = $db->select('peer_status', $cols);
|
|
if ($o === false) return $db->error();
|
|
|
|
return $o;
|
|
};
|
|
|
|
$check_peer = function ($peer_id, $peer_pin) use ($db) {
|
|
return $db->has('peers', ['AND' => ['peer_id' => $peer_id, 'peer_pin' => $peer_pin]]);
|
|
};
|
|
|
|
$store_peer = function ($peer_id, $peer_name, $peer_note, $peer_country, $peer_family, $peer_nick, $peer_type, $peer_pin) use ($db) {
|
|
$peer_id = $peer_id ?? UUID\v4();
|
|
|
|
$db->action(function(medoo $db) use ($peer_id, $peer_name, $peer_note, $peer_country, $peer_family, $peer_nick, $peer_type, $peer_pin) {
|
|
$r = [];
|
|
$r['peer_name'] = substr(strip_tags($peer_name), 0, 64);
|
|
$r['peer_note'] = substr(strip_tags($peer_note), 0, 128);
|
|
$r['peer_country'] = substr($peer_country, 0, 3);
|
|
$r['peer_family'] = (int) $peer_family;
|
|
$r['peer_nick'] = substr(strip_tags($peer_nick), 0, 64);
|
|
$r['peer_type'] = implode(',', $peer_type);
|
|
|
|
if ($peer_pin !== null) $r['peer_pin'] = substr($peer_pin, 0, 45);
|
|
|
|
if (!$db->has('peers', ['peer_id' => $peer_id])) {
|
|
$r['peer_id'] = $peer_id;
|
|
$db->insert('peers', $r);
|
|
} else {
|
|
$db->update('peers', $r, ['peer_id' => $peer_id]);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
return $peer_id;
|
|
};
|
|
|
|
$store_request = function ($req_ip, $req_hidden = 0) use ($db) {
|
|
$req_id = UUID\v4();
|
|
list($req_family, $req_ip) = chk_ip($req_ip);
|
|
if ($req_family == 0) return false;
|
|
|
|
$db->action(function(medoo $db) use ($req_id, $req_ip, $req_family, $req_hidden) {
|
|
$r = [];
|
|
$r['req_id'] = substr($req_id, 0, 36);
|
|
$r['req_ip'] = substr($req_ip, 0, 39);
|
|
$r['req_family'] = $req_family;
|
|
$r['req_hidden'] = (int) $req_hidden;
|
|
|
|
$db->insert('requests', $r);
|
|
|
|
return true;
|
|
});
|
|
|
|
return $req_id;
|
|
};
|
|
|
|
|
|
$store_result = function ($peer_id, $req_id, $res_latency, $res_scriptver = '1.0') use ($db) {
|
|
$db->action(function(medoo $db) use ($peer_id, $req_id, $res_latency, $res_scriptver) {
|
|
$r = [];
|
|
$r['peer_id'] = substr($peer_id, 0, 36);
|
|
$r['req_id'] = substr($req_id, 0, 36);
|
|
$r['res_latency'] = $res_latency == 0 ? null : (float) $res_latency;
|
|
|
|
$db->insert('results', $r);
|
|
|
|
$db->update('peers', ['peer_scriptver' => $res_scriptver], ['peer_id' => $peer_id]);
|
|
|
|
return true;
|
|
});
|
|
};
|
|
|
|
|
|
// Handle methods.
|
|
switch ("{$rq->method}_{$rq->mode}") {
|
|
case 'GET_root':
|
|
$a = $fetch_requests();
|
|
|
|
return $ok($a);
|
|
|
|
case 'GET_req':
|
|
if ($rq->id === null) return $err('missing id', 400);
|
|
|
|
if (!$db->has('requests', ['req_id' => $rq->id])) return $err('invalid req_id', 404);
|
|
|
|
$a = $fetch_completed($rq->id);
|
|
|
|
if (empty($a)) return $ok(['ok' => 'no results available'], null, 202);
|
|
|
|
$tops = [];
|
|
foreach ($a as $i) {
|
|
if (!array_key_exists($i['peer_nick'], $tops)) { $i['items'] = []; $tops[$i['peer_nick']] = $i; }
|
|
array_push($tops[$i['peer_nick']]['items'], $i);
|
|
}
|
|
|
|
$ok($a,['tops' => array_values($tops)]);
|
|
|
|
case 'POST_req':
|
|
if ($rq->id === null) {
|
|
$req_ip = $rq->parms['req_ip'] ?? false;
|
|
$req_hidden = $rq->parms['req_hidden'] ?? 0;
|
|
|
|
if ($req_ip === false || $req_ip == "") $req_ip = $rq->remote_ip;
|
|
|
|
$req_id = $store_request($req_ip, $req_hidden);
|
|
if ($req_id == false) return $err('invalid ip', 400);
|
|
|
|
$ok(['msg'=>'created', 'req_id' => $req_id]);
|
|
} else {
|
|
$req_id = $rq->id;
|
|
$peer_id = $rq->parms['peer_id'] ?? false;
|
|
$res_latency = $rq->parms['res_latency'] ?? false;
|
|
$res_scriptver = $rq->parms['peer_version'] ?? '0.0';
|
|
|
|
if ($req_id === false) return $err('missing id');
|
|
if (!$db->has('requests', ['req_id' => $req_id])) return $err('invalid req_id', 400);
|
|
|
|
if ($peer_id === false) return $err('missing peer_id');
|
|
if (!$db->has('peers', ['peer_id' => $peer_id])) return $err('invalid peer_id', 400);
|
|
|
|
if ($res_latency === false) return $err('missing res_latency');
|
|
|
|
$store_result($peer_id, $req_id, $res_latency, $res_scriptver);
|
|
|
|
$ok(['msg'=>'stored']);
|
|
}
|
|
|
|
case 'GET_peer':
|
|
if ($rq->id === null) return $err('missing id', 400);
|
|
|
|
$a = $fetch_peer($rq->id);
|
|
|
|
$TYPES = ['openvpn','gre/ipsec','gre/plain','fastd','tinc','zerotier','wireguard','pptp','l2tp','other'];
|
|
$types = [];
|
|
foreach($TYPES as $t) { $types[$t] = (in_array($t, $a['peer_type']) ? 'selected' : ''); }
|
|
|
|
$fam = [
|
|
$a['peer_family']==1?'checked':'',
|
|
$a['peer_family']==2?'checked':'',
|
|
$a['peer_family']==3?'checked':'',
|
|
];
|
|
|
|
$ok($a, [ 'types'=> $types, 'fam' => $fam ]);
|
|
|
|
case 'POST_peer':
|
|
if ($rq->id === null) {
|
|
|
|
$peer_name = $rq->parms['peer_name'] ?? '';
|
|
$peer_note = $rq->parms['peer_note'] ?? '';
|
|
$peer_country = $rq->parms['peer_country'] ?? '';
|
|
$peer_family = $rq->parms['peer_family'] ?? '';
|
|
$peer_nick = $rq->parms['peer_nick'] ?? '';
|
|
$peer_type = $rq->parms['peer_type'] ?? [];
|
|
|
|
$peer_pin = $rq->parms['peer_pin'] ?? false;
|
|
if ($peer_pin === false) return $err('missing pin');
|
|
$peer_pin = $peer_pin == null ? null : sha1($peer_pin);
|
|
|
|
$peer_id = $store_peer(null, $peer_name, $peer_note, $peer_country, $peer_family, $peer_nick, $peer_type, $peer_pin);
|
|
|
|
$ok(['msg'=>'created', 'peer_id' => $peer_id], [], 201);
|
|
} else {
|
|
$peer_id = $rq->id;
|
|
$peer_name = $rq->parms['peer_name'] ?? '';
|
|
$peer_note = $rq->parms['peer_note'] ?? '';
|
|
$peer_country = $rq->parms['peer_country'] ?? '';
|
|
$peer_family = $rq->parms['peer_family'] ?? '';
|
|
$peer_nick = $rq->parms['peer_nick'] ?? '';
|
|
$peer_type = $rq->parms['peer_type'] ?? '';
|
|
|
|
$peer_pin = $rq->parms['peer_pin'] ?? null;
|
|
$peer_pin = $peer_pin == null ? null : sha1($peer_pin);
|
|
|
|
$auth_pin = $rq->parms['auth_pin'] ?? false;
|
|
if ($auth_pin === false) return $err('missing auth_pin');
|
|
$auth_pin = $auth_pin == null ? null : sha1($auth_pin);
|
|
|
|
if (!$check_peer($peer_id, $auth_pin)) return $err("wrong auth_pin: '$auth_pin'");
|
|
|
|
$store_peer($peer_id, $peer_name, $peer_note, $peer_country, $peer_family, $peer_nick, $peer_type, $peer_pin);
|
|
|
|
$ok(['msg'=>'updated'], [], 201);
|
|
}
|
|
|
|
case 'GET_pending':
|
|
if ($rq->id === null) return $err('missing id', 400);
|
|
if (!$db->has('peers', ['peer_id' => $rq->id])) return $err('invalid id', 400);
|
|
|
|
$a = $fetch_pending($rq->id);
|
|
$a['script_version'] = $script_version;
|
|
|
|
$ok($a);
|
|
|
|
case 'GET_signup':
|
|
header("content-type: text/html");
|
|
|
|
$ok([]);
|
|
|
|
case 'GET_script':
|
|
case 'GET_script.sh':
|
|
header("content-type: text/plain");
|
|
|
|
require 'script.sh';
|
|
die;
|
|
|
|
case 'GET_script.sh.asc':
|
|
header("content-type: text/plain");
|
|
|
|
require 'script.sh.asc';
|
|
die;
|
|
|
|
case 'GET_status':
|
|
$a = $fetch_status($rq->admin);
|
|
|
|
$tops = [];
|
|
foreach ($a as $i) {
|
|
if (!array_key_exists($i['peer_nick'], $tops)) { $i['items'] = []; $tops[$i['peer_nick']] = $i; }
|
|
array_push($tops[$i['peer_nick']]['items'], $i);
|
|
}
|
|
|
|
$ok($a,['tops' => array_values($tops)]);
|
|
|
|
case 'GET_free':
|
|
// $tx = file_get_contents("https://mtn.dn42.us/free.json");
|
|
$tx = file_get_contents("/web/pub/mtn.dn42/net.dn42.registry/data/free.json");
|
|
$o = json_decode($tx,true);
|
|
|
|
foreach($o["prefixes"] as &$a) { sort($a); }
|
|
krsort($o["prefixes"], SORT_NUMERIC);
|
|
|
|
$ok($o);
|
|
|
|
|
|
default:
|
|
return $err("method not found");
|
|
}
|