Files
dn42-pingfinder/index.php
2022-11-06 08:45:05 -07:00

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");
}