2023-11-18 22:00:13 +01:00
< ? php
/*
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http :// www . gnu . org / licenses />.
*/
require 'lib/validhostname.php' ;
require 'lib/ckratelimit.php' ;
require 'lib/ght.php' ;
const SNAME = 'acck' ;
2024-07-24 06:18:29 +02:00
const SVERS = '0.1.6' ;
2023-11-20 12:25:19 +01:00
const SREPO = 'https://git.lattuga.net/jones/acck' ;
2023-11-18 22:00:13 +01:00
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' ] = '' ;
2024-07-24 06:18:29 +02:00
$errors [] = 'Value for «Fediverse account address» is too long' ;
2023-11-18 22:00:13 +01:00
}
$_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.
2024-07-24 06:18:29 +02:00
$errors [] = 'Value for «Fediverse account address» is not valid' ;
2023-11-18 22:00:13 +01:00
} else {
$_GET [ 'acctock' ] = '' ;
}
$hostok = false ;
if ( isset ( $_GET [ 'host' ])) {
if ( strlen ( $_GET [ 'host' ]) > MAXHOSTLEN ) {
$_GET [ 'host' ] = '' ;
2024-07-24 06:18:29 +02:00
$errors [] = 'Value for «Mastodon instance domain» is too long' ;
2023-11-18 22:00:13 +01:00
}
$_GET [ 'host' ] = trim ( $_GET [ 'host' ]);
if ( $_GET [ 'host' ] != '' && ! validhostname ( $_GET [ 'host' ]))
2024-07-24 06:18:29 +02:00
$errors [] = 'Value for «Mastodon instance domain» is not valid' ;
2023-11-18 22:00:13 +01:00
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 = '<div class="warning">There are some errors<ul><li>' . implode ( '</li><li>' , $errors ) . '</li></ul></div><div class="horsep"></div>' ;
else
$errors = '' ;
echo " <!DOCTYPE HTML>
< html lang = \ " en \" >
< head >
< title > { $usname } </ title >
< meta http - equiv = \ " Content-Type \" content= \" text/html; charset=utf-8 \" >
2024-07-24 06:18:29 +02:00
< meta name = \ " description \" content= \" A tool to check if a fediverse account is moderated by a Mastodon instance \" >
2023-11-18 22:00:13 +01:00
< meta name = \ " viewport \" content= \" width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no \" >
< meta property = \ " og:image \" content= \" imgs/ogimage.png \" >
< link rel = \ " icon \" type= \" image/png \" href= \" imgs/icon-16.png \" sizes= \" 16x16 \" >
< link rel = \ " icon \" type= \" image/png \" href= \" imgs/icon-32.png \" sizes= \" 32x32 \" >
< link rel = \ " icon \" type= \" image/png \" href= \" imgs/icon-192.png \" sizes= \" 192x192 \" >
< link rel = \ " icon \" type= \" image/png \" href= \" imgs/icon-512.png \" sizes= \" 512x512 \" >
< link rel = \ " apple-touch-icon-precomposed \" href= \" imgs/icon-180.png \" >
< link rel = \ " stylesheet \" type= \" text/css \" href= \" css/main.css?v= { $cjr } \" >
</ head >
< body >
< div id = \ " main \" >
< h1 > { $usname } </ h1 >
< div class = \ " normtext \" >
2024-07-24 06:18:29 +02:00
< p class = \ " firstp \" >Hello, this is { $usname } , a tool to easily check if a fediverse account is <a href= \" https://docs.joinmastodon.org/admin/moderation/#limit-user \" >limited</a> (AKA «silenced») and-or <a href= \" https://docs.joinmastodon.org/admin/moderation/#suspend-user \" >suspended</a> (AKA «blocked») by a Mastodon instance.</p>
< p > Since an account is reported as limited and - or suspended by a Mastodon instance even when that instance has limited and - or suspended the whole account’ s instance , { $usname } also tries to detect if this is the case , and tells about it .</ p >
<!-- < p > To check if an account is moderated by the given server , { $usname } tries to use the server’ s < a href = \ " https://docs.joinmastodon.org/methods/accounts/#lookup \" >«/api/v1/accounts/lookup» API endpoint</a>, 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 <a href= \" https://docs.joinmastodon.org/methods/instance/#domain_blocks \" >«/api/v1/instance/domain_blocks» API endpoint</a>, 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.</p> -->
2023-11-18 22:00:13 +01:00
< p > { $usname } does not use cookies or Javascript and does not store any data about you anywhere .</ p >
2024-07-24 06:18:29 +02:00
< p > You can find { $usname } ’ s code < a href = \ " " . SREPO . " \" >here</a>, and you can contact its developer on Mastodon <a href= \" https://puntarella.party/@j0nes \" >here</a>.</p>
2023-11-18 22:00:13 +01:00
</ div >
" . $errors . "
< form method = \ " get \" id= \" mainform \" name= \" mainform \" >
2024-07-24 06:18:29 +02:00
< div class = \ " inputdiv \" ><label for= \" acctock \" >Fediverse account address</label><input type= \" text \" id= \" acctock \" name= \" acctock \" class= \" input \" placeholder= \" Example: DonaldDuck@duckburg.social \" value= \" { $_GET [ 'acctock' ] } \" maxlength= \" " . MAXACCLEN . " \" required></div>
< div class = \ " inputdiv \" ><label for= \" host \" >Mastodon instance domain</label><input type= \" text \" id= \" host \" name= \" host \" class= \" input \" placeholder= \" Example: mastodon.social \" value= \" { $_GET [ 'host' ] } \" maxlength= \" " . MAXHOSTLEN . " \" required></div>
2023-11-18 22:00:13 +01:00
< div class = \ " lastinputdiv \" ><button type= \" submit \" id= \" button \" class= \" button \" >Check</button></div> \n </form> \n " ;
if ( $errors == '' && $_GET [ 'acctock' ] != '' && $_GET [ 'host' ] != '' ) {
echo " <div class= \" horsep \" ></div> \n " ;
$context = [
'http' => [
'method' => 'GET' ,
'ignore_errors' => true ,
2023-11-20 12:25:19 +01:00
'protocol_version' => 1.1 ,
'user_agent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0' ,
2023-11-18 22:00:13 +01:00
'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 " <div class= \" error \" >Error: could not connect to « { $hhost } ».</div> \n " ;
} elseif ( is_array ( $http_response_header ) && gethttpcode ( $http_response_header ) != '200' ) {
echo " <div class= \" error \" >Error: « { $hhost } » seems not to be running Mastodon.</div> \n " ;
} elseif ( null === $res =@ json_decode ( $res , true )) {
echo " <div class= \" error \" >Error: « { $hurl } » returned data which could not be parsed as JSON ( " . json_last_error () . ': ' . json_last_error_msg () . " ).</div> \n " ;
} elseif ( isset ( $res [ 'error' ])) {
echo " <div class= \" error \" >Error: « { $hurl } » replied with this error message: « " . htmlentities ( $res [ 'error' ]) . " ».</div> \n " ;
} elseif ( ! isset ( $res [ 'version' ])) {
echo " <div class= \" error \" >Error: « { $hurl } » returned data in an unexpected format.</div> \n " ;
} elseif ( preg_replace ( '#[^\d\.].*#' , '' , $res [ 'version' ]) < '3.4.0' ) {
echo " <div class= \" error \" >Error: « { $hhost } » is running a version of Mastodon that is earlier than 3.4.0 (« { $res [ 'version' ] } »).</div> \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' )
2024-07-24 06:18:29 +02:00
$acchostck = " The whole domain of the account to check, « { $acchost } », is limited by « { $_GET [ 'host' ] } ». " ;
2023-11-18 22:00:13 +01:00
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 )
2024-07-24 06:18:29 +02:00
$acchostck = " The domain of the account to check, « { $acchost } », is neither limited nor suspended by « { $_GET [ 'host' ] } ». " ;
2023-11-18 22:00:13 +01:00
} else {
2024-07-24 06:18:29 +02:00
$acchostck = " { $usname } could not detect if the domain of the account to check, « { $acchost } », is limited or suspended by « { $_GET [ 'host' ] } ». " ;
2023-11-18 22:00:13 +01:00
}
$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 " <div class= \" error \" >Error: could not connect to « { $hhost } ».</div> \n " ;
} elseif ( null === $res =@ json_decode ( $res , true )) {
echo " <div class= \" error \" >Error: « { $hurl } » returned data which could not be parsed as JSON ( " . json_last_error () . ': ' . json_last_error_msg () . " ).</div> \n " ;
} elseif ( isset ( $res [ 'error' ])) {
if ( $res [ 'error' ] == 'Record not found' )
2024-07-24 06:32:11 +02:00
echo " <div class= \" neutral \" >« { $hhost } » does not know the « { $hacctock } » account.<br><br> { $acchostck } </div> \n " ;
2023-11-18 22:00:13 +01:00
else
echo " <div class= \" error \" >Error: « { $hurl } » replied with this error message: « " . htmlentities ( $res [ 'error' ]) . " ».</div> \n " ;
} elseif ( ! isset ( $res [ 'id' ])) {
echo " <div class= \" error \" >Error: « { $hurl } » returned data in an unexpected format.</div> \n " ;
} else {
$out = " <div class= \" neutral \" > \n « { $hacctock } » " ;
( isset ( $res [ 'limited' ]) && $res [ 'limited' ]) ? $out .= 'is' : $out .= 'is not' ;
2024-07-24 06:18:29 +02:00
$out .= " limited by « { $hhost } ».<br> \n « { $hacctock } » " ;
2023-11-18 22:00:13 +01:00
( isset ( $res [ 'suspended' ]) && $res [ 'suspended' ]) ? $out .= 'is' : $out .= 'is not' ;
2024-07-24 06:18:29 +02:00
$out .= " suspended by « { $hhost } ».<br> \n <br> \n { $acchostck } </div> \n " ;
2023-11-18 22:00:13 +01:00
echo $out ;
}
}
if ( is_array ( $rl ) && @ file_put_contents ( $rlfp , $rl [ 'remaining' ] . " \n " . ( $rl [ 'secstoreset' ] + time ()) . " \n " ) === false )
echo " <div class= \" warning \" >Warning: could not write to rate limit state file.</div> \n " ;
}
if ( isset ( $conf [ 'footer' ]))
echo " <div id= \" almfooter \" > { $conf [ 'footer' ] } </div> \n " ;
2024-07-24 06:18:29 +02:00
echo " <div id= \" footer \" ><a href= \" " . SREPO . " \" > { $usname } " . SVERS . " </a></div>
2023-11-18 22:00:13 +01:00
</ div >
</ body >
</ html > \n " ;
function preprint ( $var ) {
return '<pre>' . print_r ( $var , true ) . " </pre> \n " ;
}
function gethttpcode ( $headers ) {
return preg_replace ( '#^[^ ]+ (\d+).*$#' , '$1' , $headers [ 0 ]);
}
function echofun ( $msg ) {
echo $msg ;
}
?>