. */ require 'lib/validhostname.php'; require 'lib/ckratelimit.php'; require 'lib/ght.php'; const SNAME='acck'; const SVERS='0.1.5'; const SREPO='https://git.lattuga.net/jones/acck'; const MAXACCLEN=30+253+2; const MAXHOSTLEN=253; const INIFP='sec/conf.ini'; $usname=ucfirst(SNAME); $timeout=5; $cjr=rand(0,999999); header('Content-Language: en'); if (file_exists(INIFP)) { $conf=@parse_ini_file(INIFP,false,INI_SCANNER_RAW); if ($conf===false) die('Configuration file «'.INIFP."» exists but {$usname} could not open it."); } $errors=[]; if (isset($_GET['acctock'])) { if (strlen($_GET['acctock'])>MAXACCLEN) { $_GET['acctock']=''; $errors[]='Value for “Account address” is too long'; } $_GET['acctock']=trim($_GET['acctock']); if ($_GET['acctock']!='' && preg_match('#^@?[0-9a-zA-Z_]+(@[a-z0-9.-]{4,253})?$#',$_GET['acctock'])!==1)// todo: make it better, like split ecc. $errors[]='Value for “Account address” is not a valid fediverse account'; } else { $_GET['acctock']=''; } $hostok=false; if (isset($_GET['host'])) { if (strlen($_GET['host'])>MAXHOSTLEN) { $_GET['host']=''; $errors[]='Value for “Server domain” is too long'; } $_GET['host']=trim($_GET['host']); if ($_GET['host']!='' && !validhostname($_GET['host'])) $errors[]='Value for “Server domain” is not a valid hostname'; else $hostok=true; } else { $_GET['host']=''; } $rl=['remaining'=>400,'restime'=>0]; if ($hostok) { $rlfp='sec/'.$_GET['host'].'.rl.state'; if (file_exists($rlfp)) { $buf=@file($rlfp,FILE_IGNORE_NEW_LINES); if ($buf===false) die('Could not open rate limiting state file.'); if (count($buf)!=2 || preg_match('#^\d+$#',$buf[0])!==1 || preg_match('#^\d+$#',$buf[1])!==1) die('Malformed rate limiting state file.'); $rl=['remaining'=>$buf[0]+0,'restime'=>$buf[1]+0]; } } $now=time(); if ($rl['remaining']<=10 && $now<=$rl['restime'])// ten to leave a margin for "many people using it" and to account for 3 calls to host's endpoints $errors[]="This {$usname} instance has reached rate limit on «{$_GET['host']}», please wait at least ".ght($rl['restime']-$now,null,0).'.'; if (count($errors)>0) $errors='
There are some errors
'; else $errors=''; echo " {$usname}

{$usname}

Hello, this is {$usname}, a tool to easily check if a fediverse account is moderated (silenced, aka “limited”, and-or suspended, aka “blocked”) by a Mastodon server (aka “instance”).

Since an account can be reported as moderated by a Mastodon server even when the server is moderating the whole account’s domain, {$usname} also tries to detect if this is the case, and tells about it.

To check if an account is moderated by the given server, {$usname} tries to use the server’s «/api/v1/accounts/lookup» API endpoint, which is public and was introduced in Mastodon v3.4.0; to check if the server is moderating the whole account’s domain, {$usname} tries to use the server’s «/api/v1/instance/domain_blocks» API endpoint, which is not always set to public by the server’s admins, and was introduced in Mastodon v4.0.0. If it can’t use one or both of these endpoints, it tells.

{$usname} does not use cookies or Javascript and does not store any data about you anywhere.

You can find {$usname}’s code here, and you can contact me on Mastodon here.

".$errors."

Check

\n
\n"; if ($errors=='' && $_GET['acctock']!='' && $_GET['host']!='') { echo "
\n"; $context=[ 'http'=>[ 'method'=>'GET', 'ignore_errors'=>true, 'protocol_version'=>1.1, 'user_agent'=>'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0', 'timeout'=>5 ] ]; if (isset($conf['proxy'])) $context['http']['proxy']=$conf['proxy']; $http_response_header=null; $context=stream_context_create($context); $hhost=htmlentities($_GET['host']); $url=$_GET['host'].'/api/v1/instance'; $hurl=htmlentities($url); $res=@file_get_contents('https://'.$url,false,$context); //echo preprint($http_response_header); if (isset($http_response_header)) $rl=ckratelimit($http_response_header,'echofun',true,false); if ($res===false) { echo "
Error: could not connect to «{$hhost}».
\n"; } elseif (is_array($http_response_header) && gethttpcode($http_response_header)!='200') { echo "
Error: «{$hhost}» seems not to be running Mastodon.
\n"; } elseif (null===$res=@json_decode($res,true)) { echo "
Error: «{$hurl}» returned data which could not be parsed as JSON (".json_last_error().': '.json_last_error_msg().").
\n"; } elseif (isset($res['error'])) { echo "
Error: «{$hurl}» replied with this error message: «".htmlentities($res['error'])."».
\n"; } elseif (!isset($res['version'])) { echo "
Error: «{$hurl}» returned data in an unexpected format.
\n"; } elseif (preg_replace('#[^\d\.].*#','',$res['version'])<'3.4.0') { echo "
Error: «{$hhost}» is running a version of Mastodon that is earlier than 3.4.0 («{$res['version']}»).
\n"; } else { $acchost=preg_replace('#^@?[^@]+@(.*)$#','$1',$_GET['acctock']); if ($acchost==$_GET['acctock'] || $acchost=='') $acchost=$_GET['host']; $acchostck=false; $http_response_header=null; $url=$_GET['host'].'/api/v1/instance/domain_blocks'; $hurl=htmlentities($url); $res=@file_get_contents('https://'.$url,false,$context); //echo preprint($http_response_header); if (isset($http_response_header)) $rl=ckratelimit($http_response_header,'echofun',true,false); if ($res!==false && is_array($http_response_header) && gethttpcode($http_response_header)=='200' && null!==$res=@json_decode($res,true)) { foreach ($res as $val) { if (isset($val['domain'],$val['severity']) && $val['domain']==$acchost) { if ($val['severity']=='silence') $acchostck="The whole domain of the account to check, «{$acchost}», is silenced by «{$_GET['host']}»."; elseif ($val['severity']=='suspend') $acchostck="The whole domain of the account to check, «{$acchost}», is suspended by «{$_GET['host']}»."; else $acchostck="The whole domain of the account to check, «{$acchost}», is moderated by «{$_GET['host']}», but {$usname} could not detect the moderation severity."; break; } } if ($acchostck===false) $acchostck="The domain of the account to check, «{$acchost}», is not moderated (silenced or suspended) by «{$_GET['host']}»."; } else { $acchostck="{$usname} could not detect if the domain of the account to check, «{$acchost}», is moderated (silenced or suspended) by «{$_GET['host']}»."; } $acchostck=htmlentities($acchostck); $http_response_header=null; $url=$_GET['host'].'/api/v1/accounts/lookup?acct='.urlencode($_GET['acctock']); $hacctock=htmlentities($_GET['acctock']); $hurl=htmlentities($url); $res=@file_get_contents('https://'.$url,false,$context); //echo preprint($http_response_header); if (isset($http_response_header)) $rl=ckratelimit($http_response_header,'echofun',true,false); if ($res===false) { echo "
Error: could not connect to «{$hhost}».
\n"; } elseif (null===$res=@json_decode($res,true)) { echo "
Error: «{$hurl}» returned data which could not be parsed as JSON (".json_last_error().': '.json_last_error_msg().").
\n"; } elseif (isset($res['error'])) { if ($res['error']=='Record not found') echo "
Server «{$hhost}» doesn’t know «{$hacctock}» account.

{$acchostck}
\n"; else echo "
Error: «{$hurl}» replied with this error message: «".htmlentities($res['error'])."».
\n"; } elseif (!isset($res['id'])) { echo "
Error: «{$hurl}» returned data in an unexpected format.
\n"; } else { $out="
\n«{$hacctock}» "; (isset($res['limited']) && $res['limited']) ? $out.='is' : $out.='is not'; $out.=" silenced by «{$hhost}»
\n«{$hacctock}» "; (isset($res['suspended']) && $res['suspended']) ? $out.='is' : $out.='is not'; $out.=" suspended by «{$hhost}»

\n{$acchostck}
\n"; echo $out; } } if (is_array($rl) && @file_put_contents($rlfp,$rl['remaining']."\n".($rl['secstoreset']+time())."\n")===false) echo "
Warning: could not write to rate limit state file.
\n"; } if (isset($conf['footer'])) echo "
{$conf['footer']}
\n"; echo "
".SNAME." ".SVERS."
\n"; function preprint($var) { return '
'.print_r($var,true)."
\n"; } function gethttpcode($headers) { return preg_replace('#^[^ ]+ (\d+).*$#','$1',$headers[0]); } function echofun($msg) { echo $msg; } ?>