Compare commits
12
Commits
63f421023a
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51c939d743 | ||
|
|
742a0a1c28 | ||
|
|
e05ce9a5de | ||
|
|
c9f8a4abd3 | ||
|
|
09a19d100e | ||
|
|
9541edba24 | ||
|
|
f13c508c64 | ||
|
|
8ff673a96f | ||
|
|
950e7fa872 | ||
|
|
cbec21aa84 | ||
|
|
ad56acc547 | ||
|
|
2f43c1f21d |
@@ -1 +1,2 @@
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
@@ -15,8 +15,8 @@ Features:
|
||||
* Optimized for Tor
|
||||
* No JavaScript needed
|
||||
* Cookies supported, but not needed
|
||||
* Captcha
|
||||
* Multiple languages
|
||||
* CAPTCHA
|
||||
* Multiple languages (20+ languages)
|
||||
* Members and guests
|
||||
* Waiting room for guests
|
||||
* Moderatoral approval of new guests
|
||||
@@ -33,20 +33,21 @@ Features:
|
||||
* Clean the whole room
|
||||
* Plain text message filter
|
||||
* Regex message filter
|
||||
* 2FA via PGP
|
||||
* And more
|
||||
|
||||
Installation Instructions:
|
||||
--------------------------
|
||||
|
||||
You'll need to have php with gettext, pdo, pcre, mbstring and date extension, and a web-server installed.
|
||||
You will also need the pdo_sqlite, pdo_mysql or pdo_pgsql extension, depending on which database you choose.
|
||||
You'll need to have php with `gettext`, `pdo`, `pcre`, `mbstring` and `date` extension, and a web-server installed.
|
||||
You will also need the `pdo_sqlite`, `pdo_mysql` or `pdo_pgsql` extension, depending on which database you choose.
|
||||
Optionally, you can install:
|
||||
- the gd extension for the captcha feature
|
||||
- the json extension for save/restore
|
||||
- the intl extension for browser language detection
|
||||
- the `gd` extension for the captcha feature
|
||||
- the `json` extension for save/restore
|
||||
- the `intl` extension for browser language detection
|
||||
- a memcached server and the memcached extension and change the configuration to use memcached. This will lessen the database load a bit.
|
||||
- a MySQL or PostgreSQL server to use as an external database instead of SQLite
|
||||
- the libsodium extension (PHP >= 7.2) for encryption of messages and notes in the database
|
||||
- the `libsodium` extension (PHP >= 7.2) for encryption of messages and notes in the database
|
||||
When you have everything installed and use MySQL or PostgreSQL, you'll have to create a database and a user for the chat.
|
||||
Then edit the configuration at the bottom of the script to reflect the appropriate database settings and to modify the chat settings the way you like them.
|
||||
Then copy the script to your web-server directory and call the script in your browser with a parameter like this:
|
||||
@@ -68,7 +69,7 @@ Regex:
|
||||
|
||||
Yes, the chat supports regular expression filtering of messages. As regex tends to be difficult for most people, I decided to give it an extra section here.
|
||||
Regex is very powerful and can be used to filter messages that contain certain expressions and replace them with something else.
|
||||
It can be used e.g. to turn BB Code into html, so it is possible to use BB Code in the chat to format messages.
|
||||
It can be used e.g. to turn BBCode into HTML, so it is possible to use BBCode in the chat to format messages.
|
||||
To do this, use this Regex-Match `\[(u|b)\](.*?)\[\/\1\]` and this Regex-Replace `<$1>$2</$1>` and your text will be `[b]bold[/b]` or `[u]underlined[/u]`.
|
||||
You can also use smilies by using this Regex-Match `(?-i::(cry|eek|lol|sad|smile|surprised|wink):)` and this Regex-Replace `<img src="/pictures/$1.gif" alt=":$1:">`
|
||||
And now if you enter `:smile:` an image with the smiley will be loaded from your server at `/pictures/smile.gif`.
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Security Policy
|
||||
|
||||
I take sucurity very seriously and anyone is welcome to report vulnerabiliteis found. Please get in touch with me by email on [daniel@danwin1210.de](mailto:daniel@danwin1210.de) if you have found a vulnerability. For non-critical issues and suggestions, feel free to open a new Issue.
|
||||
I take security very seriously and anyone is welcome to report vulnerabilities found. Please get in touch with me by email on [daniel@danwin1210.de](mailto:daniel@danwin1210.de) if you have found a vulnerability. For non-critical issues and suggestions, feel free to open a new Issue.
|
||||
|
||||
@@ -99,6 +99,10 @@ function route(): void
|
||||
}elseif($_REQUEST['action']==='view'){
|
||||
check_session();
|
||||
send_messages();
|
||||
}elseif($_REQUEST['action']==='like'){
|
||||
check_session();
|
||||
toggle_like();
|
||||
send_messages();
|
||||
}elseif($_REQUEST['action']==='redirect' && !empty($_GET['url'])){
|
||||
send_redirect($_GET['url']);
|
||||
}elseif($_REQUEST['action']==='wait'){
|
||||
@@ -122,6 +126,10 @@ function route(): void
|
||||
check_login();
|
||||
show_fails();
|
||||
send_frameset();
|
||||
}elseif($_REQUEST['action']==='pgp2fa'){
|
||||
check_pgp2fa();
|
||||
show_fails();
|
||||
send_frameset();
|
||||
}elseif($_REQUEST['action']==='controls'){
|
||||
check_session();
|
||||
send_controls();
|
||||
@@ -469,6 +477,7 @@ function prepare_stylesheets(string $class): void
|
||||
$styles['messages'] .= '.msg{max-height:180px;overflow-y:auto} #bottom_link{position:fixed;top:0.5em;right:0.5em} #top_link{position:fixed;bottom:0.5em;right:0.5em} ';
|
||||
$styles['messages'] .= '#chatters th,#chatters td{vertical-align:top} a img{width:15%} a:hover img{width:35%}';
|
||||
$styles['messages'] .= '#messages{word-wrap:break-word}';
|
||||
$styles['messages'] .= '.msgactions form{display:inline}.msgactions input{font-size:smaller}.replyref{font-size:smaller}';
|
||||
}
|
||||
$css=get_setting('css');
|
||||
$coltxt=get_setting('coltxt');
|
||||
@@ -2074,19 +2083,37 @@ function send_notes(int $type): void
|
||||
echo '<h2>'._('Public notes').'</h2><p>';
|
||||
$hiddendo=hidden('do', 'public');
|
||||
}
|
||||
if(isset($_POST['text'])){
|
||||
if(MSGENCRYPTED){
|
||||
try {
|
||||
$_POST['text']=base64_encode(sodium_crypto_aead_aes256gcm_encrypt($_POST['text'], '', AES_IV, ENCRYPTKEY));
|
||||
} catch (SodiumException $e){
|
||||
send_error($e->getMessage());
|
||||
$notice='';
|
||||
if(isset($_POST['save_notes']) && isset($_POST['text'])){
|
||||
$save=true;
|
||||
if($type===2){
|
||||
$password=$_POST['note_password'] ?? '';
|
||||
$confirm=$_POST['note_password_confirm'] ?? '';
|
||||
if($password===''){
|
||||
$notice='<b>'._('Please enter a note password.').'</b> ';
|
||||
$save=false;
|
||||
}elseif($confirm!=='' && $password!==$confirm){
|
||||
$notice='<b>'._('Password confirmation does not match!').'</b> ';
|
||||
$save=false;
|
||||
}else{
|
||||
$_POST['text']=encrypt_personal_note($_POST['text'], $password);
|
||||
}
|
||||
}
|
||||
$time=time();
|
||||
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'notes (type, lastedited, editedby, text) VALUES (?, ?, ?, ?);');
|
||||
$stmt->execute([$type, $time, $U['nickname'], $_POST['text']]);
|
||||
echo '<b>'._('Notes saved!').'</b> ';
|
||||
if($save){
|
||||
if(MSGENCRYPTED){
|
||||
try {
|
||||
$_POST['text']=base64_encode(sodium_crypto_aead_aes256gcm_encrypt($_POST['text'], '', AES_IV, ENCRYPTKEY));
|
||||
} catch (SodiumException $e){
|
||||
send_error($e->getMessage());
|
||||
}
|
||||
}
|
||||
$time=time();
|
||||
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'notes (type, lastedited, editedby, text) VALUES (?, ?, ?, ?);');
|
||||
$stmt->execute([$type, $time, $U['nickname'], $_POST['text']]);
|
||||
$notice='<b>'._('Notes saved!').'</b> ';
|
||||
}
|
||||
}
|
||||
echo $notice;
|
||||
$dateformat=get_setting('dateformat');
|
||||
if(($type!==2) && ($type !==3)){
|
||||
$stmt=$db->prepare('SELECT COUNT(*) FROM ' . PREFIX . 'notes WHERE type=?;');
|
||||
@@ -2120,9 +2147,30 @@ function send_notes(int $type): void
|
||||
send_error($e->getMessage());
|
||||
}
|
||||
}
|
||||
if($type===2){
|
||||
if(is_personal_note_encrypted($note['text'])){
|
||||
if(isset($_POST['note_password']) && $_POST['note_password']!==''){
|
||||
$decrypted=decrypt_personal_note($note['text'], $_POST['note_password']);
|
||||
if($decrypted===false){
|
||||
echo '<b>'._('Wrong note password.').'</b> ';
|
||||
$note['text']='';
|
||||
}else{
|
||||
$note['text']=$decrypted;
|
||||
}
|
||||
}else{
|
||||
echo '<b>'._('Enter your note password to view or edit encrypted personal notes.').'</b> ';
|
||||
$note['text']='';
|
||||
}
|
||||
}
|
||||
}
|
||||
echo "</p>".form('notes');
|
||||
echo "$hiddendo<textarea name=\"text\">".htmlspecialchars($note['text']).'</textarea><br>';
|
||||
echo submit(_('Save notes')).'</form><br>';
|
||||
if($type===2){
|
||||
echo '<label>'._('Note password').': <input type="password" name="note_password"></label> ';
|
||||
echo '<label>'._('Confirm password').': <input type="password" name="note_password_confirm"></label><br>';
|
||||
echo submit(_('View notes'), 'name="view_notes"').' ';
|
||||
}
|
||||
echo submit(_('Save notes'), 'name="save_notes"').'</form><br>';
|
||||
if($num[0]>1){
|
||||
echo '<br><table><tr><td>'._('Revisions:').'</td>';
|
||||
if($revision<$num[0]-1){
|
||||
@@ -2138,6 +2186,57 @@ function send_notes(int $type): void
|
||||
print_end();
|
||||
}
|
||||
|
||||
function encrypt_personal_note(string $text, string $password): string
|
||||
{
|
||||
if(!extension_loaded('sodium')){
|
||||
send_fatal_error(sprintf(_('The %s extension of PHP is required for encrypted personal notes. Please install it first.'), 'sodium'));
|
||||
}
|
||||
try {
|
||||
$salt=random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
|
||||
$nonce=random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
$key=sodium_crypto_pwhash(SODIUM_CRYPTO_SECRETBOX_KEYBYTES, $password, $salt, SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE);
|
||||
$cipher=sodium_crypto_secretbox($text, $nonce, $key);
|
||||
sodium_memzero($key);
|
||||
return 'LCNOTE1:'.base64_encode(json_encode([
|
||||
'salt'=>base64_encode($salt),
|
||||
'nonce'=>base64_encode($nonce),
|
||||
'cipher'=>base64_encode($cipher),
|
||||
]));
|
||||
} catch (SodiumException $e){
|
||||
send_error($e->getMessage());
|
||||
} catch (Exception $e){
|
||||
send_error($e->getMessage());
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function decrypt_personal_note(string $payload, string $password)
|
||||
{
|
||||
if(!extension_loaded('sodium')){
|
||||
send_fatal_error(sprintf(_('The %s extension of PHP is required for encrypted personal notes. Please install it first.'), 'sodium'));
|
||||
}
|
||||
if(!is_personal_note_encrypted($payload)){
|
||||
return $payload;
|
||||
}
|
||||
$data=json_decode(base64_decode(substr($payload, 8)), true);
|
||||
if(!is_array($data) || !isset($data['salt'], $data['nonce'], $data['cipher'])){
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$key=sodium_crypto_pwhash(SODIUM_CRYPTO_SECRETBOX_KEYBYTES, $password, base64_decode($data['salt']), SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE);
|
||||
$plain=sodium_crypto_secretbox_open(base64_decode($data['cipher']), base64_decode($data['nonce']), $key);
|
||||
sodium_memzero($key);
|
||||
return $plain;
|
||||
} catch (SodiumException $e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function is_personal_note_encrypted(string $text): bool
|
||||
{
|
||||
return strpos($text, 'LCNOTE1:')===0;
|
||||
}
|
||||
|
||||
function send_approve_waiting(): void
|
||||
{
|
||||
global $db;
|
||||
@@ -2248,8 +2347,16 @@ function send_post(string $rejected=''): void
|
||||
if(!isset($_REQUEST['sendto'])){
|
||||
$_REQUEST['sendto']='';
|
||||
}
|
||||
$reply_to=get_valid_reply_id($_REQUEST['reply_to'] ?? 0);
|
||||
if($reply_to>0 && $_REQUEST['sendto']===''){
|
||||
$_REQUEST['sendto']=get_reply_sendto($reply_to);
|
||||
}
|
||||
echo '<table><tr><td>'.form('post');
|
||||
echo hidden('postid', $U['postid']);
|
||||
if($reply_to>0){
|
||||
echo hidden('reply_to', (string) $reply_to);
|
||||
echo '<div class="replyref">'.sprintf(_('Replying to message #%d'), $reply_to).'</div>';
|
||||
}
|
||||
if(isset($_POST['multi'])){
|
||||
echo hidden('multi', 'on');
|
||||
}
|
||||
@@ -2419,6 +2526,14 @@ function send_profile(string $arg=''): void
|
||||
while($tmp=$stmt->fetch(PDO::FETCH_ASSOC)){
|
||||
$ignored[]=htmlspecialchars($tmp['ign']);
|
||||
}
|
||||
$pgpkey='';
|
||||
if($U['status']>=2){
|
||||
$stmt=$db->prepare('SELECT pgpkey FROM ' . PREFIX . 'members WHERE nickname=?;');
|
||||
$stmt->execute([$U['nickname']]);
|
||||
if($tmp=$stmt->fetch(PDO::FETCH_NUM)){
|
||||
$pgpkey=$tmp[0] ?? '';
|
||||
}
|
||||
}
|
||||
if(count($ignored)>0){
|
||||
echo '<tr><td><table id="unignore"><tr><th>'._("Don't ignore anymore").'</th><td>';
|
||||
echo '<select name="unignore" size="1"><option value="">'._('(choose)').'</option>';
|
||||
@@ -2544,6 +2659,11 @@ function send_profile(string $arg=''): void
|
||||
echo '<tr><td> </td><td>'._('Confirm new password:').'</td><td><input type="password" name="confirmpass" size="20" autocomplete="new-password"></td></tr>';
|
||||
echo '</table></td></tr></table></td></tr>';
|
||||
thr();
|
||||
echo '<tr><td><table id="pgp2fa"><tr><th>'._('PGP two-factor authentication').'</th></tr>';
|
||||
echo '<tr><td><textarea name="pgpkey" rows="8" cols="72" placeholder="'._('Paste your PGP public key here to require PGP 2FA at login.').'">'.htmlspecialchars($pgpkey).'</textarea></td></tr>';
|
||||
echo '<tr><td><label><input type="checkbox" name="clearpgpkey" value="on"> '._('Remove PGP two-factor authentication').'</label></td></tr>';
|
||||
echo '</table></td></tr>';
|
||||
thr();
|
||||
echo '<tr><td><table id="changenick"><tr><th>'._('Change Nickname').'</th><td><table>';
|
||||
echo '<tr><td> </td><td>'._('New nickname:').'</td><td><input type="text" name="newnickname" size="20" autocomplete="username">';
|
||||
echo '</table></td></tr></table></td></tr>';
|
||||
@@ -2668,6 +2788,22 @@ function send_colours(): void
|
||||
print_end();
|
||||
}
|
||||
|
||||
function send_pgp2fa(string $nickname, string $encrypted_challenge): void
|
||||
{
|
||||
print_start('pgp2fa');
|
||||
echo '<h1 id="chatname">'.get_setting('chatname').'</h1>';
|
||||
echo '<h2>'._('PGP two-factor authentication').'</h2>';
|
||||
echo '<p>'._('Decrypt this message with your PGP private key, then enter the verification code.').'</p>';
|
||||
echo '<textarea rows="12" cols="72" readonly>'.htmlspecialchars($encrypted_challenge).'</textarea>';
|
||||
echo form_target('_parent', 'pgp2fa');
|
||||
echo hidden('nick', htmlspecialchars($nickname));
|
||||
echo '<table>';
|
||||
echo '<tr><td>'._('Verification code:').'</td><td><input type="text" name="pgpcode" size="20" autocomplete="one-time-code" autofocus></td></tr>';
|
||||
echo '<tr><td colspan="2">'.submit(_('Enter Chat')).'</td></tr></table></form>';
|
||||
echo form_target('_parent', '').submit(_('Back to the login page.'), 'class="backbutton"').'</form>';
|
||||
print_end();
|
||||
}
|
||||
|
||||
function send_login(): void
|
||||
{
|
||||
$ga=(int) get_setting('guestaccess');
|
||||
@@ -2836,6 +2972,9 @@ function create_session(bool $setup, string $nickname, string $password): void
|
||||
if($setup && $U['status']>=7){
|
||||
$U['incognito']=1;
|
||||
}
|
||||
if(!$setup && !empty(trim($U['pgpkey'] ?? ''))){
|
||||
start_pgp2fa();
|
||||
}
|
||||
$U['entry']=$U['lastpost']=time();
|
||||
}else{
|
||||
add_user_defaults($password);
|
||||
@@ -2865,6 +3004,189 @@ function create_session(bool $setup, string $nickname, string $password): void
|
||||
write_new_session($password);
|
||||
}
|
||||
|
||||
function pgp_encrypt_message(string $public_key, string $message) : string
|
||||
{
|
||||
try {
|
||||
$encrypted=pgp_try_encrypt_message($public_key, $message);
|
||||
} catch(Throwable $e) {
|
||||
send_error($e->getMessage());
|
||||
}
|
||||
return $encrypted;
|
||||
}
|
||||
|
||||
function pgp_try_encrypt_message(string $public_key, string $message) : string
|
||||
{
|
||||
$public_key=str_replace("\r\n", "\n", trim($public_key));
|
||||
if(extension_loaded('gnupg')){
|
||||
$gpg=new gnupg();
|
||||
$info=$gpg->import($public_key);
|
||||
$fingerprint=pgp_import_fingerprint($info);
|
||||
if($fingerprint!==''){
|
||||
$gpg->addencryptkey($fingerprint);
|
||||
$encrypted=$gpg->encrypt($message);
|
||||
if($encrypted!==false){
|
||||
return $encrypted;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pgp_try_encrypt_message_with_gpg($public_key, $message);
|
||||
}
|
||||
|
||||
function pgp_import_fingerprint($info) : string
|
||||
{
|
||||
if(!is_array($info)){
|
||||
return '';
|
||||
}
|
||||
if(!empty($info['fingerprint']) && is_string($info['fingerprint'])){
|
||||
return $info['fingerprint'];
|
||||
}
|
||||
if(!empty($info['fingerprints']) && is_array($info['fingerprints'])){
|
||||
foreach($info['fingerprints'] as $fingerprint){
|
||||
if(is_string($fingerprint) && $fingerprint!==''){
|
||||
return $fingerprint;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!empty($info[0]) && is_string($info[0])){
|
||||
return $info[0];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function pgp_try_encrypt_message_with_gpg(string $public_key, string $message) : string
|
||||
{
|
||||
if(!function_exists('exec')){
|
||||
throw new RuntimeException(_('The PHP exec function or the gnupg extension is required for PGP two-factor authentication.'));
|
||||
}
|
||||
$gpg=pgp_find_gpg_binary();
|
||||
$base=rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'lechatpgp-'.bin2hex(random_bytes(8));
|
||||
if(!mkdir($base, 0700)){
|
||||
throw new RuntimeException(_('Could not create a temporary directory for PGP two-factor authentication.'));
|
||||
}
|
||||
$keyfile=$base.DIRECTORY_SEPARATOR.'key.asc';
|
||||
$messagefile=$base.DIRECTORY_SEPARATOR.'message.txt';
|
||||
$outputfile=$base.DIRECTORY_SEPARATOR.'message.asc';
|
||||
file_put_contents($keyfile, $public_key);
|
||||
file_put_contents($messagefile, $message);
|
||||
try {
|
||||
$result=pgp_run_command([$gpg, '--batch', '--homedir', $base, '--import', $keyfile]);
|
||||
if($result['code']!==0){
|
||||
throw new RuntimeException(_('Invalid PGP public key.').' '.htmlspecialchars($result['output']));
|
||||
}
|
||||
$result=pgp_run_command([$gpg, '--batch', '--homedir', $base, '--with-colons', '--fingerprint', '--list-keys']);
|
||||
$fingerprint='';
|
||||
foreach(explode("\n", $result['output']) as $line){
|
||||
if(preg_match('/^fpr:::::::::([0-9A-F]+):/i', $line, $match)){
|
||||
$fingerprint=$match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($fingerprint===''){
|
||||
throw new RuntimeException(_('Invalid PGP public key.'));
|
||||
}
|
||||
$result=pgp_run_command([$gpg, '--batch', '--yes', '--trust-model', 'always', '--homedir', $base, '--armor', '--encrypt', '--recipient', $fingerprint, '--output', $outputfile, $messagefile]);
|
||||
if($result['code']!==0 || !is_file($outputfile)){
|
||||
throw new RuntimeException(_('Could not encrypt the PGP two-factor authentication challenge.').' '.htmlspecialchars($result['output']));
|
||||
}
|
||||
return file_get_contents($outputfile);
|
||||
} finally {
|
||||
pgp_remove_directory($base);
|
||||
}
|
||||
}
|
||||
|
||||
function pgp_find_gpg_binary() : string
|
||||
{
|
||||
if(defined('GPG_BINARY')){
|
||||
return GPG_BINARY;
|
||||
}
|
||||
foreach(['gpg', '/usr/bin/gpg', '/usr/local/bin/gpg', '/opt/homebrew/bin/gpg'] as $gpg){
|
||||
$result=pgp_run_command([$gpg, '--version']);
|
||||
if($result['code']===0){
|
||||
return $gpg;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException(_('The gpg command or the gnupg extension is required for PGP two-factor authentication.'));
|
||||
}
|
||||
|
||||
function pgp_run_command(array $args) : array
|
||||
{
|
||||
$cmd=implode(' ', array_map('escapeshellarg', $args));
|
||||
$output=[];
|
||||
$code=0;
|
||||
exec($cmd.' 2>&1', $output, $code);
|
||||
return ['code' => $code, 'output' => implode("\n", $output)];
|
||||
}
|
||||
|
||||
function pgp_remove_directory(string $dir): void
|
||||
{
|
||||
if(!is_dir($dir)){
|
||||
return;
|
||||
}
|
||||
foreach(scandir($dir) as $file){
|
||||
if($file==='.' || $file==='..'){
|
||||
continue;
|
||||
}
|
||||
$path=$dir.DIRECTORY_SEPARATOR.$file;
|
||||
if(is_dir($path)){
|
||||
pgp_remove_directory($path);
|
||||
}else{
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
function start_pgp2fa(): void
|
||||
{
|
||||
global $U, $db;
|
||||
try {
|
||||
$code=strtoupper(bin2hex(random_bytes(4)));
|
||||
} catch(Exception $e) {
|
||||
send_error($e->getMessage());
|
||||
}
|
||||
$message=sprintf(_("Your verification code for %s is: %s"), get_setting('chatname'), $code);
|
||||
$encrypted=pgp_encrypt_message($U['pgpkey'], $message);
|
||||
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET pgpchallengehash=?, pgpchallengeexpires=? WHERE nickname=?;');
|
||||
$stmt->execute([hash('sha256', $code), time()+300, $U['nickname']]);
|
||||
send_pgp2fa($U['nickname'], $encrypted);
|
||||
}
|
||||
|
||||
function check_pgp2fa(): void
|
||||
{
|
||||
global $U, $db;
|
||||
if(empty($_POST['nick']) || empty($_POST['pgpcode'])){
|
||||
send_login();
|
||||
}
|
||||
$nick=preg_replace('/\s/', '', $_POST['nick']);
|
||||
$stmt=$db->prepare('SELECT * FROM ' . PREFIX . 'members WHERE nickname=?;');
|
||||
$stmt->execute([$nick]);
|
||||
if(!$member=$stmt->fetch(PDO::FETCH_ASSOC)){
|
||||
send_error(_('Invalid PGP two-factor authentication challenge.'));
|
||||
}
|
||||
$hash=$member['pgpchallengehash'] ?? '';
|
||||
$expires=(int) ($member['pgpchallengeexpires'] ?? 0);
|
||||
$code=strtoupper(preg_replace('/[^0-9A-F]/i', '', $_POST['pgpcode']));
|
||||
if($hash==='' || $expires<time() || !hash_equals($hash, hash('sha256', $code))){
|
||||
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET loginfails=? WHERE nickname=?;');
|
||||
$stmt->execute([$member['loginfails']+1, $member['nickname']]);
|
||||
send_error(_('Invalid or expired PGP two-factor authentication code.'));
|
||||
}
|
||||
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET pgpchallengehash=?, pgpchallengeexpires=?, lastlogin=? WHERE nickname=?;');
|
||||
$stmt->execute(['', 0, time(), $member['nickname']]);
|
||||
$U=$member;
|
||||
$U['entry']=$U['lastpost']=time();
|
||||
$U['exiting']=0;
|
||||
try {
|
||||
$U[ 'postid' ] = bin2hex( random_bytes( 3 ) );
|
||||
} catch(Exception $e) {
|
||||
send_error($e->getMessage());
|
||||
}
|
||||
write_new_session('', true);
|
||||
if($U['status']==1 && in_array((int) get_setting('guestaccess'), [2, 3], true)){
|
||||
send_waiting_room();
|
||||
}
|
||||
}
|
||||
|
||||
function check_captcha(string $challenge, string $captcha_code): void
|
||||
{
|
||||
global $db, $memcached;
|
||||
@@ -2920,14 +3242,14 @@ function set_secure_cookie(string $name, string $value): void
|
||||
}
|
||||
}
|
||||
|
||||
function write_new_session(string $password): void
|
||||
function write_new_session(string $password, bool $authenticated=false): 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'])){
|
||||
if(($authenticated && hash_equals($U['passhash'], $temp['passhash'])) || password_verify($password, $temp['passhash'])){
|
||||
$U=$temp;
|
||||
check_kicked();
|
||||
set_secure_cookie(COOKIENAME, $U['session']);
|
||||
@@ -3432,8 +3754,18 @@ function save_profile() : string {
|
||||
$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']]);
|
||||
$pgpkey=trim($_POST['pgpkey'] ?? '');
|
||||
if(isset($_POST['clearpgpkey'])){
|
||||
$pgpkey='';
|
||||
}elseif($pgpkey!==''){
|
||||
try {
|
||||
pgp_try_encrypt_message($pgpkey, 'test');
|
||||
} catch(Throwable $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
$stmt=$db->prepare('UPDATE ' . PREFIX . 'members SET refresh=?, bgcolour=?, timestamps=?, embed=?, incognito=?, style=?, nocache=?, tz=?, eninbox=?, sortupdown=?, hidechatters=?, pgpkey=?, pgpchallengehash=?, pgpchallengeexpires=? 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'], $pgpkey, '', 0, $U['nickname']]);
|
||||
}
|
||||
if(!empty($_POST['unignore'])){
|
||||
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'ignored WHERE ign=? AND ignby=?;');
|
||||
@@ -3641,7 +3973,8 @@ function validate_input() : string {
|
||||
$message=sprintf(get_setting('msgattache'), "<a class=\"attachement\" href=\"$_SERVER[SCRIPT_NAME]?action=download&id=$hash\" target=\"_blank\">$name</a>", $message);
|
||||
}
|
||||
}
|
||||
if(add_message($message, $recipient, $U['nickname'], (int) $U['status'], $poststatus, $displaysend, $U['style'])){
|
||||
$reply_to=get_valid_reply_id($_POST['reply_to'] ?? 0);
|
||||
if(add_message($message, $recipient, $U['nickname'], (int) $U['status'], $poststatus, $displaysend, $U['style'], $reply_to)){
|
||||
$U['lastpost']=time();
|
||||
try {
|
||||
$U[ 'postid' ] = bin2hex( random_bytes( 3 ) );
|
||||
@@ -3814,7 +4147,7 @@ function apply_mention(string $message) : string {
|
||||
}, $message);
|
||||
}
|
||||
|
||||
function add_message(string $message, string $recipient, string $poster, int $delstatus, int $poststatus, string $displaysend, string$style) : bool {
|
||||
function add_message(string $message, string $recipient, string $poster, int $delstatus, int $poststatus, string $displaysend, string $style, int $reply_to=0) : bool {
|
||||
global $db;
|
||||
if($message===''){
|
||||
return false;
|
||||
@@ -3825,11 +4158,12 @@ function add_message(string $message, string $recipient, string $poster, int $de
|
||||
'poster' =>$poster,
|
||||
'recipient' =>$recipient,
|
||||
'text' =>"<span class=\"usermsg\">$displaysend".style_this($message, $style).'</span>',
|
||||
'delstatus' =>$delstatus
|
||||
'delstatus' =>$delstatus,
|
||||
'reply_to' =>$reply_to
|
||||
];
|
||||
//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']]);
|
||||
$stmt=$db->prepare('SELECT id FROM ' . PREFIX . 'messages WHERE poststatus=? AND poster=? AND recipient=? AND text=? AND reply_to=? 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'], $newmessage['reply_to']]);
|
||||
if($stmt->fetch(PDO::FETCH_NUM)){
|
||||
return false;
|
||||
}
|
||||
@@ -3849,7 +4183,8 @@ function add_system_message(string $mes, string $doer): void
|
||||
'poster' =>'',
|
||||
'recipient' =>'',
|
||||
'text' =>"$mes",
|
||||
'delstatus' =>4
|
||||
'delstatus' =>4,
|
||||
'reply_to' =>0
|
||||
];
|
||||
|
||||
} else {
|
||||
@@ -3859,7 +4194,8 @@ function add_system_message(string $mes, string $doer): void
|
||||
'poster' =>'',
|
||||
'recipient' =>'',
|
||||
'text' =>"$mes ($doer)",
|
||||
'delstatus' =>4
|
||||
'delstatus' =>4,
|
||||
'reply_to' =>0
|
||||
];
|
||||
}
|
||||
write_message($sysmessage);
|
||||
@@ -3876,7 +4212,8 @@ function add_system_pm_message(string $mes, string $recipient, string $doer): vo
|
||||
'poster' =>'System',
|
||||
'recipient' => $recipient,
|
||||
'text' =>"$mes",
|
||||
'delstatus' =>4
|
||||
'delstatus' =>4,
|
||||
'reply_to' =>0
|
||||
];
|
||||
|
||||
} else {
|
||||
@@ -3886,7 +4223,8 @@ function add_system_pm_message(string $mes, string $recipient, string $doer): vo
|
||||
'poster' =>'System',
|
||||
'recipient' => $recipient,
|
||||
'text' =>"$mes ($doer)",
|
||||
'delstatus' =>4
|
||||
'delstatus' =>4,
|
||||
'reply_to' =>0
|
||||
];
|
||||
}
|
||||
write_message($sysmessage);
|
||||
@@ -3901,8 +4239,11 @@ function write_message(array $message): void
|
||||
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(!isset($message['reply_to'])){
|
||||
$message['reply_to']=0;
|
||||
}
|
||||
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'messages (postdate, poststatus, poster, recipient, text, delstatus, reply_to) VALUES (?, ?, ?, ?, ?, ?, ?);');
|
||||
$stmt->execute([$message['postdate'], $message['poststatus'], $message['poster'], $message['recipient'], $message['text'], $message['delstatus'], $message['reply_to']]);
|
||||
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";
|
||||
@@ -4008,7 +4349,7 @@ function print_messages(int $delstatus=0): void
|
||||
}
|
||||
echo '<div id="messages">';
|
||||
if($delstatus>0){
|
||||
$stmt=$db->prepare('SELECT postdate, id, text FROM ' . PREFIX . 'messages WHERE '.
|
||||
$stmt=$db->prepare('SELECT postdate, id, text, reply_to 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)){
|
||||
@@ -4020,7 +4361,7 @@ function print_messages(int $delstatus=0): void
|
||||
echo " $message[text]</label></div>";
|
||||
}
|
||||
}else{
|
||||
$stmt=$db->prepare('SELECT id, postdate, poststatus, text FROM ' . PREFIX . 'messages WHERE (poststatus<=? OR poststatus=4 OR '.
|
||||
$stmt=$db->prepare('SELECT id, postdate, poststatus, poster, recipient, text, reply_to 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']]);
|
||||
@@ -4033,13 +4374,47 @@ function print_messages(int $delstatus=0): void
|
||||
if ($message['poststatus']==4) {
|
||||
echo '<span class="sysmsg" title="'._('system message').'">'.get_setting('sysmessagetxt')."$message[text]</span></div>";
|
||||
} else {
|
||||
echo "$message[text]</div>";
|
||||
print_reply_reference((int) $message['reply_to']);
|
||||
echo "$message[text]";
|
||||
print_message_actions($message);
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
function print_reply_reference(int $reply_to): void
|
||||
{
|
||||
global $db;
|
||||
if($reply_to<1 || !can_view_message($reply_to)){
|
||||
return;
|
||||
}
|
||||
$stmt=$db->prepare('SELECT poster FROM ' . PREFIX . 'messages WHERE id=?;');
|
||||
$stmt->execute([$reply_to]);
|
||||
if($message=$stmt->fetch(PDO::FETCH_ASSOC)){
|
||||
echo '<div class="replyref">'.sprintf(_('Reply to #%1$d by %2$s'), $reply_to, htmlspecialchars($message['poster'])).'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function print_message_actions(array $message): void
|
||||
{
|
||||
global $U;
|
||||
$liked=is_message_liked((int) $message['id']);
|
||||
$likes=count_message_likes((int) $message['id']);
|
||||
echo '<div class="msgactions">';
|
||||
echo form('like').hidden('mid', (string) $message['id']);
|
||||
echo submit(($liked ? _('Unlike') : _('Like'))." ($likes)").'</form> ';
|
||||
echo form_target('post', 'post');
|
||||
echo hidden('reply_to', (string) $message['id']);
|
||||
echo hidden('sendto', htmlspecialchars(get_reply_sendto((int) $message['id'])));
|
||||
if($U['sortupdown']){
|
||||
echo hidden('sort', '1');
|
||||
}
|
||||
echo submit(_('Reply')).'</form>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
function prepare_message_print(array &$message, bool $removeEmbed): void
|
||||
{
|
||||
if(MSGENCRYPTED){
|
||||
@@ -4058,6 +4433,76 @@ function prepare_message_print(array &$message, bool $removeEmbed): void
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_like(): void
|
||||
{
|
||||
global $U, $db;
|
||||
$message_id=(int) ($_POST['mid'] ?? 0);
|
||||
if($message_id<1 || !can_view_message($message_id)){
|
||||
return;
|
||||
}
|
||||
$stmt=$db->prepare('SELECT id FROM ' . PREFIX . 'likes WHERE message_id=? AND nickname=?;');
|
||||
$stmt->execute([$message_id, $U['nickname']]);
|
||||
if($like=$stmt->fetch(PDO::FETCH_ASSOC)){
|
||||
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'likes WHERE id=?;');
|
||||
$stmt->execute([$like['id']]);
|
||||
}else{
|
||||
$stmt=$db->prepare('INSERT INTO ' . PREFIX . 'likes (message_id, nickname) VALUES (?, ?);');
|
||||
$stmt->execute([$message_id, $U['nickname']]);
|
||||
}
|
||||
}
|
||||
|
||||
function count_message_likes(int $message_id): int
|
||||
{
|
||||
global $db;
|
||||
$stmt=$db->prepare('SELECT COUNT(*) FROM ' . PREFIX . 'likes WHERE message_id=?;');
|
||||
$stmt->execute([$message_id]);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function is_message_liked(int $message_id): bool
|
||||
{
|
||||
global $U, $db;
|
||||
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'likes WHERE message_id=? AND nickname=?;');
|
||||
$stmt->execute([$message_id, $U['nickname']]);
|
||||
return (bool) $stmt->fetch(PDO::FETCH_NUM);
|
||||
}
|
||||
|
||||
function get_valid_reply_id($reply_to): int
|
||||
{
|
||||
$reply_to=(int) $reply_to;
|
||||
if($reply_to<1 || !can_view_message($reply_to)){
|
||||
return 0;
|
||||
}
|
||||
return $reply_to;
|
||||
}
|
||||
|
||||
function get_reply_sendto(int $reply_to): string
|
||||
{
|
||||
global $U, $db;
|
||||
$stmt=$db->prepare('SELECT poststatus, poster, recipient FROM ' . PREFIX . 'messages WHERE id=?;');
|
||||
$stmt->execute([$reply_to]);
|
||||
if($message=$stmt->fetch(PDO::FETCH_ASSOC)){
|
||||
if((int) $message['poststatus']===9 && $message['poster']!==$U['nickname']){
|
||||
return $message['poster'];
|
||||
}
|
||||
}
|
||||
return 's *';
|
||||
}
|
||||
|
||||
function can_view_message(int $message_id): bool
|
||||
{
|
||||
global $U, $db;
|
||||
if($message_id<1){
|
||||
return false;
|
||||
}
|
||||
$entry=$U['status']>1 ? 0 : $U['entry'];
|
||||
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'messages WHERE id=? AND (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=?);');
|
||||
$stmt->execute([$message_id, $U['status'], $U['nickname'], $U['nickname'], $U['nickname'], $entry, $U['nickname']]);
|
||||
return (bool) $stmt->fetch(PDO::FETCH_NUM);
|
||||
}
|
||||
|
||||
// this and that
|
||||
|
||||
function send_headers(): void
|
||||
@@ -4342,6 +4787,12 @@ function cron(): void
|
||||
while($tmp=$result->fetch(PDO::FETCH_NUM)){
|
||||
$stmt->execute($tmp);
|
||||
}
|
||||
// delete likes that do not belong to any message
|
||||
$result=$db->query('SELECT id FROM ' . PREFIX . 'likes WHERE message_id NOT IN (SELECT id FROM ' . PREFIX . 'messages);');
|
||||
$stmt=$db->prepare('DELETE FROM ' . PREFIX . 'likes WHERE id=?;');
|
||||
while($tmp=$result->fetch(PDO::FETCH_NUM)){
|
||||
$stmt->execute($tmp);
|
||||
}
|
||||
// delete old notes
|
||||
$limit=get_setting('numnotes');
|
||||
$to_keep = [];
|
||||
@@ -4452,16 +4903,20 @@ function init_chat(): void
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "ignored (id $primary, ign varchar(50) NOT NULL, ignby varchar(50) NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'ign ON ' . PREFIX . 'ignored(ign);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'ignby ON ' . PREFIX . 'ignored(ignby);');
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "members (id $primary, nickname varchar(50) NOT NULL UNIQUE, passhash varchar(255) NOT NULL, status smallint NOT NULL, refresh smallint NOT NULL, bgcolour char(6) NOT NULL, regedby varchar(50) DEFAULT '', lastlogin integer DEFAULT 0, loginfails integer unsigned NOT NULL DEFAULT 0, timestamps smallint NOT NULL, embed smallint NOT NULL, incognito smallint NOT NULL, style varchar(255) NOT NULL, nocache smallint NOT NULL, tz varchar(255) NOT NULL, eninbox smallint NOT NULL, sortupdown smallint NOT NULL, hidechatters smallint NOT NULL, nocache_old smallint NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "members (id $primary, nickname varchar(50) NOT NULL UNIQUE, passhash varchar(255) NOT NULL, status smallint NOT NULL, refresh smallint NOT NULL, bgcolour char(6) NOT NULL, regedby varchar(50) DEFAULT '', lastlogin integer DEFAULT 0, loginfails integer unsigned NOT NULL DEFAULT 0, timestamps smallint NOT NULL, embed smallint NOT NULL, incognito smallint NOT NULL, style varchar(255) NOT NULL, nocache smallint NOT NULL, tz varchar(255) NOT NULL, eninbox smallint NOT NULL, sortupdown smallint NOT NULL, hidechatters smallint NOT NULL, nocache_old smallint NOT NULL, pgpkey text, pgpchallengehash varchar(64) NOT NULL DEFAULT '', pgpchallengeexpires integer NOT NULL DEFAULT 0)$diskengine$charset;");
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "inbox (id $primary, postdate integer NOT NULL, postid integer NOT NULL UNIQUE, poster varchar(50) NOT NULL, recipient varchar(50) NOT NULL, text text NOT NULL, FOREIGN KEY (recipient) REFERENCES " . PREFIX . "members(nickname) ON DELETE CASCADE ON UPDATE CASCADE)$diskengine$charset;");
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'inbox_poster ON ' . PREFIX . 'inbox(poster);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'inbox_recipient ON ' . PREFIX . 'inbox(recipient);');
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "linkfilter (id $primary, filtermatch varchar(255) NOT NULL, filterreplace varchar(255) NOT NULL, regex smallint NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "messages (id $primary, postdate integer NOT NULL, poststatus smallint NOT NULL, poster varchar(50) NOT NULL, recipient varchar(50) NOT NULL, text text NOT NULL, delstatus smallint NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "messages (id $primary, postdate integer NOT NULL, poststatus smallint NOT NULL, poster varchar(50) NOT NULL, recipient varchar(50) NOT NULL, text text NOT NULL, delstatus smallint NOT NULL, reply_to integer NOT NULL DEFAULT 0)$diskengine$charset;");
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'poster ON ' . PREFIX . 'messages (poster);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'recipient ON ' . PREFIX . 'messages(recipient);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'postdate ON ' . PREFIX . 'messages(postdate);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'poststatus ON ' . PREFIX . 'messages(poststatus);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'reply_to ON ' . PREFIX . 'messages(reply_to);');
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "likes (id $primary, message_id integer NOT NULL, nickname varchar(50) NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'likes_message_id ON ' . PREFIX . 'likes(message_id);');
|
||||
$db->exec('CREATE UNIQUE INDEX ' . PREFIX . 'likes_message_nickname ON ' . PREFIX . 'likes(message_id, nickname);');
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "notes (id $primary, type smallint NOT NULL, lastedited integer NOT NULL, editedby varchar(50) NOT NULL, text text NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'notes_type ON ' . PREFIX . 'notes(type);');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'notes_editedby ON ' . PREFIX . 'notes(editedby);');
|
||||
@@ -4940,7 +5395,7 @@ function update_db(): void
|
||||
$db->exec('INSERT INTO ' . PREFIX . "settings (setting,value) VALUES ('hide_reload_post_box', '0'), ('hide_reload_messages', '0'),('hide_profile', '0'),('hide_admin', '0'),('hide_notes', '0'),('hide_clone', '0'),('hide_rearrange', '0'),('hide_help', '0'),('max_refresh_rate', '150'),('min_refresh_rate', '5'),('postbox_delete_globally', '0'),('allow_js', '1');");
|
||||
}
|
||||
if($dbversion<48){
|
||||
$db->exec('INSERT INTO ' . PREFIX . "settings (setting, value) VALUES ('exitwait', '180'), ('exitingtxt', ' 🚪"); // door emoji
|
||||
$db->exec('INSERT INTO ' . PREFIX . "settings (setting, value) VALUES ('exitwait', '180'), ('exitingtxt', ' 🚪');"); // door emoji
|
||||
$db->exec('ALTER TABLE ' . PREFIX . 'sessions ADD COLUMN exiting smallint NOT NULL DEFAULT 0;');
|
||||
}
|
||||
if($dbversion<49){
|
||||
@@ -4952,6 +5407,18 @@ function update_db(): void
|
||||
if($dbversion<51){
|
||||
$db->exec('INSERT INTO ' . PREFIX . "settings (setting,value) VALUES ('captchagdfont', '');");
|
||||
}
|
||||
if($dbversion<52){
|
||||
$db->exec('ALTER TABLE ' . PREFIX . 'members ADD COLUMN pgpkey text;');
|
||||
$db->exec('ALTER TABLE ' . PREFIX . "members ADD COLUMN pgpchallengehash varchar(64) NOT NULL DEFAULT '';");
|
||||
$db->exec('ALTER TABLE ' . PREFIX . 'members ADD COLUMN pgpchallengeexpires integer NOT NULL DEFAULT 0;');
|
||||
}
|
||||
if($dbversion<53){
|
||||
$db->exec('ALTER TABLE ' . PREFIX . 'messages ADD COLUMN reply_to integer NOT NULL DEFAULT 0;');
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'reply_to ON ' . PREFIX . 'messages(reply_to);');
|
||||
$db->exec('CREATE TABLE ' . PREFIX . "likes (id $primary, message_id integer NOT NULL, nickname varchar(50) NOT NULL)$diskengine$charset;");
|
||||
$db->exec('CREATE INDEX ' . PREFIX . 'likes_message_id ON ' . PREFIX . 'likes(message_id);');
|
||||
$db->exec('CREATE UNIQUE INDEX ' . PREFIX . 'likes_message_nickname ON ' . PREFIX . 'likes(message_id, nickname);');
|
||||
}
|
||||
update_setting('dbversion', DBVERSION);
|
||||
if($msgencrypted!==MSGENCRYPTED){
|
||||
if(!extension_loaded('sodium')){
|
||||
@@ -5154,7 +5621,7 @@ function load_lang(): void
|
||||
function load_config(): void
|
||||
{
|
||||
define('VERSION', '1.24.1'); // Script version
|
||||
define('DBVERSION', 51); // Database layout version
|
||||
define('DBVERSION', 53); // Database layout version
|
||||
define('MSGENCRYPTED', false); // Store messages encrypted in the database to prevent other database users from reading them - true/false - visit the setup page after editing!
|
||||
define('ENCRYPTKEY_PASS', 'MY_SECRET_KEY'); // Recommended length: 32. Encryption key for messages
|
||||
define('AES_IV_PASS', '012345678912'); // Recommended length: 12. AES Encryption IV
|
||||
|
||||
Reference in New Issue
Block a user