*
* 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 .
*/
/*
* status codes
* 0 - Kicked/Banned
* 1 - Guest
* 2 - Applicant
* 3 - Member
* 4 - System message
* 5 - Moderator
* 6 - Super-Moderator
* 7 - Admin
* 8 - Super-Admin
* 9 - Private messages
*/
if (!extension_loaded('gettext')) {
prepare_stylesheets('fatal_error');
send_headers();
echo '
'.form('setup').submit(_('Go to the Setup-Page'))."$msg ".credit();
print_end();
}
function send_alogin(): void
{
print_start('alogin');
echo form('setup').'
'.credit();
print_end();
}
function send_sa_password_reset(): void
{
global $db;
print_start('sa_password_reset');
echo '
'._('Reset password').'
';
if(defined('RESET_SUPERADMIN_PASSWORD') && !empty(RESET_SUPERADMIN_PASSWORD)){
$stmt = $db->query('SELECT nickname FROM ' . PREFIX . 'members WHERE status = 8 LIMIT 1;');
if($user = $stmt->fetch(PDO::FETCH_ASSOC)){
$mem_update = $db->prepare('UPDATE ' . PREFIX . 'members SET passhash = ? WHERE nickname = ? LIMIT 1;');
$mem_update->execute([password_hash(RESET_SUPERADMIN_PASSWORD, PASSWORD_DEFAULT), $user['nickname']]);
$sess_delete = $db->prepare('DELETE FROM ' . PREFIX . 'sessions WHERE nickname = ?;');
$sess_delete->execute([$user['nickname']]);
printf('
'._('Successfully reset password for username %s. Please remove the password reset define from the script again.').'
', $user['nickname']);
}
} else {
echo '
'._("Please modify the script and put the following at the bottom of it (change the password). Then refresh this page: define('RESET_SUPERADMIN_PASSWORD', 'changeme');").'
";
echo form('admin').submit(_('Reload')).'';
print_end();
}
function send_sessions(): void
{
global $U, $db;
$stmt=$db->prepare('SELECT nickname, style, lastpost, status, useragent, ip FROM ' . PREFIX . 'sessions WHERE entry!=0 AND (incognito=0 OR status OR nickname=?) ORDER BY status DESC, lastpost DESC;');
$stmt->execute([$U['status'], $U['nickname']]);
if(!$lines=$stmt->fetchAll(PDO::FETCH_ASSOC)){
$lines=[];
}
print_start('sessions');
echo '
';
}
print_end();
}
function send_approve_waiting(): void
{
global $db;
print_start('approve_waiting');
echo '
'._('Waiting room').'
';
$result=$db->query('SELECT * FROM ' . PREFIX . 'sessions WHERE entry=0 AND status=1 ORDER BY id LIMIT 100;');
if($tmp=$result->fetchAll(PDO::FETCH_ASSOC)){
echo form('admin', 'approve');
echo '
';
}else{
echo _('No more entry requests to approve.').' ';
}
echo ' '.form('view').submit(_('Back to the chat.'), 'class="backbutton"').'';
print_end();
}
function send_waiting_room(): void
{
global $U, $db, $language;
$ga=(int) get_setting('guestaccess');
if($ga===3 && (get_count_mods()>0 || !get_setting('modfallback'))){
$wait=false;
}else{
$wait=true;
}
check_expired();
check_kicked();
$timeleft=get_setting('entrywait')-(time()-$U['lastpost']);
if($wait && ($timeleft<=0 || $ga===1)){
$U['entry']=$U['lastpost'];
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET entry=lastpost WHERE session=?;');
$stmt->execute([$U['session']]);
send_frameset();
}elseif(!$wait && $U['entry']!=0){
send_frameset();
}else{
$refresh=(int) get_setting('defaultrefresh');
print_start('waitingroom', $refresh, "$_SERVER[SCRIPT_NAME]?action=wait&session=$U[session]&lang=$language&nc=".substr(time(),-6));
echo '
'._('Waiting room').'
';
if($wait){
printf(_('Welcome %1$s, your login has been delayed, you can access the chat in %2$d seconds.'), style_this(htmlspecialchars($U['nickname']), $U['style']), $timeleft);
}else{
printf(_('Welcome %1$s, your login has been delayed, you can access the chat as soon, as a moderator lets you in.'), style_this(htmlspecialchars($U['nickname']), $U['style']));
}
echo '
';
printf(_("If this page doesn't refresh every %d seconds, use the button below to reload it manually!"), $refresh);
echo '
'.form('post');
if(isset($_POST['multi'])){
echo submit(_('Switch to single-line'));
}else{
echo hidden('multi', 'on').submit(_('Switch to multi-line'));
}
echo hidden('sendto', htmlspecialchars($_REQUEST['sendto'])).'
';
echo '
';
print_end();
}
function send_greeting(): void
{
global $U, $language;
print_start('greeting', (int) $U['refresh'], "$_SERVER[SCRIPT_NAME]?action=view&session=$U[session]&lang=$language");
printf('
'._('Welcome %s!').'
', style_this(htmlspecialchars($U['nickname']), $U['style']));
printf(''._('If this frame does not reload in %d seconds, you\'ll have to enable automatic redirection (meta refresh) in your browser. Also make sure no web filter, local proxy tool or browser plugin is preventing automatic refreshing! This could be for example "Polipo", "NoScript", etc. As a workaround (or in case of server/proxy reload errors) you can always use the buttons at the bottom to refresh manually.').'', $U['refresh']);
$rulestxt=get_setting('rulestxt');
if(!empty($rulestxt)){
echo '
'._('Rules')."
$rulestxt
";
}
print_end();
}
function send_help(): void
{
global $U;
print_start('help');
$rulestxt=get_setting('rulestxt');
if(!empty($rulestxt)){
echo '
'._('Rules')."
$rulestxt
";
}
echo '
'._('Help').'
';
echo _("All functions should be pretty much self-explaining, just use the buttons. In your profile you can adjust the refresh rate and font colour, as well as ignore users. Note: This is a chat, so if you don't keep talking, you will be automatically logged out after a while.");
if(get_setting('imgembed')){
echo ' '._('If you want to embed an image in your post, simply put [img] in front of your image URL. Example: [img]http://example.com/images/file.jpg will embed the image in your post.');
}
if($U['status']>=3){
echo ' '._("Members: You'll have some more options in your profile. You can adjust your font face, change your password anytime and of course you can delete your account.").' ';
if($U['status']>=5){
echo ' '._("Moderators: Notice the Admin-button at the bottom. It'll bring up a page where you can clean the room, kick chatters, view all active sessions and disable guest access completely if needed.").' ';
if($U['status']>=7){
echo ' '._("Admins: You'll be furthermore able to register guests, edit members and register new nicknames.").' ';
}
}
}
echo '
'.form('view').submit(_('Back to the chat.'), 'class="backbutton"').''.credit().'
';
print_end();
}
function view_publicnotes(): void
{
global $db;
$dateformat = get_setting('dateformat');
print_start('publicnotes');
echo '
'._('Public notes').'
';
$query = $db->query('SELECT lastedited, editedby, text FROM ' . PREFIX . 'notes INNER JOIN (SELECT MAX(id) AS latest FROM ' . PREFIX . 'notes WHERE type=3 GROUP BY editedby) AS t ON t.latest = id;');
while($result = $query->fetch(PDO::FETCH_OBJ)){
if (!empty($result->text)) {
if(MSGENCRYPTED){
try {
$result->text = sodium_crypto_aead_aes256gcm_decrypt(base64_decode($result->text), null, AES_IV, ENCRYPTKEY);
} catch (SodiumException $e){
send_error($e->getMessage());
}
}
echo '
';
printf(_('Last edited by %1$s at %2$s'), htmlspecialchars($result->editedby), date($dateformat, $result->lastedited));
echo ' ';
echo '';
echo ' ';
}
}
print_end();
}
function send_profile(string $arg=''): void
{
global $U, $db, $language;
print_start('profile');
echo form('profile', 'save').'
'._('Your Profile')."
$arg
";
thr();
$ignored=[];
$stmt=$db->prepare('SELECT ign FROM ' . PREFIX . 'ignored WHERE ignby=? ORDER BY LOWER(ign);');
$stmt->execute([$U['nickname']]);
while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){
$ignored[]=htmlspecialchars($tmp['ign']);
}
if(count($ignored)>0){
echo '
";
}
if($U['status']>=2 && $U['eninbox']!=0){
$stmt=$db->prepare('SELECT COUNT(*) FROM ' . PREFIX . 'inbox WHERE recipient=?;');
$stmt->execute([$U['nickname']]);
$tmp=$stmt->fetch(PDO::FETCH_NUM);
if($tmp[0]>0){
echo '
'.form('inbox').submit(sprintf(_('Read %d messages in your inbox'), $tmp[0])).'
';
}
}
if($U['status']>=5 && get_setting('guestaccess')==3){
$result=$db->query('SELECT COUNT(*) FROM ' . PREFIX . 'sessions WHERE entry=0 AND status=1;');
$temp=$result->fetch(PDO::FETCH_NUM);
if($temp[0]>0){
echo '
';
echo form('admin', 'approve');
echo submit(sprintf(_('%d new guests to approve'), $temp[0])).'
';
}
}
echo '';
}
function print_chatters(): void
{
global $U, $db, $language;
if(!$U['hidechatters']){
echo '
';
$stmt=$db->prepare('SELECT nickname, style, status, exiting FROM ' . PREFIX . 'sessions WHERE entry!=0 AND status>0 AND incognito=0 AND nickname NOT IN (SELECT ign FROM '. PREFIX . 'ignored WHERE ignby=? UNION SELECT ignby FROM '. PREFIX . 'ignored WHERE ign=?) ORDER BY status DESC, lastpost DESC;');
$stmt->execute([$U['nickname'], $U['nickname']]);
$nc=substr(time(), -6);
$G=$M=$S=$A=[];
$channellink="fetch(PDO::FETCH_NUM)){
$link=$nicklink.urlencode($user[0]).'" target="post">'.style_this(htmlspecialchars($user[0]), $user[1]).'';
if ($user[3]>0) {
$link .= ''.get_setting('exitingtxt').'';
}
if($user[2]<3){ // guest or superguest
$G[]=$link;
} elseif($user[2]>=7){ // admin or superadmin
$A[]=$link;
} elseif(($user[2]>=5) && ($user[2]<=6)){ // moderator or supermoderator
$S[]=$link;
} elseif($user[2]=3){ // member
$M[]=$link;
}
}
if($U['status']>5){ // can chat in admin channel
echo '
';
}
}
// session management
function create_session(bool $setup, string $nickname, string $password): void
{
global $U;
$U['nickname']=preg_replace('/\s/', '', $nickname);
if(check_member($password)){
if($setup && $U['status']>=7){
$U['incognito']=1;
}
$U['entry']=$U['lastpost']=time();
}else{
add_user_defaults($password);
check_captcha($_POST['challenge'] ?? '', $_POST['captcha'] ?? '');
$ga=(int) get_setting('guestaccess');
if(!valid_nick($U['nickname'])){
send_error(sprintf(_('Invalid nickname (%1$d characters maximum and has to match the regular expression "%2$s")'), get_setting('maxname'), get_setting('nickregex')));
}
if(!valid_pass($password)){
send_error(sprintf(_('Invalid password (At least %1$d characters and has to match the regular expression "%2$s")'), get_setting('minpass'), get_setting('passregex')));
}
if($ga===0){
send_error(_('Sorry, currently members only!'));
}elseif(in_array($ga, [2, 3], true)){
$U['entry'] = 0;
}
if(get_setting('englobalpass')!=0 && isset($_POST['globalpass']) && $_POST['globalpass']!=get_setting('globalpass')){
send_error(_('Wrong global Password!'));
}
}
$U['exiting']=0;
try {
$U[ 'postid' ] = bin2hex( random_bytes( 3 ) );
} catch(Exception $e) {
send_error($e->getMessage());
}
write_new_session($password);
}
function check_captcha(string $challenge, string $captcha_code): void
{
global $db, $memcached;
$captcha=(int) get_setting('captcha');
if($captcha!==0){
if(empty($challenge)){
send_error(_('Wrong Captcha'));
}
$code = '';
if(MEMCACHED){
if(!$code=$memcached->get(DBNAME . '-' . PREFIX . "captcha-$_POST[challenge]")){
send_error(_('Captcha already used or timed out.'));
}
$memcached->delete(DBNAME . '-' . PREFIX . "captcha-$_POST[challenge]");
}else{
$stmt=$db->prepare('SELECT code FROM ' . PREFIX . 'captcha WHERE id=?;');
$stmt->execute([$challenge]);
$stmt->bindColumn(1, $code);
if(!$stmt->fetch(PDO::FETCH_BOUND)){
send_error(_('Captcha already used or timed out.'));
}
$time=time();
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'captcha WHERE id=? OR time<(?-(SELECT value FROM ' . PREFIX . "settings WHERE setting='captchatime'));");
$stmt->execute([$challenge, $time]);
}
if($captcha_code!==$code){
if($captcha!==3 || strrev($captcha_code)!==$code){
send_error(_('Wrong Captcha'));
}
}
}
}
function is_definitely_ssl() : bool {
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
return true;
}
if (isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'])) {
return true;
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && ('https' === $_SERVER['HTTP_X_FORWARDED_PROTO'])) {
return true;
}
return false;
}
function set_secure_cookie(string $name, string $value): void
{
if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
setcookie($name, $value, ['expires' => 0, 'path' => '/', 'domain' => '', 'secure' => is_definitely_ssl(), 'httponly' => true, 'samesite' => 'Strict']);
}else{
setcookie($name, $value, 0, '/', '', is_definitely_ssl(), true);
}
}
function write_new_session(string $password): void
{
global $U, $db, $session;
$stmt=$db->prepare('SELECT * FROM ' . PREFIX . 'sessions WHERE nickname=?;');
$stmt->execute([$U['nickname']]);
if($temp=$stmt->fetch(PDO::FETCH_ASSOC)){
// check whether alrady logged in
if(password_verify($password, $temp['passhash'])){
$U=$temp;
check_kicked();
set_secure_cookie(COOKIENAME, $U['session']);
}else{
send_error(_('A user with this nickname is already logged in.')." "._('Wrong Password!'));
}
}else{
// create new session
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'sessions WHERE session=?;');
do{
try {
$U[ 'session' ] = bin2hex( random_bytes( 16 ) );
} catch(Exception $e) {
send_error($e->getMessage());
}
$stmt->execute([$U['session']]);
}while($stmt->fetch(PDO::FETCH_NUM)); // check for hash collision
if(isset($_SERVER['HTTP_USER_AGENT'])){
$useragent=htmlspecialchars($_SERVER['HTTP_USER_AGENT']);
}else{
$useragent='';
}
if(get_setting('trackip')){
$ip=$_SERVER['REMOTE_ADDR'];
}else{
$ip='';
}
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'sessions (session, nickname, status, refresh, style, lastpost, passhash, useragent, bgcolour, entry, exiting, timestamps, embed, incognito, ip, nocache, tz, eninbox, sortupdown, hidechatters, nocache_old, postid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);');
$stmt->execute([$U['session'], $U['nickname'], $U['status'], $U['refresh'], $U['style'], $U['lastpost'], $U['passhash'], $useragent, $U['bgcolour'], $U['entry'], $U['exiting'], $U['timestamps'], $U['embed'], $U['incognito'], $ip, $U['nocache'], $U['tz'], $U['eninbox'], $U['sortupdown'], $U['hidechatters'], $U['nocache_old'], $U['postid']]);
$session = $U['session'];
set_secure_cookie(COOKIENAME, $U['session']);
if($U['status']>=3 && !$U['incognito']){
add_system_message(sprintf(get_setting('msgenter'), style_this(htmlspecialchars($U['nickname']), $U['style'])), '');
}
}
}
function show_fails(): void
{
global $db, $U;
$stmt=$db->prepare('SELECT loginfails FROM ' . PREFIX . 'members WHERE nickname=?;');
$stmt->execute([$U['nickname']]);
$temp=$stmt->fetch(PDO::FETCH_NUM);
if($temp && $temp[0]>0){
print_start('failednotice');
echo $temp[0] . " " . _('Failed login attempt(s)') . " ";
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET loginfails=? WHERE nickname=?;');
$stmt->execute([0, $U['nickname']]);
echo form_target('_self', 'login').submit(_('Dismiss')).'';
print_end();
}
}
function approve_session(): void
{
global $db;
if(isset($_POST['what'])){
if($_POST['what']==='allowchecked' && isset($_POST['csid'])){
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET entry=lastpost WHERE nickname=?;');
foreach($_POST['csid'] as $nick){
$stmt->execute([$nick]);
}
}elseif($_POST['what']==='allowall' && isset($_POST['alls'])){
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET entry=lastpost WHERE nickname=?;');
foreach($_POST['alls'] as $nick){
$stmt->execute([$nick]);
}
}elseif($_POST['what']==='denychecked' && isset($_POST['csid'])){
$time=60*(get_setting('kickpenalty')-get_setting('guestexpire'))+time();
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET lastpost=?, status=0, kickmessage=? WHERE nickname=? AND status=1;');
foreach($_POST['csid'] as $nick){
$stmt->execute([$time, $_POST['kickmessage'], $nick]);
}
}elseif($_POST['what']==='denyall' && isset($_POST['alls'])){
$time=60*(get_setting('kickpenalty')-get_setting('guestexpire'))+time();
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET lastpost=?, status=0, kickmessage=? WHERE nickname=? AND status=1;');
foreach($_POST['alls'] as $nick){
$stmt->execute([$time, $_POST['kickmessage'], $nick]);
}
}
}
}
function check_login(): void
{
global $U, $db;
$ga=(int) get_setting('guestaccess');
parse_sessions();
if(isset($U['session'])){
if($U['exiting']==1){
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET exiting=0 WHERE session=? LIMIT 1;');
$stmt->execute([$U['session']]);
}
check_kicked();
}elseif(get_setting('englobalpass')==1 && (!isset($_POST['globalpass']) || $_POST['globalpass']!=get_setting('globalpass'))){
send_error(_('Wrong global Password!'));
}elseif(!isset($_POST['nick']) || !isset($_POST['pass'])){
send_login();
}else{
if($ga===4){
send_chat_disabled();
}
if(!empty($_POST['regpass']) && $_POST['regpass']!==$_POST['pass']){
send_error(_('Password confirmation does not match!'));
}
create_session(false, $_POST['nick'], $_POST['pass']);
if(!empty($_POST['regpass'])){
$guestreg=(int) get_setting('guestreg');
if($guestreg===1){
register_guest(2, $_POST['nick']);
$U['status']=2;
}elseif($guestreg===2){
register_guest(3, $_POST['nick']);
$U['status']=3;
}
}
}
if($U['status']==1){
if(in_array($ga, [2, 3], true)){
send_waiting_room();
}
}
}
function kill_session(): void
{
global $U, $db, $session;
parse_sessions();
check_expired();
check_kicked();
setcookie(COOKIENAME, false);
$session = '';
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'sessions WHERE session=?;');
$stmt->execute([$U['session']]);
if($U['status']>=3 && !$U['incognito']){
add_system_message(sprintf(get_setting('msgexit'), style_this(htmlspecialchars($U['nickname']), $U['style'])), '');
}
}
function kick_chatter(array $names, string $mes, bool $purge) : bool {
global $U, $db;
$lonick='';
if (strlen($mes)<1){
$mes=_("no kick message");
}
$time=60*(get_setting('kickpenalty')-get_setting('guestexpire'))+time();
$check=$db->prepare('SELECT style, entry FROM ' . PREFIX . 'sessions WHERE nickname=? AND status!=0 AND (status OR nickname=?);');
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET lastpost=?, status=0, kickmessage=? WHERE nickname=?;');
$all=false;
if($names[0]==='s *'){
$tmp=$db->query('SELECT nickname FROM ' . PREFIX . 'sessions WHERE status=1;');
$names=[];
while($name=$tmp->fetch(PDO::FETCH_NUM)){
$names[]=$name[0];
}
$all=true;
}
$i=0;
foreach($names as $name){
$check->execute([$name, $U['status'], $U['nickname']]);
if($temp=$check->fetch(PDO::FETCH_ASSOC)){
$stmt->execute([$time, $mes, $name]);
if($purge){
del_all_messages($name, (int) $temp['entry']);
}
$lonick.=style_this(htmlspecialchars($name), $temp['style']).', ';
++$i;
}
}
if($i>0){
if($all){
add_system_message(sprintf(get_setting('msgallkick'), $mes), $U['nickname']);
}else{
$lonick=substr($lonick, 0, -2);
if($i>1){
add_system_message(sprintf(get_setting('msgmultikick'), $lonick, $mes), $U['nickname']);
}else{
add_system_message(sprintf(get_setting('msgkick'), $lonick, $mes), $U['nickname']);
}
}
return true;
}
return false;
}
function logout_chatter(array $names): void
{
global $U, $db;
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'sessions WHERE nickname=? AND status;');
if($names[0]==='s *'){
$tmp=$db->query('SELECT nickname FROM ' . PREFIX . 'sessions WHERE status=1;');
$names=[];
while($name=$tmp->fetch(PDO::FETCH_NUM)){
$names[]=$name[0];
}
}
foreach($names as $name){
$stmt->execute([$name, $U['status']]);
}
}
function check_session(): void
{
global $U;
parse_sessions();
check_expired();
check_kicked();
if($U['entry']==0){
send_waiting_room();
}
}
function check_expired(): void
{
global $U, $session;
if(!isset($U['session'])){
setcookie(COOKIENAME, false);
$session = '';
send_error(_('Invalid/expired session'));
}
}
function get_count_mods() : int {
global $db;
$c=$db->query('SELECT COUNT(*) FROM ' . PREFIX . 'sessions WHERE status>=5')->fetch(PDO::FETCH_NUM);
return (int) $c[0];
}
function check_kicked(): void
{
global $U, $session;
if($U['status']==0){
setcookie(COOKIENAME, false);
$session = '';
send_error(_('You have been kicked!')." $U[kickmessage]");
}
}
function get_nowchatting(): void
{
global $db;
parse_sessions();
$stmt=$db->query('SELECT COUNT(*) FROM ' . PREFIX . 'sessions WHERE entry!=0 AND status>0 AND incognito=0;');
$count=$stmt->fetch(PDO::FETCH_NUM);
echo '
'.sprintf(_('Currently %d chatter(s) in room:'), $count[0]).' ';
if(!get_setting('hidechatters')){
$stmt=$db->query('SELECT nickname, style FROM ' . PREFIX . 'sessions WHERE entry!=0 AND status>0 AND incognito=0 ORDER BY status DESC, lastpost DESC;');
while($user=$stmt->fetch(PDO::FETCH_NUM)){
echo style_this(htmlspecialchars($user[0]), $user[1]).' ';
}
}
echo '
';
}
function parse_sessions(): void
{
global $U, $db, $session;
// look for our session
if(!empty($session)){
$stmt=$db->prepare('SELECT * FROM ' . PREFIX . 'sessions WHERE session=?;');
$stmt->execute([$session]);
if($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){
$U=$tmp;
}
}
set_default_tz();
}
// member handling
function check_member(string $password) : bool {
global $U, $db;
$stmt=$db->prepare('SELECT * FROM ' . PREFIX . 'members WHERE nickname=?;');
$stmt->execute([$U['nickname']]);
if($temp=$stmt->fetch(PDO::FETCH_ASSOC)){
if(get_setting('dismemcaptcha')==0){
check_captcha($_POST['challenge'] ?? '', $_POST['captcha'] ?? '');
}
if($temp['passhash']===md5(sha1(md5($U['nickname'].$password)))){
// old hashing method, update on the fly
$temp['passhash']=password_hash($password, PASSWORD_DEFAULT);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET passhash=? WHERE nickname=?;');
$stmt->execute([$temp['passhash'], $U['nickname']]);
}
if(password_verify($password, $temp['passhash'])){
$U=$temp;
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET lastlogin=? WHERE nickname=?;');
$stmt->execute([time(), $U['nickname']]);
return true;
}else{
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET loginfails=? WHERE nickname=?;');
$stmt->execute([$temp['loginfails']+1, $temp['nickname']]);
send_error(_('This nickname is a registered member.')." "._('Wrong Password!'));
}
}
return false;
}
function delete_account(): void
{
global $U, $db;
if($U['status']<8){
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET status=1, incognito=0 WHERE nickname=?;');
$stmt->execute([$U['nickname']]);
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'members WHERE nickname=?;');
$stmt->execute([$U['nickname']]);
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'inbox WHERE recipient=?;');
$stmt->execute([$U['nickname']]);
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'notes WHERE (type=2 OR type=3) AND editedby=?;');
$stmt->execute([$U['nickname']]);
$U['status']=1;
}
}
function register_guest(int $status, string $nick) : string {
global $U, $db;
$stmt=$db->prepare('SELECT style FROM ' . PREFIX . 'members WHERE nickname=?');
$stmt->execute([$nick]);
if($tmp=$stmt->fetch(PDO::FETCH_NUM)){
return sprintf(_('%s is already registered.'), style_this(htmlspecialchars($nick), $tmp[0]));
}
$stmt=$db->prepare('SELECT * FROM ' . PREFIX . 'sessions WHERE nickname=? AND status=1;');
$stmt->execute([$nick]);
if($reg=$stmt->fetch(PDO::FETCH_ASSOC)){
$reg['status']=$status;
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET status=? WHERE session=?;');
$stmt->execute([$reg['status'], $reg['session']]);
}else{
return sprintf(_("Can't register %s"), htmlspecialchars($nick));
}
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'members (nickname, passhash, status, refresh, bgcolour, regedby, timestamps, embed, style, incognito, nocache, tz, eninbox, sortupdown, hidechatters, nocache_old) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);');
$stmt->execute([$reg['nickname'], $reg['passhash'], $reg['status'], $reg['refresh'], $reg['bgcolour'], $U['nickname'], $reg['timestamps'], $reg['embed'], $reg['style'], $reg['incognito'], $reg['nocache'], $reg['tz'], $reg['eninbox'], $reg['sortupdown'], $reg['hidechatters'], $reg['nocache_old']]);
if($reg['status']==3){
add_system_message(sprintf(get_setting('msgmemreg'), style_this(htmlspecialchars($reg['nickname']), $reg['style'])), $U['nickname']);
}else{
add_system_message(sprintf(get_setting('msgsureg'), style_this(htmlspecialchars($reg['nickname']), $reg['style'])), $U['nickname']);
}
return sprintf(_('%s successfully registered.'), style_this(htmlspecialchars($reg['nickname']), $reg['style']));
}
function register_new(string $nick, string $pass) : string {
global $U, $db;
$nick=preg_replace('/\s/', '', $nick);
if(empty($nick)){
return '';
}
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'sessions WHERE nickname=?');
$stmt->execute([$nick]);
if($stmt->fetch(PDO::FETCH_NUM)){
return sprintf(_("Can't register %s"), htmlspecialchars($nick));
}
if(!valid_nick($nick)){
return sprintf(_('Invalid nickname (%1$d characters maximum and has to match the regular expression "%2$s")'), get_setting('maxname'), get_setting('nickregex'));
}
if(!valid_pass($pass)){
return sprintf(_('Invalid password (At least %1$d characters and has to match the regular expression "%2$s")'), get_setting('minpass'), get_setting('passregex'));
}
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'members WHERE nickname=?');
$stmt->execute([$nick]);
if($stmt->fetch(PDO::FETCH_NUM)){
return sprintf(_('%s is already registered.'), htmlspecialchars($nick));
}
$reg=[
'nickname' =>$nick,
'passhash' =>password_hash($pass, PASSWORD_DEFAULT),
'status' =>3,
'refresh' =>get_setting('defaultrefresh'),
'bgcolour' =>get_setting('colbg'),
'regedby' =>$U['nickname'],
'timestamps' =>get_setting('timestamps'),
'style' =>'color:#'.get_setting('coltxt').';',
'embed' =>1,
'incognito' =>0,
'nocache' =>0,
'nocache_old' =>1,
'tz' =>get_setting('defaulttz'),
'eninbox' =>0,
'sortupdown' =>get_setting('sortupdown'),
'hidechatters' =>get_setting('hidechatters'),
];
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'members (nickname, passhash, status, refresh, bgcolour, regedby, timestamps, style, embed, incognito, nocache, tz, eninbox, sortupdown, hidechatters, nocache_old) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);');
$stmt->execute([$reg['nickname'], $reg['passhash'], $reg['status'], $reg['refresh'], $reg['bgcolour'], $reg['regedby'], $reg['timestamps'], $reg['style'], $reg['embed'], $reg['incognito'], $reg['nocache'], $reg['tz'], $reg['eninbox'], $reg['sortupdown'], $reg['hidechatters'], $reg['nocache_old']]);
return sprintf(_('%s successfully registered.'), htmlspecialchars($reg['nickname']));
}
function change_status(string $nick, string $status) : string {
global $U, $db;
if(empty($nick)){
return '';
}elseif($U['status']<=$status || !preg_match('/^[023567\-]$/', $status)){
return sprintf(_("Can't change status of %s"), htmlspecialchars($nick));
}
$stmt=$db->prepare('SELECT incognito, style FROM ' . PREFIX . 'members WHERE nickname=? AND status;');
$stmt->execute([$nick, $U['status']]);
if(!$old=$stmt->fetch(PDO::FETCH_NUM)){
return sprintf(_("Can't change status of %s"), htmlspecialchars($nick));
}
if($status==='-'){
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'members WHERE nickname=?;');
$stmt->execute([$nick]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET status=1, incognito=0 WHERE nickname=?;');
$stmt->execute([$nick]);
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'inbox WHERE recipient=?;');
$stmt->execute([$nick]);
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'notes WHERE (type=2 OR type=3) AND editedby=?;');
$stmt->execute([$nick]);
return sprintf(_('%s successfully deleted from database.'), style_this(htmlspecialchars($nick), $old[1]));
}else{
if($status<5){
$old[0]=0;
}
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET status=?, incognito=? WHERE nickname=?;');
$stmt->execute([$status, $old[0], $nick]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET status=?, incognito=? WHERE nickname=?;');
$stmt->execute([$status, $old[0], $nick]);
return sprintf(_('Status of %s successfully changed.'), style_this(htmlspecialchars($nick), $old[1]));
}
}
function passreset(string $nick, string $pass) : string {
global $U, $db;
if(empty($nick)){
return '';
}
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'members WHERE nickname=? AND status;');
$stmt->execute([$nick, $U['status']]);
if($stmt->fetch(PDO::FETCH_ASSOC)){
$passhash=password_hash($pass, PASSWORD_DEFAULT);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET passhash=? WHERE nickname=?;');
$stmt->execute([$passhash, $nick]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET passhash=? WHERE nickname=?;');
$stmt->execute([$passhash, $nick]);
return sprintf(_('Successfully reset password for %s'), htmlspecialchars($nick));
}else{
return sprintf(_("Can't reset password for %s"), htmlspecialchars($nick));
}
}
function amend_profile(): void
{
global $U;
if(isset($_POST['refresh'])){
$U['refresh']=$_POST['refresh'];
}
if($U['refresh']<5){
$U['refresh']=5;
}elseif($U['refresh']>150){
$U['refresh']=150;
}
if(preg_match('/^#([a-f0-9]{6})$/i', $_POST['colour'], $match)){
$colour=$match[1];
}else{
preg_match('/#([0-9a-f]{6})/i', $U['style'], $matches);
$colour=$matches[1];
}
if(preg_match('/^#([a-f0-9]{6})$/i', $_POST['bgcolour'], $match)){
$U['bgcolour']=$match[1];
}
$U['style']="color:#$colour;";
if($U['status']>=3){
$F=load_fonts();
if(isset($F[$_POST['font']])){
$U['style'].=$F[$_POST['font']];
}
if(isset($_POST['small'])){
$U['style'].='font-size:smaller;';
}
if(isset($_POST['italic'])){
$U['style'].='font-style:italic;';
}
if(isset($_POST['bold'])){
$U['style'].='font-weight:bold;';
}
}
if($U['status']>=5 && isset($_POST['incognito']) && get_setting('incognito')){
$U['incognito']=1;
}else{
$U['incognito']=0;
}
if(isset($_POST['tz'])){
$tzs=timezone_identifiers_list();
if(in_array($_POST['tz'], $tzs)){
$U['tz']=$_POST['tz'];
}
}
if(isset($_POST['eninbox']) && $_POST['eninbox']>=0 && $_POST['eninbox']<=5){
$U['eninbox']=$_POST['eninbox'];
}
$bool_settings=['timestamps', 'embed', 'nocache', 'sortupdown', 'hidechatters'];
foreach($bool_settings as $setting){
if(isset($_POST[$setting])){
$U[$setting]=1;
}else{
$U[$setting]=0;
}
}
}
function save_profile() : string {
global $U, $db;
amend_profile();
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET refresh=?, style=?, bgcolour=?, timestamps=?, embed=?, incognito=?, nocache=?, tz=?, eninbox=?, sortupdown=?, hidechatters=? WHERE session=?;');
$stmt->execute([$U['refresh'], $U['style'], $U['bgcolour'], $U['timestamps'], $U['embed'], $U['incognito'], $U['nocache'], $U['tz'], $U['eninbox'], $U['sortupdown'], $U['hidechatters'], $U['session']]);
if($U['status']>=2){
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET refresh=?, bgcolour=?, timestamps=?, embed=?, incognito=?, style=?, nocache=?, tz=?, eninbox=?, sortupdown=?, hidechatters=? WHERE nickname=?;');
$stmt->execute([$U['refresh'], $U['bgcolour'], $U['timestamps'], $U['embed'], $U['incognito'], $U['style'], $U['nocache'], $U['tz'], $U['eninbox'], $U['sortupdown'], $U['hidechatters'], $U['nickname']]);
}
if(!empty($_POST['unignore'])){
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'ignored WHERE ign=? AND ignby=?;');
$stmt->execute([$_POST['unignore'], $U['nickname']]);
}
if(!empty($_POST['ignore'])){
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'messages WHERE poster=? AND poster NOT IN (SELECT ign FROM ' . PREFIX . 'ignored WHERE ignby=?);');
$stmt->execute([$_POST['ignore'], $U['nickname']]);
if($U['nickname']!==$_POST['ignore'] && $stmt->fetch(PDO::FETCH_NUM)){
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'ignored (ign, ignby) VALUES (?, ?);');
$stmt->execute([$_POST['ignore'], $U['nickname']]);
}
}
if($U['status']>1 && !empty($_POST['newpass'])){
if(!valid_pass($_POST['newpass'])){
return sprintf(_('Invalid password (At least %1$d characters and has to match the regular expression "%2$s")'), get_setting('minpass'), get_setting('passregex'));
}
if(!isset($_POST['oldpass'])){
$_POST['oldpass']='';
}
if(!isset($_POST['confirmpass'])){
$_POST['confirmpass']='';
}
if($_POST['newpass']!==$_POST['confirmpass']){
return _('Password confirmation does not match!');
}else{
$U['newhash']=password_hash($_POST['newpass'], PASSWORD_DEFAULT);
}
if(!password_verify($_POST['oldpass'], $U['passhash'])){
return _('Wrong Password!');
}
$U['passhash']=$U['newhash'];
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET passhash=? WHERE session=?;');
$stmt->execute([$U['passhash'], $U['session']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET passhash=? WHERE nickname=?;');
$stmt->execute([$U['passhash'], $U['nickname']]);
}
if($U['status']>1 && !empty($_POST['newnickname'])){
$msg=set_new_nickname();
if($msg!==''){
return $msg;
}
}
return _('Your profile has successfully been saved.');
}
function set_new_nickname() : string {
global $U, $db;
$_POST['newnickname']=preg_replace('/\s/', '', $_POST['newnickname']);
if(!valid_nick($_POST['newnickname'])){
return sprintf(_('Invalid nickname (%1$d characters maximum and has to match the regular expression "%2$s")'), get_setting('maxname'), get_setting('nickregex'));
}
$stmt=$db->prepare('SELECT id FROM ' . PREFIX . 'sessions WHERE nickname=? UNION SELECT id FROM ' . PREFIX . 'members WHERE nickname=?;');
$stmt->execute([$_POST['newnickname'], $_POST['newnickname']]);
if($stmt->fetch(PDO::FETCH_NUM)){
return _('Nickname is already taken');
}else{
// Make sure members can not read private messages of previous guests with the same name
$stmt=$db->prepare('UPDATE ' . PREFIX . 'messages SET poster = "" WHERE poster = ? AND poststatus = 9;');
$stmt->execute([$_POST['newnickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'messages SET recipient = "" WHERE recipient = ? AND poststatus = 9;');
$stmt->execute([$_POST['newnickname']]);
// change names in all tables
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET nickname=? WHERE nickname=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET nickname=? WHERE nickname=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'messages SET poster=? WHERE poster=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'messages SET recipient=? WHERE recipient=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'ignored SET ignby=? WHERE ignby=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'ignored SET ign=? WHERE ign=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'inbox SET poster=? WHERE poster=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$stmt=$db->prepare('UPDATE ' . PREFIX . 'notes SET editedby=? WHERE editedby=?;');
$stmt->execute([$_POST['newnickname'], $U['nickname']]);
$U['nickname']=$_POST['newnickname'];
}
return '';
}
//sets default settings for guests
function add_user_defaults(string $password): void
{
global $U;
$U['refresh']=get_setting('defaultrefresh');
$U['bgcolour']=get_setting('colbg');
if(!isset($_POST['colour']) || !preg_match('/^[a-f0-9]{6}$/i', $_POST['colour']) || abs(greyval($_POST['colour'])-greyval(get_setting('colbg')))<75){
do{
$colour=sprintf('%06X', mt_rand(0, 16581375));
}while(abs(greyval($colour)-greyval(get_setting('colbg')))<75);
}else{
$colour=$_POST['colour'];
}
$U['style']="color:#$colour;";
$U['timestamps']=get_setting('timestamps');
$U['embed']=1;
$U['incognito']=0;
$U['status']=1;
$U['nocache']=get_setting('sortupdown');
if($U['nocache']){
$U['nocache_old']=0;
}else{
$U['nocache_old']=1;
}
$U['loginfails']=0;
$U['tz']=get_setting('defaulttz');
$U['eninbox']=0;
$U['sortupdown']=get_setting('sortupdown');
$U['hidechatters']=get_setting('hidechatters');
$U['passhash']=password_hash($password, PASSWORD_DEFAULT);
$U['entry']=$U['lastpost']=time();
$U['exiting']=0;
}
// message handling
function validate_input() : string {
global $U, $db;
$inbox=false;
$maxmessage=get_setting('maxmessage');
$message=mb_substr($_POST['message'], 0, $maxmessage);
$rejected=mb_substr($_POST['message'], $maxmessage);
if(!isset($_POST['postid'])){ // auto-kick spammers not setting a postid
kick_chatter([$U['nickname']], '', false);
}
if($U['postid'] !== $_POST['postid'] || (time() - $U['lastpost']) <= 1){ // reject bogus messages
$rejected=$_POST['message'];
$message='';
}
if(!empty($rejected)){
$rejected=trim($rejected);
$rejected=htmlspecialchars($rejected);
}
$message=htmlspecialchars($message);
$message=preg_replace("/(\r?\n|\r\n?)/u", ' ', $message);
if(isset($_POST['multi'])){
$message=preg_replace('/\s* /u', ' ', $message);
$message=preg_replace('/ ( )+/u', '
', $message);
$message=preg_replace('/
\s*$/u', ' ', $message);
$message=preg_replace('/^ \s*$/u', '', $message);
}else{
$message=str_replace(' ', ' ', $message);
}
$message=trim($message);
$message=preg_replace('/\s+/u', ' ', $message);
$recipient='';
if($_POST['sendto']==='s *'){
$poststatus=1;
$displaysend=sprintf(get_setting('msgsendall'), style_this(htmlspecialchars($U['nickname']), $U['style']));
}elseif($_POST['sendto']==='s ?' && $U['status']>=3){
$poststatus=3;
$displaysend=sprintf(get_setting('msgsendmem'), style_this(htmlspecialchars($U['nickname']), $U['style']));
}elseif($_POST['sendto']==='s %' && $U['status']>=5){
$poststatus=5;
$displaysend=sprintf(get_setting('msgsendmod'), style_this(htmlspecialchars($U['nickname']), $U['style']));
}elseif($_POST['sendto']==='s _' && $U['status']>=6){
$poststatus=6;
$displaysend=sprintf(get_setting('msgsendadm'), style_this(htmlspecialchars($U['nickname']), $U['style']));
}elseif($_POST['sendto'] === $U['nickname']){ // message to yourself?
return '';
}else{ // known nick in room?
if(get_setting('disablepm')){
//PMs disabled
return '';
}
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'ignored WHERE (ignby=? AND ign=?) OR (ign=? AND ignby=?);');
$stmt->execute([$_POST['sendto'], $U['nickname'], $_POST['sendto'], $U['nickname']]);
if($stmt->fetch(PDO::FETCH_NUM)){
//ignored
return '';
}
$stmt=$db->prepare('SELECT s.style, 0 AS inbox FROM ' . PREFIX . 'sessions AS s LEFT JOIN ' . PREFIX . 'members AS m ON (m.nickname=s.nickname) WHERE s.nickname=? AND (s.incognito=0 OR (m.eninbox!=0 AND m.eninbox<=?));');
$stmt->execute([$_POST['sendto'], $U['status']]);
if(!$tmp=$stmt->fetch(PDO::FETCH_ASSOC)){
$stmt=$db->prepare('SELECT style, 1 AS inbox FROM ' . PREFIX . 'members WHERE nickname=? AND eninbox!=0 AND eninbox<=?;');
$stmt->execute([$_POST['sendto'], $U['status']]);
if(!$tmp=$stmt->fetch(PDO::FETCH_ASSOC)){
//nickname left or disabled offline inbox for us
return '';
}
}
$recipient=$_POST['sendto'];
$poststatus=9;
$displaysend=sprintf(get_setting('msgsendprv'), style_this(htmlspecialchars($U['nickname']), $U['style']), style_this(htmlspecialchars($recipient), $tmp['style']));
$inbox=$tmp['inbox'];
}
if($poststatus!==9 && preg_match('~^/me~iu', $message)){
$displaysend=style_this(htmlspecialchars("$U[nickname] "), $U['style']);
$message=preg_replace("~^/me\s?~iu", '', $message);
}
$message=apply_filter($message, $poststatus, $U['nickname']);
$message=create_hotlinks($message);
$message=apply_linkfilter($message);
if(isset($_FILES['file']) && get_setting('enfileupload')>0 && get_setting('enfileupload')<=$U['status']){
if($_FILES['file']['error']===UPLOAD_ERR_OK && $_FILES['file']['size']<=(1024*get_setting('maxuploadsize'))){
$hash=sha1_file($_FILES['file']['tmp_name']);
$name=htmlspecialchars($_FILES['file']['name']);
$message=sprintf(get_setting('msgattache'), "$name", $message);
}
}
if(add_message($message, $recipient, $U['nickname'], (int) $U['status'], $poststatus, $displaysend, $U['style'])){
$U['lastpost']=time();
try {
$U[ 'postid' ] = bin2hex( random_bytes( 3 ) );
} catch(Exception $e) {
$U['postid'] = substr(time(), -6);
}
$stmt=$db->prepare('UPDATE ' . PREFIX . 'sessions SET lastpost=?, postid=? WHERE session=?;');
$stmt->execute([$U['lastpost'], $U['postid'], $U['session']]);
$stmt=$db->prepare('SELECT id FROM ' . PREFIX . 'messages WHERE poster=? ORDER BY id DESC LIMIT 1;');
$stmt->execute([$U['nickname']]);
$id=$stmt->fetch(PDO::FETCH_NUM);
if($inbox && $id){
$newmessage=[
'postdate' =>time(),
'poster' =>$U['nickname'],
'recipient' =>$recipient,
'text' =>"$displaysend".style_this($message, $U['style']).''
];
if(MSGENCRYPTED){
try {
$newmessage[ 'text' ] = base64_encode( sodium_crypto_aead_aes256gcm_encrypt( $newmessage[ 'text' ], '', AES_IV, ENCRYPTKEY ) );
} catch (SodiumException $e){
send_error($e->getMessage());
}
}
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'inbox (postdate, postid, poster, recipient, text) VALUES(?, ?, ?, ?, ?)');
$stmt->execute([$newmessage['postdate'], $id[0], $newmessage['poster'], $newmessage['recipient'], $newmessage['text']]);
}
if(isset($hash) && $id){
if(function_exists('mime_content_type')){
$type = mime_content_type($_FILES['file']['tmp_name']);
}elseif(!empty($_FILES['file']['type']) && preg_match('~^[a-z0-9/\-.+]*$~i', $_FILES['file']['type'])){
$type = $_FILES['file']['type'];
}else{
$type = 'application/octet-stream';
}
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'files (postid, hash, filename, type, data) VALUES (?, ?, ?, ?, ?);');
$stmt->execute([$id[0], $hash, str_replace('"', '\"', $_FILES['file']['name']), $type, base64_encode(file_get_contents($_FILES['file']['tmp_name']))]);
unlink($_FILES['file']['tmp_name']);
}
}
return $rejected;
}
function apply_filter(string $message, int $poststatus, string $nickname) : string {
global $U, $session;
$message=str_replace(' ', "\n", $message);
$message=apply_mention($message);
$filters=get_filters();
foreach($filters as $filter){
if($poststatus!==9 || !$filter['allowinpm']){
if($filter['cs']){
$message=preg_replace("/$filter[match]/u", $filter['replace'], $message, -1, $count);
}else{
$message=preg_replace("/$filter[match]/iu", $filter['replace'], $message, -1, $count);
}
}
if(isset($count) && $count>0 && $filter['kick'] && ($U['status']<5 || get_setting('filtermodkick'))){
kick_chatter([$nickname], $filter['replace'], false);
setcookie(COOKIENAME, false);
$session = '';
send_error(_('You have been kicked!')." $filter[replace]");
}
}
$message=str_replace("\n", ' ', $message);
return $message;
}
function apply_linkfilter(string $message) : string {
$filters=get_linkfilters();
foreach($filters as $filter){
$message=preg_replace_callback("/([^<]*)<\/a>/iu",
function ($matched) use(&$filter){
return "".preg_replace("/$filter[match]/iu", $filter['replace'], $matched[2]).'';
}
, $message);
}
$redirect=get_setting('redirect');
if(get_setting('imgembed')){
$message=preg_replace_callback('/\[img]\s?([^<]*)<\/a>/iu',
function ($matched){
return str_ireplace('[/img]', '', " ");
}
, $message);
}
if(empty($redirect)){
$redirect="$_SERVER[SCRIPT_NAME]?action=redirect&url=";
}
if(get_setting('forceredirect')){
$message=preg_replace_callback('/([^<]*)<\/a>/u',
function ($matched) use($redirect){
return "$matched[2]";
}
, $message);
}elseif(preg_match_all('/([^<]*)<\/a>/u', $message, $matches)){
foreach($matches[1] as $match){
if(!preg_match('~^http(s)?://~u', $match)){
$message=preg_replace_callback('/([^<]*)<\/a>/u',
function ($matched) use($redirect){
return "$matched[2]";
}
, $message);
}
}
}
return $message;
}
function create_hotlinks(string $message) : string {
//Make hotlinks for URLs, redirect through dereferrer script to prevent session leakage
// 1. all explicit schemes with whatever xxx://yyyyyyy
$message=preg_replace('~(^|[^\w"])(\w+://[^\s<>]+)~iu', "$1<<$2>>", $message);
// 2. valid URLs without scheme:
$message=preg_replace('~((?:[^\s<>]*:[^\s<>]*@)?[a-z0-9\-]+(?:\.[a-z0-9\-]+)+(?::\d*)?/[^\s<>]*)(?![^<>]*>)~iu', "<<$1>>", $message); // server/path given
$message=preg_replace('~((?:[^\s<>]*:[^\s<>]*@)?[a-z0-9\-]+(?:\.[a-z0-9\-]+)+:\d+)(?![^<>]*>)~iu', "<<$1>>", $message); // server:port given
$message=preg_replace('~([^\s<>]*:[^\s<>]*@[a-z0-9\-]+(?:\.[a-z0-9\-]+)+(?::\d+)?)(?![^<>]*>)~iu', "<<$1>>", $message); // au:th@server given
// 3. likely servers without any hints but not filenames like *.rar zip exe etc.
$message=preg_replace('~((?:[a-z0-9\-]+\.)*(?:[a-z2-7]{55}d|[a-z2-7]{16})\.onion)(?![^<>]*>)~iu', "<<$1>>", $message);// *.onion
$message=preg_replace('~([a-z0-9\-]+(?:\.[a-z0-9\-]+)+(?:\.(?!rar|zip|exe|gz|7z|bat|doc)[a-z]{2,}))(?=[^a-z0-9\-.]|$)(?![^<>]*>)~iu', "<<$1>>", $message);// xxx.yyy.zzz
// Convert every <<....>> into proper links:
$message=preg_replace_callback('/<<([^<>]+)>>/u',
function ($matches){
if(strpos($matches[1], '://')===false){
return "$matches[1]";
}else{
return "$matches[1]";
}
}
, $message);
return $message;
}
function apply_mention(string $message) : string {
return preg_replace_callback('/@([^\s]+)/iu', function ($matched){
global $db;
$nick=htmlspecialchars_decode($matched[1]);
$rest='';
for($i=0;$i<=3;++$i){
//match case-sensitive present nicknames
$stmt=$db->prepare('SELECT style FROM ' . PREFIX . 'sessions WHERE nickname=?;');
$stmt->execute([$nick]);
if($tmp=$stmt->fetch(PDO::FETCH_NUM)){
return style_this(htmlspecialchars("@$nick"), $tmp[0]).$rest;
}
//match case-insensitive present nicknames
$stmt=$db->prepare('SELECT style FROM ' . PREFIX . 'sessions WHERE LOWER(nickname)=LOWER(?);');
$stmt->execute([$nick]);
if($tmp=$stmt->fetch(PDO::FETCH_NUM)){
return style_this(htmlspecialchars("@$nick"), $tmp[0]).$rest;
}
//match case-sensitive members
$stmt=$db->prepare('SELECT style FROM ' . PREFIX . 'members WHERE nickname=?;');
$stmt->execute([$nick]);
if($tmp=$stmt->fetch(PDO::FETCH_NUM)){
return style_this(htmlspecialchars("@$nick"), $tmp[0]).$rest;
}
//match case-insensitive members
$stmt=$db->prepare('SELECT style FROM ' . PREFIX . 'members WHERE LOWER(nickname)=LOWER(?);');
$stmt->execute([$nick]);
if($tmp=$stmt->fetch(PDO::FETCH_NUM)){
return style_this(htmlspecialchars("@$nick"), $tmp[0]).$rest;
}
if(strlen($nick)===1){
break;
}
$rest=mb_substr($nick, -1).$rest;
$nick=mb_substr($nick, 0, -1);
}
return $matched[0];
}, $message);
}
function add_message(string $message, string $recipient, string $poster, int $delstatus, int $poststatus, string $displaysend, string$style) : bool {
global $db;
if($message===''){
return false;
}
$newmessage=[
'postdate' =>time(),
'poststatus' =>$poststatus,
'poster' =>$poster,
'recipient' =>$recipient,
'text' =>"$displaysend".style_this($message, $style).'',
'delstatus' =>$delstatus
];
//prevent posting the same message twice, if no other message was posted in-between.
$stmt=$db->prepare('SELECT id FROM ' . PREFIX . 'messages WHERE poststatus=? AND poster=? AND recipient=? AND text=? AND id IN (SELECT * FROM (SELECT id FROM ' . PREFIX . 'messages ORDER BY id DESC LIMIT 1) AS t);');
$stmt->execute([$newmessage['poststatus'], $newmessage['poster'], $newmessage['recipient'], $newmessage['text']]);
if($stmt->fetch(PDO::FETCH_NUM)){
return false;
}
write_message($newmessage);
return true;
}
function add_system_message(string $mes, string $doer): void
{
if($mes===''){
return;
}
if($doer==='' || !get_setting('namedoers')){
$sysmessage=[
'postdate' =>time(),
'poststatus' =>4,
'poster' =>'',
'recipient' =>'',
'text' =>"$mes",
'delstatus' =>4
];
} else {
$sysmessage=[
'postdate' =>time(),
'poststatus' =>4,
'poster' =>'',
'recipient' =>'',
'text' =>"$mes ($doer)",
'delstatus' =>4
];
}
write_message($sysmessage);
}
function write_message(array $message): void
{
global $db;
if(MSGENCRYPTED){
try {
$message['text']=base64_encode(sodium_crypto_aead_aes256gcm_encrypt($message['text'], '', AES_IV, ENCRYPTKEY));
} catch (SodiumException $e){
send_error($e->getMessage());
}
}
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'messages (postdate, poststatus, poster, recipient, text, delstatus) VALUES (?, ?, ?, ?, ?, ?);');
$stmt->execute([$message['postdate'], $message['poststatus'], $message['poster'], $message['recipient'], $message['text'], $message['delstatus']]);
if($message['poststatus']<9 && get_setting('sendmail')){
$subject='New Chat message';
$headers='From: '.get_setting('mailsender')."\r\nX-Mailer: PHP/".phpversion()."\r\nContent-Type: text/html; charset=UTF-8\r\n";
$body='$message[text]";
mail(get_setting('mailreceiver'), $subject, $body, $headers);
}
}
function clean_room(): void
{
global $U, $db;
$db->query('DELETE FROM ' . PREFIX . 'messages;');
add_system_message(sprintf(get_setting('msgclean'), get_setting('chatname')), $U['nickname']);
}
function clean_selected(int $status, string $nick): void
{
global $db;
if(isset($_POST['mid'])){
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'messages WHERE id=? AND (poster=? OR recipient=? OR (poststatus AND delstatus));');
foreach($_POST['mid'] as $mid){
$stmt->execute([$mid, $nick, $nick, $status, $status]);
}
}
}
function clean_inbox_selected(): void
{
global $U, $db;
if(isset($_POST['mid'])){
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'inbox WHERE id=? AND recipient=?;');
foreach($_POST['mid'] as $mid){
$stmt->execute([$mid, $U['nickname']]);
}
}
}
function del_all_messages(string $nick, int $entry): void
{
global $db, $U;
$globally = (bool) get_setting('postbox_delete_globally');
if($globally && $U['status'] > 4){
$stmt = $db->prepare( 'DELETE FROM ' . PREFIX . 'messages;' );
$stmt->execute();
} else {
if ( $nick === '' ) {
$nick = $U[ 'nickname' ];
}
$stmt = $db->prepare( 'DELETE FROM ' . PREFIX . 'messages WHERE poster=? AND postdate>=?;' );
$stmt->execute( [ $nick, $entry ] );
$stmt = $db->prepare( 'DELETE FROM ' . PREFIX . 'inbox WHERE poster=? AND postdate>=?;' );
$stmt->execute( [ $nick, $entry ] );
}
}
function del_last_message(): void
{
global $U, $db;
if($U['status']>1){
$entry=0;
}else{
$entry=$U['entry'];
}
$globally = (bool) get_setting('postbox_delete_globally');
if($globally && $U['status'] > 4) {
$stmt = $db->prepare( 'SELECT id FROM ' . PREFIX . 'messages WHERE postdate>=? ORDER BY id DESC LIMIT 1;' );
$stmt->execute( [ $entry ] );
} else {
$stmt = $db->prepare( 'SELECT id FROM ' . PREFIX . 'messages WHERE poster=? AND postdate>=? ORDER BY id DESC LIMIT 1;' );
$stmt->execute( [ $U[ 'nickname' ], $entry ] );
}
if ( $id = $stmt->fetch( PDO::FETCH_NUM ) ) {
$stmt = $db->prepare( 'DELETE FROM ' . PREFIX . 'messages WHERE id=?;' );
$stmt->execute( $id );
$stmt = $db->prepare( 'DELETE FROM ' . PREFIX . 'inbox WHERE postid=?;' );
$stmt->execute( $id );
}
}
function print_messages(int $delstatus=0): void
{
global $U, $db;
$dateformat=get_setting('dateformat');
if(!$U['embed'] && get_setting('imgembed')){
$removeEmbed=true;
}else{
$removeEmbed=false;
}
if($U['timestamps'] && !empty($dateformat)){
$timestamps=true;
}else{
$timestamps=false;
}
if($U['sortupdown']){
$direction='ASC';
}else{
$direction='DESC';
}
if($U['status']>1){
$entry=0;
}else{
$entry=$U['entry'];
}
echo '
';
if($delstatus>0){
$stmt=$db->prepare('SELECT postdate, id, text FROM ' . PREFIX . 'messages WHERE '.
"(poststatus AND delstatus) OR ((poster=? OR recipient=?) AND postdate>=?) ORDER BY id $direction;");
$stmt->execute([$U['status'], $delstatus, $U['nickname'], $U['nickname'], $entry]);
while($message=$stmt->fetch(PDO::FETCH_ASSOC)){
prepare_message_print($message, $removeEmbed);
echo "";
}
}else{
$stmt=$db->prepare('SELECT id, postdate, poststatus, text FROM ' . PREFIX . 'messages WHERE (poststatus<=? OR poststatus=4 OR '.
'(poststatus=9 AND ( (poster=? AND recipient NOT IN (SELECT ign FROM ' . PREFIX . 'ignored WHERE ignby=?) ) OR recipient=?) AND postdate>=?)'.
') AND poster NOT IN (SELECT ign FROM ' . PREFIX . "ignored WHERE ignby=?) ORDER BY id $direction;");
$stmt->execute([$U['status'], $U['nickname'], $U['nickname'], $U['nickname'], $entry, $U['nickname']]);
while($message=$stmt->fetch(PDO::FETCH_ASSOC)){
prepare_message_print($message, $removeEmbed);
echo '