From 9cf227363f316236b815398f484833ddcedb9f7b Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Mon, 20 May 2024 03:01:44 -0700 Subject: [PATCH 01/11] Add prefix to memcache key names to allow for multiple rslight instances. --- Rocksolid_Light/rocksolid/lib/thread.inc.php | 6 +-- Rocksolid_Light/rocksolid/newsportal.php | 51 +++++++++++--------- Rocksolid_Light/rslight/memcached.inc.php | 7 +++ 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Rocksolid_Light/rocksolid/lib/thread.inc.php b/Rocksolid_Light/rocksolid/lib/thread.inc.php index 32903ac..d9a56b3 100644 --- a/Rocksolid_Light/rocksolid/lib/thread.inc.php +++ b/Rocksolid_Light/rocksolid/lib/thread.inc.php @@ -73,7 +73,7 @@ function thread_cache_load($group) } // Check memcache if ($memcacheD) { - $key = 'thread_cache-' . $group; + $key = $memcache_key_prefix . '_' . 'thread_cache-' . $group; if ($headers = unserialize(gzuncompress($memcacheD->get($key)))) { if ($enable_memcache_logging) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $key", FILE_APPEND); @@ -94,7 +94,7 @@ function thread_cache_load($group) $dbh = null; } if ($memcacheD) { - $key = 'thread_cache-' . $group; + $key = $memcache_key_prefix . '_' . 'thread_cache-' . $group; $add_thread = gzcompress(serialize($headers), 9); $thread_bytes = strlen($add_thread); @@ -151,7 +151,7 @@ function thread_cache_save($headers, $group) $dbh->commit(); $dbh = null; if ($memcacheD) { - $key = 'thread_cache-' . $group; + $key = $memcache_key_prefix . '_' . 'thread_cache-' . $group; $del = $memcacheD->delete($key); $add_thread = gzcompress(serialize($headers), 9); $thread_bytes = strlen($add_thread); diff --git a/Rocksolid_Light/rocksolid/newsportal.php b/Rocksolid_Light/rocksolid/newsportal.php index aafe245..7dade47 100644 --- a/Rocksolid_Light/rocksolid/newsportal.php +++ b/Rocksolid_Light/rocksolid/newsportal.php @@ -640,7 +640,7 @@ function groups_show($gruppen) // Get last article info from article database // First check memcache if ($memcacheD) { - $lar_memcache = 'lastarticleinfo-' . $g->name; + $lar_memcache = $memcache_key_prefix . '_' . 'lastarticleinfo-' . $g->name; $groupfile = $spooldir . '/' . $g->name . '-lastarticleinfo.dat'; if ($lastarticleinfo = unserialize($memcacheD->get($lar_memcache))) { if ($lastarticleinfo && file_exists($groupfile) && filemtime($groupfile) <= $lastarticleinfo['date']) { @@ -1625,7 +1625,7 @@ function get_newsgroups_by_msgid($msgid, $noarray = false) include $config_dir . '/memcache.inc.php'; } if ($memcacheD) { - $key = 'get_newsgroups_by_msgid-' . $msgid; + $key = $memcache_key_prefix . '_' . 'get_newsgroups_by_msgid-' . $msgid; if ($groups = $memcacheD->get($key)) { if ($enable_memcache_logging) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $key", FILE_APPEND); @@ -1902,7 +1902,7 @@ function np_get_db_article($article, $group, $makearray = 1, $dbh = null) $ok_article = 0; // Check memcache if ($memcacheD) { - $article_key = 'article.db3-' . $group . ':' . $article; + $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $article; if ($msg2 = $memcacheD->get($article_key)) { $ok_article = 1; if ($enable_memcache_logging) { @@ -1968,24 +1968,24 @@ function get_poster_name($name) $fromline = address_decode($name, "nowhere"); if (! isset($fromline[0]["host"])) $fromline[0]["host"] = ""; - $name_from = $fromline[0]["mailbox"] . "@" . $fromline[0]["host"]; - $name_username = $fromline[0]["mailbox"]; - if (! isset($fromline[0]["personal"])) { - $poster_name = $fromline[0]["mailbox"]; - } else { - $poster_name = $fromline[0]["personal"]; - } - if (trim($poster_name) == '') { - $fromoutput = explode("<", html_entity_decode($name)); - if (strlen($fromoutput[0]) < 1) { - $poster_name = $fromoutput[1]; + $name_from = $fromline[0]["mailbox"] . "@" . $fromline[0]["host"]; + $name_username = $fromline[0]["mailbox"]; + if (! isset($fromline[0]["personal"])) { + $poster_name = $fromline[0]["mailbox"]; } else { - $poster_name = $fromoutput[0]; + $poster_name = $fromline[0]["personal"]; } - } - $thisposter['name'] = $poster_name; - $thisposter['from'] = $name_from; - return ($thisposter); + if (trim($poster_name) == '') { + $fromoutput = explode("<", html_entity_decode($name)); + if (strlen($fromoutput[0]) < 1) { + $poster_name = $fromoutput[1]; + } else { + $poster_name = $fromoutput[0]; + } + } + $thisposter['name'] = $poster_name; + $thisposter['from'] = $name_from; + return ($thisposter); } /* @@ -2457,7 +2457,7 @@ function insert_article_from_array($this_article, $check_duplicates = true) include $config_dir . '/memcache.inc.php'; } if ($memcacheD) { - $article_key = 'article.db3-' . $group . ':' . $this_article['local']; + $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $this_article['local']; $nicole = $memcacheD->add($article_key, $this_article['article'], $memcache_ttl); if ($enable_memcache_logging && $nicole) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote (new) $article_key", FILE_APPEND); @@ -2537,7 +2537,7 @@ function get_db_data_from_msgid($msgid, $group) } if ($memcacheD) { - $row_cache = 'get_db_data_from_msgid-' . $msgid; + $row_cache = $memcache_key_prefix . '_' . 'get_db_data_from_msgid-' . $msgid; if ($row = $memcacheD->get($row_cache)) { if ($enable_memcache_logging) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $row_cache", FILE_APPEND); @@ -2611,7 +2611,7 @@ function get_data_from_msgid($msgid, $thisgroup = null) } if ($memcacheD) { - $row_cache = 'get_data_from_msgid-' . $msgid; + $row_cache = $memcache_key_prefix . '_' . 'get_data_from_msgid-' . $msgid; if ($row = $memcacheD->get($row_cache)) { if ($enable_memcache_logging) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $row_cache", FILE_APPEND); @@ -2806,7 +2806,7 @@ function delete_message($messageid, $group = null, $overview_dbh = null) ]); // Delete article from memcache if ($memcacheD) { - $article_key = 'article.db3-' . $group . ':' . $row['number']; + $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $row['number']; $result = $memcacheD->delete($article_key); if ($enable_memcache_logging) { if ($result) { @@ -2877,11 +2877,14 @@ function wrap_post($body) $depth = 0; while ($line[$depth] == '>') { $depth ++; + if ($depth > 30) { + break; + } } if (strlen($line) > $line_length) { // HERE is where we wrap quoted lines (not so easy) $start = substr($line, 0, $depth + 1); - $end = substr($line, $depth +1); + $end = substr($line, $depth + 1); $line_wrapped = $start . mb_wordwrap($end, $line_length); $line_wrapped = preg_split("/\n/", $line_wrapped); foreach ($line_wrapped as $lw) { diff --git a/Rocksolid_Light/rslight/memcached.inc.php b/Rocksolid_Light/rslight/memcached.inc.php index 52aaf0a..e6ff7eb 100644 --- a/Rocksolid_Light/rslight/memcached.inc.php +++ b/Rocksolid_Light/rslight/memcached.inc.php @@ -24,6 +24,13 @@ $memcache_ttl = 14400; */ $memcache_maxitemsize = 1024000; +/* + * A string to prepend to cached key names + * Required if using more than one rslight instance + * with one memcache instance + */ +$memcache_key_prefix = 'rsl'; + /* PLEASE DO NOT EDIT BELOW THIS LINE */ if ($enable_memcache) { From 95f1b1c1495e6b1e012e2339b887684a0cc4861d Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Mon, 20 May 2024 03:59:30 -0700 Subject: [PATCH 02/11] Avoid retrieving articles multiple times for display_full_headers. --- Rocksolid_Light/rocksolid/lib/message.inc.php | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Rocksolid_Light/rocksolid/lib/message.inc.php b/Rocksolid_Light/rocksolid/lib/message.inc.php index 5036df0..5ab31b4 100644 --- a/Rocksolid_Light/rocksolid/lib/message.inc.php +++ b/Rocksolid_Light/rocksolid/lib/message.inc.php @@ -185,7 +185,7 @@ function message_parse($rawmessage) */ function message_read($id, $bodynum = 0, $group = "") { - global $CONFIG, $config_name, $cache_articles, $spooldir, $spoolpath, $logdir, $text_error, $ns; + global $CONFIG, $config_name, $cache_articles, $spooldir, $spoolpath, $logdir, $text_error, $ns, $current_article; $logfile = $logdir . '/newsportal.log'; if (! testGroup($group)) { echo $text_error["read_access_denied"]; @@ -227,6 +227,7 @@ function message_read($id, $bodynum = 0, $group = "") unset($rawmessage); if ($CONFIG['article_database'] == '1') { $rawmessage = np_get_db_article($id, $group, 1); + $current_article = $rawmessage; } else { $articlepath = $spoolpath . preg_replace('/\./', '/', $group) . "/" . $id; if (file_exists($articlepath)) { @@ -601,15 +602,19 @@ function copy_messageid() function display_full_headers($article, $group, $name, $from, $getface = false) { - global $spoolpath, $CONFIG; - if ($CONFIG['article_database'] == '1') { - $message = np_get_db_article($article, $group, 1); - } else { - $thisgroup = $spoolpath . "/" . preg_replace('/\./', '/', $group); - if (! file_exists($group . "/" . $article)) { - // Return something useful + global $spoolpath, $CONFIG, $current_message; + if (! isset($current_message)) { + if ($CONFIG['article_database'] == '1') { + $message = np_get_db_article($article, $group, 1); + } else { + $thisgroup = $spoolpath . "/" . preg_replace('/\./', '/', $group); + if (! file_exists($group . "/" . $article)) { + // Return something useful + } + $message = file($thisgroup . "/" . $article, FILE_IGNORE_NEW_LINES); } - $message = file($thisgroup . "/" . $article, FILE_IGNORE_NEW_LINES); + } else { + $message = $current_message; } if (isset($sendface)) { unlink($sendface); @@ -762,7 +767,10 @@ function message_show($group, $id, $attachment = 0, $article_data = false, $maxl global $file_article, $file_article_full, $OVERRIDES, $spooldir; global $text_header, $text_article, $article_showthread, $file_attachment, $attachment_show; global $block_xnoarchive, $article_graphicquotes; - global $CONFIG; + global $CONFIG, $current_message; + if (! isset($current_message)) { + $current_message = np_get_db_article($id, $group, 1); + } if ($article_data == false) $article_data = message_read($id, $attachment, $group); $head = $article_data->header; @@ -785,14 +793,14 @@ function message_show($group, $id, $attachment = 0, $article_data = false, $maxl } } $block = false; - foreach($blocked_user_config as $key => $value) { - $blockme = '/'.addslashes($key).'/'; + foreach ($blocked_user_config as $key => $value) { + $blockme = '/' . addslashes($key) . '/'; if (preg_match($blockme, $head->from)) { $block = true; break; } } - + if ($block == true) { echo '

(message #' . $head->number . ' hidden by your blocklist)


'; return "blocked"; From b7badacd3f984cd7d1e26ce941814f1283474d85 Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Mon, 20 May 2024 04:15:15 -0700 Subject: [PATCH 03/11] Standardize wording in memcache logging. --- Rocksolid_Light/rocksolid/lib/thread.inc.php | 6 +++--- Rocksolid_Light/rocksolid/newsportal.php | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Rocksolid_Light/rocksolid/lib/thread.inc.php b/Rocksolid_Light/rocksolid/lib/thread.inc.php index d9a56b3..0a2bbb2 100644 --- a/Rocksolid_Light/rocksolid/lib/thread.inc.php +++ b/Rocksolid_Light/rocksolid/lib/thread.inc.php @@ -107,7 +107,7 @@ function thread_cache_load($group) } if ($nicole && $enable_memcache_logging) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache miss) Wrote $key (" . strlen($add_thread) . " bytes)", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $key (" . strlen($add_thread) . " bytes)", FILE_APPEND); } if ($too_big) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " $key too large (" . $thread_bytes . " bytes)", FILE_APPEND); @@ -164,10 +164,10 @@ function thread_cache_save($headers, $group) } if ($enable_memcache_logging) { if ($del) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Deleted $key", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache delete) $key", FILE_APPEND); } if ($nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote $key (" . $thread_bytes . " bytes)", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $key (" . $thread_bytes . " bytes)", FILE_APPEND); } if ($too_big) { file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " $key too large (" . $thread_bytes . " bytes)", FILE_APPEND); diff --git a/Rocksolid_Light/rocksolid/newsportal.php b/Rocksolid_Light/rocksolid/newsportal.php index 7dade47..7c7760d 100644 --- a/Rocksolid_Light/rocksolid/newsportal.php +++ b/Rocksolid_Light/rocksolid/newsportal.php @@ -691,9 +691,9 @@ function groups_show($gruppen) $memcacheD->add($lar_memcache, serialize($row), $memcache_ttl); if ($enable_memcache_logging) { if ($nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Updated $lar_memcache", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache update) $lar_memcache", FILE_APPEND); } else { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote $lar_memcache", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $lar_memcache", FILE_APPEND); } } } @@ -1653,7 +1653,7 @@ function get_newsgroups_by_msgid($msgid, $noarray = false) if ($groups && $memcacheD) { $nicole = $memcacheD->add($key, $groups, $memcache_ttl); if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote $key", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $key", FILE_APPEND); } } } @@ -1942,7 +1942,7 @@ function np_get_db_article($article, $group, $makearray = 1, $dbh = null) if ($ok_article == 1 && $memcacheD) { $nicole = $memcacheD->add($article_key, $msg2, $memcache_ttl); if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote $article_key", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $article_key", FILE_APPEND); } } } @@ -2460,7 +2460,7 @@ function insert_article_from_array($this_article, $check_duplicates = true) $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $this_article['local']; $nicole = $memcacheD->add($article_key, $this_article['article'], $memcache_ttl); if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote (new) $article_key", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) (new) $article_key", FILE_APPEND); } } } else { @@ -2565,7 +2565,7 @@ function get_db_data_from_msgid($msgid, $group) if ($memcacheD) { $nicole = $memcacheD->add($row_cache, $row, $memcache_ttl); if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote $row_cache", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $row_cache", FILE_APPEND); } } return $row; @@ -2644,7 +2644,7 @@ function get_data_from_msgid($msgid, $thisgroup = null) if ($memcacheD) { $nicole = $memcacheD->add($row_cache, $row, $memcache_ttl); if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Wrote $row_cache", FILE_APPEND); + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $row_cache", FILE_APPEND); } } return $row; From 97c864d4de6a5b815f20ddec5a9313560d36a737 Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Mon, 20 May 2024 05:22:24 -0700 Subject: [PATCH 04/11] At least remove one of the three requests for an article from cache. --- Rocksolid_Light/rocksolid/lib/message.inc.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Rocksolid_Light/rocksolid/lib/message.inc.php b/Rocksolid_Light/rocksolid/lib/message.inc.php index 5ab31b4..517388d 100644 --- a/Rocksolid_Light/rocksolid/lib/message.inc.php +++ b/Rocksolid_Light/rocksolid/lib/message.inc.php @@ -185,7 +185,7 @@ function message_parse($rawmessage) */ function message_read($id, $bodynum = 0, $group = "") { - global $CONFIG, $config_name, $cache_articles, $spooldir, $spoolpath, $logdir, $text_error, $ns, $current_article; + global $CONFIG, $config_name, $cache_articles, $spooldir, $spoolpath, $logdir, $text_error, $ns; $logfile = $logdir . '/newsportal.log'; if (! testGroup($group)) { echo $text_error["read_access_denied"]; @@ -227,7 +227,6 @@ function message_read($id, $bodynum = 0, $group = "") unset($rawmessage); if ($CONFIG['article_database'] == '1') { $rawmessage = np_get_db_article($id, $group, 1); - $current_article = $rawmessage; } else { $articlepath = $spoolpath . preg_replace('/\./', '/', $group) . "/" . $id; if (file_exists($articlepath)) { @@ -768,9 +767,8 @@ function message_show($group, $id, $attachment = 0, $article_data = false, $maxl global $text_header, $text_article, $article_showthread, $file_attachment, $attachment_show; global $block_xnoarchive, $article_graphicquotes; global $CONFIG, $current_message; - if (! isset($current_message)) { - $current_message = np_get_db_article($id, $group, 1); - } + $current_message = np_get_db_article($id, $group, 1); + if ($article_data == false) $article_data = message_read($id, $attachment, $group); $head = $article_data->header; From e720f3729ce46e5f183c41f195aa6932b7bd0d51 Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Tue, 21 May 2024 05:39:45 -0700 Subject: [PATCH 05/11] Add feature to block clients by content of user agent string. --- Rocksolid_Light/rocksolid/newsportal.php | 5885 +++++++++++----------- 1 file changed, 2950 insertions(+), 2935 deletions(-) diff --git a/Rocksolid_Light/rocksolid/newsportal.php b/Rocksolid_Light/rocksolid/newsportal.php index 7c7760d..8d2b511 100644 --- a/Rocksolid_Light/rocksolid/newsportal.php +++ b/Rocksolid_Light/rocksolid/newsportal.php @@ -1,2939 +1,2954 @@ -HTTP Gateway - * Download: https://news.novabbs.com/get - * - * Based on Newsportal by Florian Amrhein - * - * E-Mail: retroguy@novabbs.com - * Web: https://news.novabbs.com - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -if (file_exists("lib/types.inc.php")) - include "lib/types.inc.php"; -if (file_exists("lib/thread.inc.php")) - include "lib/thread.inc.php"; -if (file_exists("lib/message.inc.php")) - include "lib/message.inc.php"; -if (file_exists("lib/post.inc.php")) - include "lib/post.inc.php"; - -$CONFIG = include ($config_file); - -/* - * opens the connection to the NNTP-Server - * - * $server: adress of the NNTP-Server - * $port: port of the server - */ -function nntp_open($nserver = 0, $nport = 0) -{ - global $text_error, $CONFIG; - global $server, $port; - - // echo "
NNTP OPEN
"; - if (! isset($CONFIG['enable_nntp']) || $CONFIG['enable_nntp'] != true) { - $CONFIG['server_auth_user'] = $CONFIG['remote_auth_user']; - $CONFIG['server_auth_pass'] = $CONFIG['remote_auth_pass']; - } - $authorize = ((isset($CONFIG['server_auth_user'])) && (isset($CONFIG['server_auth_pass'])) && ($CONFIG['server_auth_user'] != "")); - if ($nserver == 0) - $nserver = $server; - if ($nport == 0) - $nport = $port; - $ns = @fsockopen($nserver, $nport); - - // if the connection to the news server fails, inform the user and stop processing. - if ($ns == false) { - echo '

' . $text_error["error:"] . " " . $text_error["connection_failed"] . '.

'; - echo '
'; - echo '

Please wait a few moments and try again. If you see the same error, notify the owner that their Message Server is offline.

'; - echo '
'; - return false; - // exit(0); - } - - $weg = line_read($ns); // kill the first line - if (substr($weg, 0, 2) != "20") { - echo "

" . $text_error["error:"] . $weg . "

"; - fclose($ns); - $ns = false; - } else { - if ($ns != false) { - fputs($ns, "MODE reader\r\n"); - $weg = line_read($ns); // and once more - if ((substr($weg, 0, 2) != "20") && ((! $authorize) || ((substr($weg, 0, 3) != "480") && ($authorize)))) { - echo "

" . $text_error["error:"] . $weg . "

"; - fclose($ns); - $ns = false; - } - } - if ((isset($CONFIG['server_auth_user'])) && (isset($CONFIG['server_auth_pass'])) && ($CONFIG['server_auth_user'] != "")) { - fputs($ns, "AUTHINFO USER " . $CONFIG['server_auth_user'] . "\r\n"); - $weg = line_read($ns); - fputs($ns, "AUTHINFO PASS " . $CONFIG['server_auth_pass'] . "\r\n"); - $weg = line_read($ns); - /* Only check auth if reading and posting same server */ - // NNTP Response NOT 281 (Authorization failed) - if (substr($weg, 0, 3) != "281" && ! (isset($post_server)) && ($post_server != "")) { - echo "

" . $text_error["error:"] . "

"; - echo "

" . $text_error["auth_error"] . "

"; - } - } - } - if ($ns == false) - echo "

" . $text_error["connection_failed"] . "

"; - return $ns; -} - -function nntp2_open($nserver = 0, $nport = 0) -{ - global $text_error, $CONFIG; - - $authorize = ((isset($CONFIG['remote_auth_user'])) && (isset($CONFIG['remote_auth_pass'])) && ($CONFIG['remote_auth_user'] != "")); - if ($nserver == 0) - $nserver = $CONFIG['remote_server']; - if ($nport == 0) - $nport = $CONFIG['remote_port']; - if ($CONFIG['remote_ssl']) { - if ($nport == $CONFIG['remote_port']) { - $nport = $CONFIG['remote_ssl']; - } - $ns = fsockopen("ssl://" . $nserver, $nport, $error, $errorString, 30); - if (! $ns) { - return false; - } - } else { - if (isset($CONFIG['socks_host']) && $CONFIG['socks_host'] !== '') { - $ns = fsocks4asockopen($CONFIG['socks_host'], $CONFIG['socks_port'], $nserver, $nport); - } else { - $ns = @fsockopen('tcp://' . $nserver . ":" . $nport); - } - } - // $ns=@fsockopen($nserver,$nport); - // echo "PORT: ".$nport." ns: ".$ns; - $weg = line_read($ns); // kill the first line - if (substr($weg, 0, 2) != "20") { - echo "

" . $text_error["error:"] . $weg . "

"; - if ($ns) { - fclose($ns); - } - $ns = false; - } else { - if ($ns != false) { - fputs($ns, "MODE reader\r\n"); - $weg = line_read($ns); // and once more - if ((substr($weg, 0, 2) != "20") && ((! $authorize) || ((substr($weg, 0, 3) != "480") && ($authorize)))) { - echo "

" . $text_error["error:"] . $weg . "

"; - fclose($ns); - $ns = false; - } - } - if ((isset($CONFIG['remote_auth_user'])) && (isset($CONFIG['remote_auth_pass'])) && ($CONFIG['remote_auth_user'] != "")) { - fputs($ns, "AUTHINFO USER " . $CONFIG['remote_auth_user'] . "\r\n"); - $weg = line_read($ns); - fputs($ns, "AUTHINFO PASS " . $CONFIG['remote_auth_pass'] . "\r\n"); - $weg = line_read($ns); - /* Only check auth if reading and posting same server */ - if (substr($weg, 0, 3) != "281" && ! (isset($post_server)) && ($post_server != "")) { - echo "

" . $text_error["error:"] . "

"; - echo "

" . $text_error["auth_error"] . "

"; - } - } - } - if ($ns == false) - echo "

" . $text_error["connection_failed"] . "

"; - return $ns; -} - -function fsocks4asockopen($proxyHostname, $proxyPort, $targetHostname, $targetPort) -{ - $sock = fsockopen($proxyHostname, $proxyPort); - if ($sock === false) - return false; - fwrite($sock, pack("CCnCCCCC", 0x04, 0x01, $targetPort, 0x00, 0x00, 0x00, 0x01, 0x00) . $targetHostname . pack("C", 0x00)); - $response = fread($sock, 16); - $values = unpack("xnull/Cret/nport/Nip", $response); - if ($values["ret"] == 0x5a) - return $sock; - else { - fclose($sock); - return false; - } -} - -/* - * Close a NNTP connection - * - * $ns: the handle of the connection - */ -function nntp_close(&$ns) -{ - if ($ns != false) { - fputs($ns, "QUIT\r\n"); - fclose($ns); - } -} - -/* - * Validates an email adress - * - * $address: a string containing the email-address to be validated - * - * returns true if the address passes the tests, false otherwise. - */ -function validate_email($address) -{ - global $validate_email; - $return = true; - if (($validate_email >= 1) && ($return == true)) -/* Need to clean up this regex to work properly with preg_match - $return = (preg_match('^[-!#$%&\'*+\\./0-9=?A-Z^_A-z{|}~]+'.'@'. - '[-!#$%&\'*+\\/0-9=?A-Z^_A-z{|}~]+\.'. - '[-!#$%&\'*+\\./0-9=?A-Z^_A-z{|}~]+$',$address)); -*/ - $return = 1; - if (($validate_email >= 2) && ($return == true)) { - $addressarray = address_decode($address, "garantiertungueltig"); - $return = checkdnsrr($addressarray[0]["host"], "MX"); - if (! $return) - $return = checkdnsrr($addressarray[0]["host"], "A"); - } - return ($return); -} - -/* - * decodes a block of 7bit-data in uuencoded format to it's original - * 8bit format. - * The headerline containing filename and permissions doesn't have to - * be included. - * - * $data: The uuencoded data as a string - * - * returns the 8bit data as a string - * - * Note: this function is very slow and doesn't recognize incorrect code. - */ -function uudecode_line($line) -{ - $data = substr($line, 1); - $length = ord($line[0]) - 32; - $decoded = ""; - for ($i = 0; $i < (strlen($data) >> 2); $i ++) { - $pack = substr($data, $i << 2, 4); - $upack = ""; - $bitmaske = 0; - for ($o = 0; $o < 4; $o ++) { - $g = ((ord($pack[3 - $o]) - 32)); - if ($g == 64) - $g = 0; - $bitmaske = $bitmaske | ($g << (6 * $o)); - } - $schablone = 255; - for ($o = 0; $o < 3; $o ++) { - $c = ($bitmaske & $schablone) >> ($o << 3); - $schablone = ($schablone << 8); - $upack = chr($c) . $upack; - } - $decoded .= $upack; - } - $decoded = substr($decoded, 0, $length); - return $decoded; -} - -/* - * decodes uuencoded Attachments. - * - * $data: the encoded data - * - * returns the decoded data - */ -function uudecode($data) -{ - $d = explode("\n", $data); - $u = ""; - for ($i = 0; $i < count($d) - 1; $i ++) - $u .= uudecode_line($d[$i]); - return $u; -} - -/* - * returns the mimetype of an filename - * - * $name: the complete filename of a file - * - * returns a string containing the mimetype - */ -function get_mimetype_by_filename($name) -{ - $ending = strtolower(strrchr($name, ".")); - switch ($ending) { - case ".jpg": - case ".jpeg": - $type = "image/jpeg"; - break; - case ".gif": - $type = "image/gif"; - break; - case ".png": - $type = "image/png"; - break; - case ".bmp": - $type = "image/bmp"; - break; - default: - $type = "text/plain"; - } - return $type; -} - -function get_mimetype_by_string($filedata) -{ - if (function_exists('finfo_open')) { - $f = finfo_open(); - return finfo_buffer($f, $filedata, FILEINFO_MIME_TYPE); - } else { - return false; - } -} - -/* - * Test, if the access to a group is allowed. This is true, if $testgroup is - * false or the groupname is in groups.txt - * - * $groupname: name of the group to be checked - * - * returns true, if access is allowed - */ -function testGroup($groupname) -{ - global $CONFIG, $testgroup, $file_groups, $config_dir; - $groupname = strtolower($groupname); - if ($testgroup) { - $gf = fopen($file_groups, "r"); - while (! feof($gf)) { - $read = trim(line_read($gf)); - $read = preg_replace('/\t/', ' ', $read); - $read = strtolower($read); - $pos = strpos($read, " "); - if ($pos != false) { - if (substr($read, 0, $pos) == trim($groupname)) - return true; - } else { - if ($read == trim($groupname)) - return true; - } - } - fclose($gf); - if ($groupname == $CONFIG['spamgroup']) { - return true; - } else { - /* Find section */ - $menulist = file($config_dir . "menu.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - foreach ($menulist as $menu) { - if ($menu[0] == '#') { - continue; - } - $menuitem = explode(':', $menu); - if ($menuitem[1] == '0') { - continue; - } - $glfp = fopen($config_dir . $menuitem[0] . "/groups.txt", 'r'); - $section = ""; - while ($gl = fgets($glfp)) { - $group_name = preg_split("/( |\t)/", $gl, 2); - if (stripos(trim($groupname), trim($group_name[0])) !== false) { - fclose($glfp); - return true; - } - } - } - fclose($glfp); - return false; - } - } else { - return true; - } -} - -function get_section_by_group($groupname, $all_sections = false) -{ - global $CONFIG, $config_dir; - $menulist = file($config_dir . "menu.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - // Get first group in Newsgroups - $groupname = preg_split("/( |\,)/", $groupname, 2); - $groupname = $groupname[0]; - foreach ($menulist as $menu) { - if ($menu[0] == '#') { - continue; - } - $menuitem = explode(':', $menu); - if ($menuitem[1] == '0') { - if (! $all_sections) { - continue; - } - } - $section = ""; - $gldata = file($config_dir . $menuitem[0] . "/groups.txt"); - foreach ($gldata as $gl) { - $group_name = preg_split("/( |\t)/", $gl, 2); - if (strtolower(trim($groupname)) == strtolower(trim($group_name[0]))) { - $section = $menuitem[0]; - return $section; - } - } - } - return false; -} - -function testGroups($newsgroups) -{ - $groups = explode(",", $newsgroups); - $count = count($groups); - $return = ""; - $o = 0; - for ($i = 0; $i < $count; $i ++) { - if (testgroup($groups[$i]) && (! function_exists("npreg_group_has_write_access") || npreg_group_has_write_access($groups[$i]))) { - if ($o > 0) - $return .= ","; - $o ++; - $return .= $groups[$i]; - } - } - return ($return); -} - -/* - * read one line from the NNTP-server - */ -function line_read(&$ns) -{ - if ($ns != false) { - $t = str_replace("\n", "", str_replace("\r", "", fgets($ns, 1200))); - return $t; - } -} - -/* - * Split an internet-address string into its parts. An address string could - * be for example: - * - user@host.domain (Realname) - * - "Realname" - * - user@host.domain - * - * The address will be split into user, host (incl. domain) and realname - * - * $adrstring: The string containing the address in internet format - * $defaulthost: The name of the host which should be returned if the - * address-string doesn't contain a hostname. - * - * returns an hash containing the fields "mailbox", "host" and "personal" - */ -function address_decode($adrstring, $defaulthost) -{ - $parsestring = trim($adrstring); - $len = strlen($parsestring); - $at_pos = strpos($parsestring, '@'); // find @ - $ka_pos = strpos($parsestring, "("); // find ( - $kz_pos = strpos($parsestring, ')'); // find ) - $ha_pos = strpos($parsestring, '<'); // find < - $hz_pos = strpos($parsestring, '>'); // find > - $space_pos = strpos($parsestring, ')'); // find ' ' - $email = ""; - $mailbox = ""; - $host = ""; - $personal = ""; - if ($space_pos != false) { - if (($ka_pos != false) && ($kz_pos != false)) { - $personal = substr($parsestring, $ka_pos + 1, $kz_pos - $ka_pos - 1); - $email = trim(substr($parsestring, 0, $ka_pos - 1)); - } - } else { - $email = $adrstring; - } - if (($ha_pos != false) && ($hz_pos != false)) { - $email = trim(substr($parsestring, $ha_pos + 1, $hz_pos - $ha_pos - 1)); - $personal = substr($parsestring, 0, $ha_pos - 1); - } - if ($at_pos != false) { - $mailbox = substr($email, 0, strpos($email, '@')); - $host = substr($email, strpos($email, '@') + 1); - } else { - $mailbox = $email; - $host = $defaulthost; - } - $personal = trim($personal); - if (substr($personal, 0, 1) == '"') - $personal = substr($personal, 1); - if (substr($personal, strlen($personal) - 1, 1) == '"') - $personal = substr($personal, 0, strlen($personal) - 1); - $result["mailbox"] = trim($mailbox); - $result["host"] = trim($host); - if ($personal != "") - $result["personal"] = $personal; - $complete[] = $result; - return ($complete); -} - -/* - * Read the groupnames from groups.txt, and get additional informations - * of the groups from the newsserver - * - * when load=0, returns cached group list - * when load=1, checks if the cache should be used, and returns nothing - * when force_reload=true, rebuilds group list cache - */ -function groups_read($server, $port, $load = 0, $force_reload = false) -{ - global $gl_age, $file_groups, $spooldir, $config_name, $cache_index; - // is there a cached version, and is it actual enough? - $cachefile = $spooldir . '/' . $config_name . '-groups.dat'; - // if cache is new enough, don't recreate it - clearstatcache(TRUE, $cachefile); - if (! $force_reload && $load == 1 && file_exists($cachefile) && (filemtime($cachefile) + $cache_index > time())) { - return; - } - if (! $force_reload && file_exists($cachefile) && $load == 0) { - // cached file exists and is new enough, so lets read it out. - $file = fopen($cachefile, "r"); - $data = ""; - while (! feof($file)) { - $data .= fgets($file, 1000); - } - fclose($file); - $newsgroups = unserialize($data); - } else { - // force a refresh of the group list - $ns = nntp_open($server, $port); - if ($ns == false) - return false; - // $gf=fopen($file_groups,"r"); - $gfdata = file($file_groups); - // if we want to mark groups with new articles with colors, we will later - // need the format of the overview - $overviewformat = thread_overview_read($ns); - foreach ($gfdata as $gf) { - $gruppe = new newsgroupType(); - $tmp = preg_replace('/\t/', ' ', trim($gf)); - if (substr($tmp, 0, 1) == ":") { - $gruppe->text = substr($tmp, 1); - $newsgroups[] = $gruppe; - } elseif (strlen($tmp) > 0) { - // is there a description in groups.txt? - $gr = explode(" ", $tmp, 2); - if (isset($gr[1])) { // Yes - $gruppe->name = $gr[0]; - $desc = $gr[1]; - } else { // No - // no, get it from the newsserver. - $gruppe->name = $tmp; - if (is_file($spooldir . '/' . $tmp . '-title')) { - $response = file_get_contents($spooldir . '/' . $tmp . '-title'); - $desc = strrchr($response, "\t"); - } else { - $desc = "-"; - } - } - if (strcmp($desc, "") == 0) - $desc = "-"; - $gruppe->description = $desc; - fputs($ns, "GROUP " . $gruppe->name . "\r\n"); - $t = explode(" ", line_read($ns)); - - if ($t[0] == "211") - $gruppe->count = $t[1]; - else { - nntp_close($ns); - $ns = nntp_open($server, $port); - if ($ns == false) - return false; - fputs($ns, "GROUP " . $gruppe->name . "\r\n"); - $t = explode(" ", line_read($ns)); - if ($t[0] == "211") - $gruppe->count = $t[1]; - else - continue; - } - // mark group with new articles with colors - if ($gl_age) { - fputs($ns, 'XOVER ' . $t[3] . "\r\n"); - $tmp = explode(" ", line_read($ns)); - if ($tmp[0] == "224") { - $tmp = line_read($ns); - if ($tmp != ".") { - $head = thread_overview_interpret($tmp, $overviewformat, $gruppe->name); - $tmp = line_read($ns); - $gruppe->age = $head->date; - } - } - } - if ((strcmp(trim($gruppe->name), "") != 0) && (substr($gruppe->name, 0, 1) != "#")) - $newsgroups[] = $gruppe; - } - } - nntp_close($ns); - // write the data to the cachefile - file_put_contents($cachefile, serialize($newsgroups)); - } - if ($load == 0) { - return $newsgroups; - } else { - return; - } -} - -function groups_show($gruppen) -{ - global $gl_age, $frame, $spooldir, $config_dir, $logdir, $CONFIG, $OVERRIDES, $spoolnews; - if ($gruppen == false) - return; - global $file_thread, $text_groups; - $logfile = $logdir . '/debug.log'; - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - $c = count($gruppen); - $acttype = "keins"; - echo ''; - $subs = array(); - $nonsubs = array(); - $user = null; - // Get registered user settings - if (isset($_COOKIE['mail_name'])) { - if ($userdata = get_user_mail_auth_data($_COOKIE['mail_name'])) { - $userfile = $spooldir . '/' . strtolower($_COOKIE['mail_name']) . '-articleviews.dat'; - $user_config = unserialize(file_get_contents($config_dir . '/userconfig/' . strtolower($_COOKIE['mail_name']) . '.config')); - } - } - for ($i = 0; $i < $c; $i ++) { - unset($groupdisplay); - $g = $gruppen[$i]; - if (isset($g->text)) { - if ($acttype != "text") { - $acttype = "text"; - } - } else { - if ($acttype != "group") { - $acttype = "group"; - } - if (! isset($userdata[$g->name])) { - if (isset($user_config['hide_unsub']) && $user_config['hide_unsub'] == 'hide') { - continue; - } - } - unset($lastarticleinfo); - $found = 0; - // Get last article info from article database - // First check memcache - if ($memcacheD) { - $lar_memcache = $memcache_key_prefix . '_' . 'lastarticleinfo-' . $g->name; - $groupfile = $spooldir . '/' . $g->name . '-lastarticleinfo.dat'; - if ($lastarticleinfo = unserialize($memcacheD->get($lar_memcache))) { - if ($lastarticleinfo && file_exists($groupfile) && filemtime($groupfile) <= $lastarticleinfo['date']) { - if ($enable_memcache_logging) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . ' (cache hit) lastarticleinfo for ' . $g->name, FILE_APPEND); - } - $found = 1; - } else { - unset($lastarticleinfo); - } - } - } - if (! isset($lastarticleinfo['date'])) { - if ($CONFIG['article_database'] == '1') { - $database = $spooldir . '/' . $g->name . '-articles.db3'; - $article_dbh = article_db_open($database); - $article_query = $article_dbh->prepare('SELECT * FROM articles ORDER BY CAST(date AS int) DESC LIMIT 5'); - $article_query->execute(); - while ($row = $article_query->fetch()) { - if ($row['date'] > time()) { - continue; - } - $found = 1; - break; - } - $article_dbh = null; - } else { - $database = $spooldir . '/articles-overview.db3'; - $overview_dbh = overview_db_open($database); - $overview_query = $overview_dbh->prepare('SELECT * FROM overview WHERE newsgroup=:newsgroup ORDER BY CAST(date AS int) DESC LIMIT 5'); - $overview_query->execute([ - 'newsgroup' => $g->name - ]); - while ($row = $overview_query->fetch()) { - if ($row['date'] > time()) { - continue; - } - $found = 1; - break; - } - $overview_dbh = null; - } - if ($found == 1) { - $lastarticleinfo = $row; - if ($memcacheD) { - touch($groupfile, $lastarticleinfo['date']); - $nicole = $memcacheD->delete($lar_memcache); - $memcacheD->add($lar_memcache, serialize($row), $memcache_ttl); - if ($enable_memcache_logging) { - if ($nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache update) $lar_memcache", FILE_APPEND); - } else { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $lar_memcache", FILE_APPEND); - } - } - } - } - } - $new = false; - $new_style_on = ''; - $new_style_off = ''; - if (isset($userdata[$g->name]) && ($userdata[$g->name] < $lastarticleinfo['date'])) { - $new_style_on = '*'; - $new_style_off = ''; - $new = true; - } - /* Display group name and description */ - if (isset($userdata[$g->name])) { - $lineclass = "np_thread_line2"; - } else { - $lineclass = "np_thread_line1"; - } - if ($new) { - $latest_link = '&time=' . $userdata[$g->name]; - } else { - $latest_link = ''; - } - $groupdisplay = ''; - - $groupdisplay .= '
'; - echo 'LatestNewsgroupMessagesLast Message
'; - $groupdisplay .= ''; - if ((isset($_SESSION['theme'])) && file_exists('../common/themes/' . $_SESSION['theme'] . '/images/latest.png')) { - $latest_image = '../common/themes/' . $_SESSION['theme'] . '/images/latest.png'; - } else { - $latest_image = '../common/images/latest.png'; - } - if ($new) { - if ((isset($_SESSION['theme'])) && file_exists('../common/themes/' . $_SESSION['theme'] . '/images/new-articles.png')) { - $latest_image = '../common/themes/' . $_SESSION['theme'] . '/images/new-articles.png'; - } else { - $latest_image = '../common/images/new-articles.png'; - } - $groupdisplay .= ''; - } else { - $groupdisplay .= ''; - } - $groupdisplay .= ''; - $groupdisplay .= ''; - $groupdisplay .= ''; - $groupdisplay .= '' . $new_style_on . group_display_name($g->name) . $new_style_off . "\n"; - if ($new) { - echo ''; - } - if ($g->description != "-") { - $groupdisplay .= '

' . $g->description . '

'; - } - if (isset($userdata[$g->name])) { - $groupdisplay .= '

'; - $groupdisplay .= '(unsubscribe)'; - if ($new) { - $groupdisplay .= '(mark read)'; - } - $groupdisplay .= ''; - if ($gl_age) - $datecolor = thread_format_date_color($g->age); - $groupdisplay .= ''; - if ($datecolor != "") - $groupdisplay .= '' . $g->count . ''; - else - $groupdisplay .= $g->count; - $groupdisplay .= ''; - - /* Display latest article info */ - $groupdisplay .= '

'; - - if ($found == 1) { - $poster = address_decode($lastarticleinfo['name'], "nowhere"); - $lastarticleinfo['from'] = $poster[0]['mailbox'] . "@" . $poster[0]['host']; - if (isset($poster[0]['personal'])) { - $lastarticleinfo['name'] = $poster[0]['personal']; - } else { - $lastarticleinfo['name'] = $poster[0]['mailbox']; - } - $fromoutput[0] = $poster[0]['mailbox'] . "@" . $poster[0]['host']; - $groupdisplay .= get_date_interval(date("D, j M Y H:i T", $lastarticleinfo['date'])); - $groupdisplay .= '
'; - $groupdisplay .= 'by: '; - $groupdisplay .= create_name_link(mb_decode_mimeheader(html_entity_decode($lastarticleinfo['name'])), $lastarticleinfo['from']); - $groupdisplay .= '
'; - } else { - unset($lastarticleinfo); - } - } - if (isset($groupdisplay)) { - $groupdisplay .= "\n"; - flush(); - if (isset($userdata[$g->name])) { - $subs[] = $groupdisplay; - } else { - $nonsubs[] = $groupdisplay; - } - } - } - foreach ($subs as $sub) { - echo $sub; - } - foreach ($nonsubs as $nonsub) { - echo $nonsub; - } - echo "
"; - echo ''; - echo '
'; - if (isset($user_config['hide_unsub']) && $user_config['hide_unsub'] == 'hide') { - echo ' Unsubscribed groups are HIDDEN.'; - echo ' Select groups from Grouplist to add groups'; - } - if (isset($userdata)) { - show_groups_hide_toggle(); - } - echo '
'; -} - -function show_groups_hide_toggle() -{ - global $user_config; - echo '
'; - echo ' Unsubscribed Groups: '; - if ($user_config['hide_unsub'] == 'hide') { - echo 'Show'; - echo ' '; - echo 'Hide'; - echo ' '; - } else { - echo 'Show'; - echo ' '; - echo 'Hide'; - echo ' '; - } - echo ''; - echo '
'; -} - -/* - * print the group names from an array to the webpage - */ -function groups_show_frames($gruppen) -{ - global $gl_age, $frame, $spooldir; - if ($gruppen == false) - return; - global $file_thread, $text_groups; - $c = count($gruppen); - echo '
'; - $acttype = "keins"; - for ($i = 0; $i < $c; $i ++) { - $g = $gruppen[$i]; - if (isset($g->text)) { - if ($acttype != "text") { - $acttype = "text"; - if ($i > 0) - echo '
'; - echo '
'; - } - echo $g->text; - } else { - if ($acttype != "group") { - $acttype = "group"; - if ($i > 0) - echo '
'; - echo '
'; - } - echo '
'; - echo '' . group_display_name($g->name) . "\n"; - if ($gl_age) - $datecolor = thread_format_date_color($g->age); - echo '('; - if ($datecolor != "") - echo '' . $g->count . ''; - else - echo $g->count; - echo ')'; - if ($g->description != "-") - echo '
' . $g->description . ''; - echo '
'; - } - echo "\n"; - flush(); - } - echo "
\n"; -} - -/* - * gets a list of available articles in the group $groupname - */ -/* - * function getArticleList(&$ns,$groupname) { - * fputs($ns,"LISTGROUP $groupname \r\n"); - * $line=line_read($ns); - * $line=line_read($ns); - * while(strcmp($line,".") != 0) { - * $articleList[] = trim($line); - * $line=line_read($ns); - * } - * if (!isset($articleList)) $articleList="-"; - * return $articleList; - * } - */ - -/* - * Decode quoted-printable or base64 encoded headerlines - * - * $value: The to be decoded line - * - * returns the decoded line - */ -function headerDecode($value) -{ - $value = preg_replace_callback('/(=\?[^\?]+\?Q\?)([^\?]+)(\?=)/i', function ($matches) { - return $matches[1] . str_replace('_', '=20', $matches[2]) . $matches[3]; - }, $value); - return mb_decode_mimeheader($value); -} - -/* - * calculates an Unix timestamp out of a Date-Header in an article - * - * $value: Value of the Date: header - * - * returns an Unix timestamp - */ -function getTimestamp($value) -{ - global $CONFIG; - - return strtotime($value); -} - -function parse_header($hdr, $number = "") -{ - for ($i = count($hdr) - 1; $i > 0; $i --) - if (preg_match("/^(\x09|\x20)/", $hdr[$i])) - $hdr[$i - 1] = $hdr[$i - 1] . " " . ltrim($hdr[$i]); - $header = new headerType(); - $header->isAnswer = false; - for ($count = 0; $count < count($hdr); $count ++) { - $variable = substr($hdr[$count], 0, strpos($hdr[$count], " ")); - $value = trim(substr($hdr[$count], strpos($hdr[$count], " ") + 1)); - switch (strtolower($variable)) { - case "from:": - $fromline = address_decode(headerDecode($value), "nowhere"); - if (! isset($fromline[0]["host"])) - $fromline[0]["host"] = ""; - $header->from = $fromline[0]["mailbox"] . "@" . $fromline[0]["host"]; - $header->username = $fromline[0]["mailbox"]; - if (! isset($fromline[0]["personal"])) { - $header->name = ""; - } else { - $header->name = $fromline[0]["personal"]; - } - break; - case "message-id:": - $header->id = $value; - break; - case "subject:": - $header->subject = headerDecode($value); - break; - case "newsgroups:": - $header->newsgroups = $value; - break; - case "organization:": - $header->organization = headerDecode($value); - break; - case "content-transfer-encoding:": - $header->content_transfer_encoding = trim(strtolower($value)); - break; - case "content-disposition:": - $getname = preg_split("/name\=/", $value, 2); - if (isset($getname[1])) { - $header->content_type_name = array( - $getname[1] - ); - } - break; - case "content-type:": - $header->content_type = array(); - $subheader = explode(";", $value); - $header->content_type[0] = strtolower(trim($subheader[0])); - for ($i = 1; $i < count($subheader); $i ++) { - $gleichpos = strpos($subheader[$i], "="); - if ($gleichpos) { - $subvariable = trim(substr($subheader[$i], 0, $gleichpos)); - $subvalue = trim(substr($subheader[$i], $gleichpos + 1)); - if (($subvalue[0] == '"') && ($subvalue[strlen($subvalue) - 1] == '"')) - $subvalue = substr($subvalue, 1, strlen($subvalue) - 2); - switch ($subvariable) { - case "charset": - $header->content_type_charset = array( - strtolower($subvalue) - ); - break; - case "name": - $header->content_type_name = array( - $subvalue - ); - break; - case "boundary": - $header->content_type_boundary = $subvalue; - break; - case "format": - $header->content_type_format = array( - $subvalue - ); - } - } - } - break; - case "references:": - $ref = trim($value); - while (strpos($ref, "> <") != false) { - $header->references[] = substr($ref, 0, strpos($ref, " ")); - $ref = substr($ref, strpos($ref, "> <") + 2); - } - $header->references[] = trim($ref); - break; - case "date:": - $header->date = getTimestamp(trim($value)); - break; - case "followup-to:": - $header->followup = trim($value); - break; - case "x-newsreader:": - case "x-mailer:": - case "x-rslight-to:": - $header->rslight_to = trim($value); - break; - case "x-rslight-site:": - $header->rslight_site = trim($value); - break; - case "user-agent:": - $header->user_agent = trim($value); - break; - case "x-face:": // not ready - // echo "

-".base64_decode($value)."-

"; - break; - case "x-no-archive:": - $header->xnoarchive = strtolower(trim($value)); - } - } - if (! isset($header->content_type[0])) - $header->content_type[0] = "text/plain"; - if (! isset($header->content_transfer_encoding)) - $header->content_transfer_encoding = "8bit"; - if ($number != "") - $header->number = $number; - return $header; -} - -/* - * convert the charset of a text - */ -function recode_charset($text, $source = false, $dest = false) -{ - global $iconv_enable, $www_charset; - if ($dest == false) - $dest = $www_charset; - if (($iconv_enable) && ($source != false)) { - $return = iconv($source, $dest . "//TRANSLIT", $text); - if ($return != "") - return $return; - else - return $text; - } else { - return $text; - } -} - -function decode_body($body, $encoding) -{ - $bodyzeile = ""; - switch ($encoding) { - case "base64": - $body = base64_decode($body); - break; - case "quoted-printable": - $body = Quoted_printable_decode($body); - $body = str_replace("=\n", "", $body); - // default: - // $body=str_replace("\n..\n","\n.\n",$body); - } - - return $body; -} - -/* - * makes URLs clickable - * - * $text: A text-line probably containing links. - * - * the function returns the text-line with HTML-Links to the links or - * email-adresses. - */ -function html_parse($text) -{ - global $frame_externallink; - if ((isset($frame_externallink)) && ($frame_externallink != "")) { - $target = ' TARGET="' . $frame_externallink . '" '; - } else { - $target = ' '; - } - $ntext = ""; - // split every line into it's words - $words = explode(" ", $text); - $n = count($words); - $is_link = 0; - for ($i = 0; $i < $n; $i ++) { - $word = $words[$i]; - // add the spaces between the words - if ($i > 0) - $ntext .= " "; - $ntext .= $word; - } - return ($ntext); -} - -function display_links_in_body($text) -{ - global $config_dir; - preg_match_all('/(https?|ftp|scp|news|gopher|gemini|telnet):\/\/[a-zA-Z0-9.?%=\-\+\;\:\,\~\@\!\(\)\$\#&_\/]+/', $text, $matches); - $found = array(); - $isquote = false; - if (strpos($text, ">") == 0) { - $isquote = true; - echo '
'; - } - foreach ($matches[0] as $match) { - if (! $match) { - continue; - } - if (in_array($match, $found)) { - continue; - } - $found[] = $match; - $linkurl = preg_replace("/(<|>)/", ' ', htmlspecialchars_decode($match)); - $url = preg_replace("/(<|>)/", ' ', $match); - $pattern = preg_quote($url); - $pattern = "!$pattern!"; - $text = preg_replace($pattern, '' . $url . '', $text, 1); - } - if (file_exists($config_dir . '/rewrite_body.inc.php')) { - include ($config_dir . '/rewrite_body.inc.php'); - } - - echo $text; - if ($isquote) { - echo '
'; - } -} - -/* - * read the header of an article in plaintext into an array - * $articleNumber can be the number of an article or its message-id. - */ -function readPlainHeader(&$ns, $group, $articleNumber) -{ - fputs($ns, "GROUP $group\r\n"); - $line = line_read($ns); - fputs($ns, "HEAD $articleNumber\r\n"); - $line = line_read($ns); - if (substr($line, 0, 3) != "221") { - echo $text_error["article_not_found"]; - $header = false; - } else { - $line = line_read($ns); - $body = ""; - while (strcmp(trim($line), ".") != 0) { - $body .= $line . "\n"; - $line = line_read($ns); - } - return explode("\n", str_replace("\r\n", "\n", $body)); - } -} - -/* - * cancel an article on the newsserver - * - * DO NOT USE THIS FUNCTION, IF YOU DON'T KNOW WHAT YOU ARE DOING! - * - * $ns: The handler of the NNTP-Connection - * $group: The group of the article - * $id: the Number of the article inside the group or the message-id - */ -function message_cancel($subject, $from, $newsgroups, $ref, $body, $id) -{ - global $server, $port, $send_poster_host, $CONFIG, $text_error; - global $www_charset; - flush(); - $ns = nntp_open($server, $port); - if ($ns != false) { - fputs($ns, "POST\r\n"); - $weg = line_read($ns); - fputs($ns, 'Subject: ' . quoted_printable_encode($subject) . "\r\n"); - fputs($ns, 'From: ' . $from . "\r\n"); - fputs($ns, 'Newsgroups: ' . $newsgroups . "\r\n"); - fputs($ns, "Mime-Version: 1.0\r\n"); - fputs($ns, "Content-Type: text/plain; charset=" . $www_charset . "\r\n"); - fputs($ns, "Content-Transfer-Encoding: 8bit\r\n"); - if ($send_poster_host) - fputs($ns, 'X-HTTP-Posting-Host: ' . gethostbyaddr(getenv("REMOTE_ADDR")) . "\r\n"); - if ($ref != false) - fputs($ns, 'References: ' . $ref . "\r\n"); - if (isset($CONFIG['organization'])) - fputs($ns, 'Organization: ' . quoted_printable_encode($CONFIG['organization']) . "\r\n"); - fputs($ns, "Control: cancel " . $id . "\r\n"); - $body = str_replace("\n.\r", "\n..\r", $body); - $body = str_replace("\r", '', $body); - $b = explode("\n", $body); - $body = ""; - for ($i = 0; $i < count($b); $i ++) { - if ((strpos(substr($b[$i], 0, strpos($b[$i], " ")), ">") != false) | (strcmp(substr($b[$i], 0, 1), ">") == 0)) { - $body .= textwrap(stripSlashes($b[$i]), 78, "\r\n") . "\r\n"; - } else { - $body .= textwrap(stripSlashes($b[$i]), 74, "\r\n") . "\r\n"; - } - } - fputs($ns, "\r\n" . $body . "\r\n.\r\n"); - $message = line_read($ns); - nntp_close($ns); - } else { - $message = $text_error["post_failed"]; - } - return $message; -} - -function rslight_encrypt($data, $key) -{ - $encryption_key = base64_decode($key); - $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); - $encrypted = openssl_encrypt($data, 'aes-256-cbc', $encryption_key, 0, $iv); - return base64_encode($encrypted . '::' . $iv); -} - -function _rawurlencode($string) -{ - $string = rawurlencode(str_replace('+', '%2B', $string)); - return $string; -} - -function _rawurldecode($string) -{ - $string = rawurldecode(str_replace('%2B', '+', $string)); - return $string; -} - -function rslight_decrypt($data, $key) -{ - $encryption_key = base64_decode($key); - list ($encrypted_data, $iv) = explode('::', base64_decode($data), 2); - return openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv); -} - -function group_display_name($gname) -{ - global $config_dir; - $namelist = file($config_dir . "rename.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - foreach ($namelist as $name) { - if ($name[0] == '#') { - continue; - } - $nameitem = explode(':', $name); - if (! strcmp(trim($nameitem[0]), trim($gname))) { - return $nameitem[1]; - } - } - return $gname; -} - -function check_bbs_auth($username, $password) -{ - global $config_dir, $spooldir, $CONFIG; - - $logfile = $spooldir . '/log/auth.log'; - if ($username == '' && $password == '') { - return false; - } - - $workpath = $config_dir . "users/"; - $username = trim(strtolower($username)); - $userFilename = $workpath . $username; - $banned_list = file($config_dir . '/banned_users.conf'); - $keyFilename = $config_dir . "/userconfig/" . $username; - - foreach ($banned_list as $banned) { - if ($banned[0] == '#') - continue; - if (strtolower(trim($username)) == strtolower(trim($banned))) { - file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username . ' (user is banned)', FILE_APPEND); - return false; - } - } - - // Create accounts for $anonymous and $CONFIG['server_auth_user'] if not exist - if ($username == strtolower($CONFIG['anonusername'])) { - if (filemtime($config_dir . "rslight.inc.php") > filemtime($userFilename)) { - if ($userFileHandle = fopen($userFilename, 'w+')) { - fwrite($userFileHandle, password_hash($CONFIG['anonuserpass'], PASSWORD_DEFAULT)); - fclose($userFileHandle); - } - } - } - if ($username == strtolower($CONFIG['server_auth_user'])) { - if (filemtime($config_dir . "rslight.inc.php") > filemtime($userFilename)) { - if ($userFileHandle = fopen($userFilename, 'w+')) { - fwrite($userFileHandle, password_hash($CONFIG['server_auth_pass'], PASSWORD_DEFAULT)); - fclose($userFileHandle); - } - } - } - - if (trim($username) == strtolower($CONFIG['anonusername']) && $CONFIG['anonuser'] != true) { - file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username . ' (' . $CONFIG["anonusername"] . ' is disabled)', FILE_APPEND); - return FALSE; - } - - if ($userFileHandle = fopen($userFilename, 'r')) { - $userFileInfo = fread($userFileHandle, filesize($userFilename)); - fclose($userFileHandle); - if (password_verify($password, $userFileInfo)) { - touch($userFilename); - $ok = TRUE; - } else { - file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username . ' (password incorrect)', FILE_APPEND); - return FALSE; - } - } else { - $ok = FALSE; - } - if ($ok) { - if ($username != 'localuser') { - file_put_contents($logfile, "\n" . format_log_date() . " AUTH OK for: " . $username, FILE_APPEND); - } - return TRUE; - } else { - if (isset($CONFIG['auto_create']) && $CONFIG['auto_create'] == true) { - if ($userFileHandle = @fopen($userFilename, 'w+')) { - fwrite($userFileHandle, password_hash($password, PASSWORD_DEFAULT)); - fclose($userFileHandle); - chmod($userFilename, 0666); - } - $newkey = base64_encode(openssl_random_pseudo_bytes(44)); - if ($userFileHandle = @fopen($keyFilename, 'w+')) { - fwrite($userFileHandle, 'encryptionkey:' . $newkey); - fclose($userFileHandle); - chmod($userFilename, 0666); - } - file_put_contents($logfile, "\n" . format_log_date() . " AUTH OK for: " . $username . ' (auto created user)', FILE_APPEND); - return TRUE; - } else { - file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username, FILE_APPEND); - return FALSE; - } - } -} - -function check_encryption_groups($request) -{ - global $config_dir; - $groupsFilename = $config_dir . "encryption_ok.txt"; - if ($groupsFileHandle = @fopen($groupsFilename, 'r')) { - while (! feof($groupsFileHandle)) { - $buffer = fgets($groupsFileHandle); - $buffer = str_replace(array( - "\r", - "\n" - ), '', $buffer); - if (! strcmp($buffer, $request)) { - fclose($groupsFileHandle); - return TRUE; - } - } - fclose($groupsFileHandle); - } else { - return FALSE; - } -} - -function set_user_config($username, $request, $newval) -{ - global $config_dir; - $userconfigpath = $config_dir . "userconfig/"; - $username = strtolower($username); - $userFilename = $userconfigpath . $username; - $userData = file($userFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - $userFileHandle = fopen($userFilename, 'w'); - - $found = 0; - foreach ($userData as $data) { - if (strpos($data, $request . ':') !== FALSE) { - fputs($userFileHandle, $request . ':' . $newval . "\r\n"); - $found = 1; - } else { - fputs($userFileHandle, $data . "\r\n"); - } - } - if ($found == 0) { - fputs($userFileHandle, $request . ':' . $newval . "\r\n"); - } - fclose($userFileHandle); - return; -} - -function get_user_config($username, $request) -{ - global $config_dir; - $userconfigpath = $config_dir . "userconfig/"; - $username = strtolower($username); - $userFilename = $userconfigpath . $username; - - if ($userFileHandle = @fopen($userFilename, 'r')) { - while (! feof($userFileHandle)) { - $buffer = fgets($userFileHandle); - if (strpos($buffer, $request . ':') !== FALSE) { - $userdataline = $buffer; - fclose($userFileHandle); - $userdatafound = explode(':', $userdataline); - return trim($userdatafound[1]); - } - } - fclose($userFileHandle); - return FALSE; - } else { - return FALSE; - } -} - -function is_multibyte($s) -{ - return mb_strlen($s, 'utf-8') < strlen($s); -} - -function check_spam($subject, $from, $newsgroups, $ref, $body, $msgid, $useheaders = false) -{ - global $msgid_generate, $msgid_fqdn, $spooldir, $logdir; - global $CONFIG; - $spamdir = $spooldir . '/spam'; - if (! is_dir($spamdir)) { - mkdir($spamdir); - } - $logfile = $logdir . '/spam.log'; - $spamfile = tempnam($spooldir, 'spam-'); - if ($useheaders) { - // Add headers - $head = ''; - if (trim($subject) != '') { - $head .= 'Subject: ' . $subject . "\r\n"; - } - if (trim($from) != '') { - $head .= 'From: ' . $from . "\r\n"; - } - if (trim($newsgroups) != '') { - $head .= 'Newsgroups: ' . $newsgroups . "\r\n"; - } - if (trim($ref) != '') { - $head .= 'References: ' . $ref . "\r\n"; - } - if (trim($msgid) != '') { - $head .= 'Message-ID: ' . $msgid . "\r\n"; - } - $message = $head . "\r\n" . $body; - } else { - $message = $body; - } - file_put_contents($spamfile, $message); - $spamcommand = $CONFIG['spamc'] . ' -E < ' . $spamfile; - ob_start(); - passthru($spamcommand, $res); - $spamresult = ob_get_contents(); - ob_end_clean(); - $spam_fail = 1; - foreach (explode(PHP_EOL, $spamresult) as $line) { - $line = str_replace(array( - "\n\r", - "\n", - "\r" - ), '', $line); - if (strpos($line, 'X-Spam-Checker-Version:') !== FALSE) { - $spamcheckerversion = $line; - $spam_fail = 0; - } - if (strpos($line, 'X-Spam-Level:') !== FALSE) { - $spamlevel = $line; - } - if ((strpos($line, "X-Spam-Flag: YES") === 0) && ($res !== 1)) { - $res = 1; - } - } - unlink($spamfile); - if ($res === 1) { - file_put_contents($logfile, "\n" . format_log_date() . " spamc:\tSPAM\t" . $msgid . "\t" . $newsgroups . "\t" . preg_replace('/\t/', ' ', $from), FILE_APPEND); - file_put_contents($spamdir . '/' . $msgid, $spamresult); - } else { - file_put_contents($logfile, "\n" . format_log_date() . " spamc:\tHAM\t" . $msgid . "\t" . $newsgroups . "\t" . preg_replace('/\t/', ' ', $from), FILE_APPEND); - } - return array( - 'res' => $res, - 'spamresult' => $spamresult, - 'spamcheckerversion' => $spamcheckerversion, - 'spamlevel' => $spamlevel, - 'spam_fail' => $spam_fail - ); -} - -function format_log_date() -{ - return date('M d H:i:s'); -} - -function create_name_link($name, $data = null) -{ - global $CONFIG; - $name = preg_replace('/\"/', '', $name); - if ($data) { - $data = urlencode(base64_encode($data)); - } - if ((strpos($name, '...@') !== false && (isset($CONFIG['hide_email']) && $CONFIG['hide_email'] == true)) && ! $data) { - $return = '' . substr(htmlspecialchars($name), 0, 20) . ''; - } else { - if (isset($_COOKIE['mail_name'])) { - $return = '' . substr(htmlspecialchars($name), 0, 20) . ''; - } else { - $return = '' . substr(htmlspecialchars($name), 0, 20) . ''; - } - } - return ($return); -} - -function truncate_email($address) -{ - $before_at = explode('@', $address); - $namelen = strlen($before_at[0]); - if ($namelen > 3) { - $endname = $namelen - 3; - if ($endname > 8) - $endname = 8; - if ($endname < 3) - $endname ++; - if ($endname < 3) - $endname ++; - } else { - $endname = $namelen; - } - return substr($before_at[0], 0, $endname) . '...' . substr($address, $namelen, strlen($address)); -} - -function get_date_interval($value) -{ - $current = time(); - $datetime1 = date_create($value); - $datetime2 = date_create("@$current"); - $interval = date_diff($datetime1, $datetime2); - if (! $interval) { - return '(date error)'; - } - $years = $interval->format('%y') . " Years "; - $months = $interval->format('%m') . " Months "; - $days = $interval->format('%d') . " Days "; - $hours = $interval->format('%h') . " Hours "; - $minutes = $interval->format('%i') . " Minutes "; - if ($interval->format('%y') == 1) { - $years = $interval->format('%y') . " Year "; - } - if ($interval->format('%m') == 1) { - $months = $interval->format('%m') . " Month "; - } - if ($interval->format('%d') == 1) { - $days = $interval->format('%d') . " Day "; - } - if ($interval->format('%h') == 1) { - $hours = $interval->format('%h') . " Hour "; - } - if ($interval->format('%i') == 1) { - $minutes = $interval->format('%i') . " Minute "; - } - if ($interval->format('%y') == 0) { - $years = ''; - } - if ($interval->format('%m') == 0) { - $months = ''; - } - if ($interval->format('%d') == 0) { - $days = ''; - } - if ($interval->format('%h') == 0) { - $hours = ''; - } - if ($interval->format('%i') == 0) { - $minutes = ''; - } - if ($years > 0) { - $days = ''; - $hours = ''; - $minutes = ''; - } - if ($months > 0) { - $hours = ''; - $minutes = ''; - } - if ($days > 0) { - $minutes = ''; - } - $variance = $interval->format($years . $months . $days . $hours . $minutes . ' ago'); - if (strlen($variance) < 5) { - $variance = " now"; - } - return $variance; -} - -function get_newsgroups_by_msgid($msgid, $noarray = false) -{ - global $spooldir, $config_dir, $logdir, $CONFIG; - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - if ($memcacheD) { - $key = $memcache_key_prefix . '_' . 'get_newsgroups_by_msgid-' . $msgid; - if ($groups = $memcacheD->get($key)) { - if ($enable_memcache_logging) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $key", FILE_APPEND); - } - } - } - if (! $groups) { - $database = $spooldir . '/articles-overview.db3'; - $table = 'overview'; - $overview_dbh = overview_db_open($database, $table); - $overview_stmt = $overview_dbh->prepare("SELECT newsgroup FROM overview WHERE msgid=:msgid"); - $overview_stmt->bindParam(':msgid', $msgid); - $overview_stmt->execute(); - - $found = false; - $groups = array(); - while ($row = $overview_stmt->fetch()) { - $groups[] = $row['newsgroup']; - $found = true; - } - if (! $found) { - $groups = null; - } - $overview_dbh = null; - if ($groups && $memcacheD) { - $nicole = $memcacheD->add($key, $groups, $memcache_ttl); - if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $key", FILE_APPEND); - } - } - } - if ($noarray) { - return (implode(",", $groups)); - } else { - return ($groups); - } -} - -function create_xref_from_msgid($msgid, $thisgroup = null, $thisnumber = null) -{ - global $spooldir, $CONFIG; - $database = $spooldir . '/articles-overview.db3'; - $table = 'overview'; - $overview_dbh = overview_db_open($database, $table); - $overview_stmt = $overview_dbh->prepare("SELECT * FROM overview WHERE msgid=:msgid"); - $overview_stmt->bindParam(':msgid', $msgid); - $overview_stmt->execute(); - - $found = false; - $xref = "Xref: " . $CONFIG['pathhost']; - while ($row = $overview_stmt->fetch()) { - if ($row['newsgroup'] == $thisgroup && $thisgroup != null) { - $found = true; - } - $xref .= ' ' . $row['newsgroup'] . ':' . $row['number']; - } - if (! $found) { - $xref .= ' ' . $thisgroup . ':' . $thisnumber; - } - $overview_dbh = null; - return ($xref); -} - -function get_search_snippet($body, $content_type = '', $content_transfer_encoding = null) -{ - if ($content_transfer_encoding == 'base64') { - $body = base64_decode($body); - } - if ($content_transfer_encoding == 'quoted-printable') { - $body = quoted_printable_decode($body); - } - if ($content_type !== '') { - $mysnippet = recode_charset($body, $content_type, "utf8"); - } else { - $mysnippet = $body; - } - if ($bodyend = strrpos($mysnippet, "\n---\n")) { - $mysnippet = substr($mysnippet, 0, $bodyend); - } else { - if ($bodyend = strrpos($mysnippet, "\n-- ")) { - $mysnippet = substr($mysnippet, 0, $bodyend); - } else { - if ($bodyend = strrpos($mysnippet, "\n.")) { - $mysnippet = substr($mysnippet, 0, $bodyend); - } - } - } - $mysnippet = preg_replace('/\n.{0,5}>(.*)/', '', $mysnippet); - - $snipstart = strpos($mysnippet, ":\n"); - if (substr_count(trim(substr($mysnippet, 0, $snipstart)), "\n") < 2) { - $mysnippet = substr($mysnippet, $snipstart + 1); - } else { - $mysnippet = substr($mysnippet, 0); - } - return $mysnippet; -} - -function mail_db_open($database, $table = 'messages') -{ - try { - $dbh = new PDO('sqlite:' . $database); - } catch (PDOException $e) { - echo 'Connection failed: ' . $e->getMessage(); - exit(); - } - $dbh->exec("CREATE TABLE IF NOT EXISTS messages( - id INTEGER PRIMARY KEY, - msgid TEXT UNIQUE, - mail_from TEXT, - mail_viewed TEXT, - rcpt_to TEXT, - rcpt_viewed TEXT, - rcpt_target TEXT, - date TEXT, - subject TEXT, - message TEXT, - from_hide TEXT, - to_hide TEXT)"); - return ($dbh); -} - -function threads_db_open($database, $table = "threads") -{ - try { - $dbh = new PDO('sqlite:' . $database); - } catch (PDOException $e) { - echo 'Connection failed: ' . $e->getMessage(); - exit(); - } - $dbh->exec("CREATE TABLE IF NOT EXISTS threads( - id INTEGER PRIMARY KEY, - headers TEXT, - unique (headers))"); - return ($dbh); -} - -function history_db_open($database, $table = 'history') -{ - try { - $dbh = new PDO('sqlite:' . $database); - } catch (PDOException $e) { - echo 'Connection failed: ' . $e->getMessage(); - exit(); - } - $dbh->exec("CREATE TABLE IF NOT EXISTS history( - id INTEGER PRIMARY KEY, +HTTP Gateway + * Download: https://news.novabbs.com/get + * + * Based on Newsportal by Florian Amrhein + * + * E-Mail: retroguy@novabbs.com + * Web: https://news.novabbs.com + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +if (file_exists("lib/types.inc.php")) + include "lib/types.inc.php"; +if (file_exists("lib/thread.inc.php")) + include "lib/thread.inc.php"; +if (file_exists("lib/message.inc.php")) + include "lib/message.inc.php"; +if (file_exists("lib/post.inc.php")) + include "lib/post.inc.php"; + +$CONFIG = include ($config_file); + +/* + * opens the connection to the NNTP-Server + * + * $server: adress of the NNTP-Server + * $port: port of the server + */ +function nntp_open($nserver = 0, $nport = 0) +{ + global $text_error, $CONFIG; + global $server, $port; + + // echo "
NNTP OPEN
"; + if (! isset($CONFIG['enable_nntp']) || $CONFIG['enable_nntp'] != true) { + $CONFIG['server_auth_user'] = $CONFIG['remote_auth_user']; + $CONFIG['server_auth_pass'] = $CONFIG['remote_auth_pass']; + } + $authorize = ((isset($CONFIG['server_auth_user'])) && (isset($CONFIG['server_auth_pass'])) && ($CONFIG['server_auth_user'] != "")); + if ($nserver == 0) + $nserver = $server; + if ($nport == 0) + $nport = $port; + $ns = @fsockopen($nserver, $nport); + + // if the connection to the news server fails, inform the user and stop processing. + if ($ns == false) { + echo '

' . $text_error["error:"] . " " . $text_error["connection_failed"] . '.

'; + echo '
'; + echo '

Please wait a few moments and try again. If you see the same error, notify the owner that their Message Server is offline.

'; + echo '
'; + return false; + // exit(0); + } + + $weg = line_read($ns); // kill the first line + if (substr($weg, 0, 2) != "20") { + echo "

" . $text_error["error:"] . $weg . "

"; + fclose($ns); + $ns = false; + } else { + if ($ns != false) { + fputs($ns, "MODE reader\r\n"); + $weg = line_read($ns); // and once more + if ((substr($weg, 0, 2) != "20") && ((! $authorize) || ((substr($weg, 0, 3) != "480") && ($authorize)))) { + echo "

" . $text_error["error:"] . $weg . "

"; + fclose($ns); + $ns = false; + } + } + if ((isset($CONFIG['server_auth_user'])) && (isset($CONFIG['server_auth_pass'])) && ($CONFIG['server_auth_user'] != "")) { + fputs($ns, "AUTHINFO USER " . $CONFIG['server_auth_user'] . "\r\n"); + $weg = line_read($ns); + fputs($ns, "AUTHINFO PASS " . $CONFIG['server_auth_pass'] . "\r\n"); + $weg = line_read($ns); + /* Only check auth if reading and posting same server */ + // NNTP Response NOT 281 (Authorization failed) + if (substr($weg, 0, 3) != "281" && ! (isset($post_server)) && ($post_server != "")) { + echo "

" . $text_error["error:"] . "

"; + echo "

" . $text_error["auth_error"] . "

"; + } + } + } + if ($ns == false) + echo "

" . $text_error["connection_failed"] . "

"; + return $ns; +} + +function nntp2_open($nserver = 0, $nport = 0) +{ + global $text_error, $CONFIG; + + $authorize = ((isset($CONFIG['remote_auth_user'])) && (isset($CONFIG['remote_auth_pass'])) && ($CONFIG['remote_auth_user'] != "")); + if ($nserver == 0) + $nserver = $CONFIG['remote_server']; + if ($nport == 0) + $nport = $CONFIG['remote_port']; + if ($CONFIG['remote_ssl']) { + if ($nport == $CONFIG['remote_port']) { + $nport = $CONFIG['remote_ssl']; + } + $ns = fsockopen("ssl://" . $nserver, $nport, $error, $errorString, 30); + if (! $ns) { + return false; + } + } else { + if (isset($CONFIG['socks_host']) && $CONFIG['socks_host'] !== '') { + $ns = fsocks4asockopen($CONFIG['socks_host'], $CONFIG['socks_port'], $nserver, $nport); + } else { + $ns = @fsockopen('tcp://' . $nserver . ":" . $nport); + } + } + // $ns=@fsockopen($nserver,$nport); + // echo "PORT: ".$nport." ns: ".$ns; + $weg = line_read($ns); // kill the first line + if (substr($weg, 0, 2) != "20") { + echo "

" . $text_error["error:"] . $weg . "

"; + if ($ns) { + fclose($ns); + } + $ns = false; + } else { + if ($ns != false) { + fputs($ns, "MODE reader\r\n"); + $weg = line_read($ns); // and once more + if ((substr($weg, 0, 2) != "20") && ((! $authorize) || ((substr($weg, 0, 3) != "480") && ($authorize)))) { + echo "

" . $text_error["error:"] . $weg . "

"; + fclose($ns); + $ns = false; + } + } + if ((isset($CONFIG['remote_auth_user'])) && (isset($CONFIG['remote_auth_pass'])) && ($CONFIG['remote_auth_user'] != "")) { + fputs($ns, "AUTHINFO USER " . $CONFIG['remote_auth_user'] . "\r\n"); + $weg = line_read($ns); + fputs($ns, "AUTHINFO PASS " . $CONFIG['remote_auth_pass'] . "\r\n"); + $weg = line_read($ns); + /* Only check auth if reading and posting same server */ + if (substr($weg, 0, 3) != "281" && ! (isset($post_server)) && ($post_server != "")) { + echo "

" . $text_error["error:"] . "

"; + echo "

" . $text_error["auth_error"] . "

"; + } + } + } + if ($ns == false) + echo "

" . $text_error["connection_failed"] . "

"; + return $ns; +} + +function fsocks4asockopen($proxyHostname, $proxyPort, $targetHostname, $targetPort) +{ + $sock = fsockopen($proxyHostname, $proxyPort); + if ($sock === false) + return false; + fwrite($sock, pack("CCnCCCCC", 0x04, 0x01, $targetPort, 0x00, 0x00, 0x00, 0x01, 0x00) . $targetHostname . pack("C", 0x00)); + $response = fread($sock, 16); + $values = unpack("xnull/Cret/nport/Nip", $response); + if ($values["ret"] == 0x5a) + return $sock; + else { + fclose($sock); + return false; + } +} + +/* + * Close a NNTP connection + * + * $ns: the handle of the connection + */ +function nntp_close(&$ns) +{ + if ($ns != false) { + fputs($ns, "QUIT\r\n"); + fclose($ns); + } +} + +/* + * Validates an email adress + * + * $address: a string containing the email-address to be validated + * + * returns true if the address passes the tests, false otherwise. + */ +function validate_email($address) +{ + global $validate_email; + $return = true; + if (($validate_email >= 1) && ($return == true)) +/* Need to clean up this regex to work properly with preg_match + $return = (preg_match('^[-!#$%&\'*+\\./0-9=?A-Z^_A-z{|}~]+'.'@'. + '[-!#$%&\'*+\\/0-9=?A-Z^_A-z{|}~]+\.'. + '[-!#$%&\'*+\\./0-9=?A-Z^_A-z{|}~]+$',$address)); +*/ + $return = 1; + if (($validate_email >= 2) && ($return == true)) { + $addressarray = address_decode($address, "garantiertungueltig"); + $return = checkdnsrr($addressarray[0]["host"], "MX"); + if (! $return) + $return = checkdnsrr($addressarray[0]["host"], "A"); + } + return ($return); +} + +/* + * decodes a block of 7bit-data in uuencoded format to it's original + * 8bit format. + * The headerline containing filename and permissions doesn't have to + * be included. + * + * $data: The uuencoded data as a string + * + * returns the 8bit data as a string + * + * Note: this function is very slow and doesn't recognize incorrect code. + */ +function uudecode_line($line) +{ + $data = substr($line, 1); + $length = ord($line[0]) - 32; + $decoded = ""; + for ($i = 0; $i < (strlen($data) >> 2); $i ++) { + $pack = substr($data, $i << 2, 4); + $upack = ""; + $bitmaske = 0; + for ($o = 0; $o < 4; $o ++) { + $g = ((ord($pack[3 - $o]) - 32)); + if ($g == 64) + $g = 0; + $bitmaske = $bitmaske | ($g << (6 * $o)); + } + $schablone = 255; + for ($o = 0; $o < 3; $o ++) { + $c = ($bitmaske & $schablone) >> ($o << 3); + $schablone = ($schablone << 8); + $upack = chr($c) . $upack; + } + $decoded .= $upack; + } + $decoded = substr($decoded, 0, $length); + return $decoded; +} + +/* + * decodes uuencoded Attachments. + * + * $data: the encoded data + * + * returns the decoded data + */ +function uudecode($data) +{ + $d = explode("\n", $data); + $u = ""; + for ($i = 0; $i < count($d) - 1; $i ++) + $u .= uudecode_line($d[$i]); + return $u; +} + +/* + * returns the mimetype of an filename + * + * $name: the complete filename of a file + * + * returns a string containing the mimetype + */ +function get_mimetype_by_filename($name) +{ + $ending = strtolower(strrchr($name, ".")); + switch ($ending) { + case ".jpg": + case ".jpeg": + $type = "image/jpeg"; + break; + case ".gif": + $type = "image/gif"; + break; + case ".png": + $type = "image/png"; + break; + case ".bmp": + $type = "image/bmp"; + break; + default: + $type = "text/plain"; + } + return $type; +} + +function get_mimetype_by_string($filedata) +{ + if (function_exists('finfo_open')) { + $f = finfo_open(); + return finfo_buffer($f, $filedata, FILEINFO_MIME_TYPE); + } else { + return false; + } +} + +/* + * Test, if the access to a group is allowed. This is true, if $testgroup is + * false or the groupname is in groups.txt + * + * $groupname: name of the group to be checked + * + * returns true, if access is allowed + */ +function testGroup($groupname) +{ + global $CONFIG, $testgroup, $file_groups, $config_dir; + $groupname = strtolower($groupname); + if ($testgroup) { + $gf = fopen($file_groups, "r"); + while (! feof($gf)) { + $read = trim(line_read($gf)); + $read = preg_replace('/\t/', ' ', $read); + $read = strtolower($read); + $pos = strpos($read, " "); + if ($pos != false) { + if (substr($read, 0, $pos) == trim($groupname)) + return true; + } else { + if ($read == trim($groupname)) + return true; + } + } + fclose($gf); + if ($groupname == $CONFIG['spamgroup']) { + return true; + } else { + /* Find section */ + $menulist = file($config_dir . "menu.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + foreach ($menulist as $menu) { + if ($menu[0] == '#') { + continue; + } + $menuitem = explode(':', $menu); + if ($menuitem[1] == '0') { + continue; + } + $glfp = fopen($config_dir . $menuitem[0] . "/groups.txt", 'r'); + $section = ""; + while ($gl = fgets($glfp)) { + $group_name = preg_split("/( |\t)/", $gl, 2); + if (stripos(trim($groupname), trim($group_name[0])) !== false) { + fclose($glfp); + return true; + } + } + } + fclose($glfp); + return false; + } + } else { + return true; + } +} + +function get_section_by_group($groupname, $all_sections = false) +{ + global $CONFIG, $config_dir; + $menulist = file($config_dir . "menu.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + // Get first group in Newsgroups + $groupname = preg_split("/( |\,)/", $groupname, 2); + $groupname = $groupname[0]; + foreach ($menulist as $menu) { + if ($menu[0] == '#') { + continue; + } + $menuitem = explode(':', $menu); + if ($menuitem[1] == '0') { + if (! $all_sections) { + continue; + } + } + $section = ""; + $gldata = file($config_dir . $menuitem[0] . "/groups.txt"); + foreach ($gldata as $gl) { + $group_name = preg_split("/( |\t)/", $gl, 2); + if (strtolower(trim($groupname)) == strtolower(trim($group_name[0]))) { + $section = $menuitem[0]; + return $section; + } + } + } + return false; +} + +function testGroups($newsgroups) +{ + $groups = explode(",", $newsgroups); + $count = count($groups); + $return = ""; + $o = 0; + for ($i = 0; $i < $count; $i ++) { + if (testgroup($groups[$i]) && (! function_exists("npreg_group_has_write_access") || npreg_group_has_write_access($groups[$i]))) { + if ($o > 0) + $return .= ","; + $o ++; + $return .= $groups[$i]; + } + } + return ($return); +} + +/* + * read one line from the NNTP-server + */ +function line_read(&$ns) +{ + if ($ns != false) { + $t = str_replace("\n", "", str_replace("\r", "", fgets($ns, 1200))); + return $t; + } +} + +/* + * Split an internet-address string into its parts. An address string could + * be for example: + * - user@host.domain (Realname) + * - "Realname" + * - user@host.domain + * + * The address will be split into user, host (incl. domain) and realname + * + * $adrstring: The string containing the address in internet format + * $defaulthost: The name of the host which should be returned if the + * address-string doesn't contain a hostname. + * + * returns an hash containing the fields "mailbox", "host" and "personal" + */ +function address_decode($adrstring, $defaulthost) +{ + $parsestring = trim($adrstring); + $len = strlen($parsestring); + $at_pos = strpos($parsestring, '@'); // find @ + $ka_pos = strpos($parsestring, "("); // find ( + $kz_pos = strpos($parsestring, ')'); // find ) + $ha_pos = strpos($parsestring, '<'); // find < + $hz_pos = strpos($parsestring, '>'); // find > + $space_pos = strpos($parsestring, ')'); // find ' ' + $email = ""; + $mailbox = ""; + $host = ""; + $personal = ""; + if ($space_pos != false) { + if (($ka_pos != false) && ($kz_pos != false)) { + $personal = substr($parsestring, $ka_pos + 1, $kz_pos - $ka_pos - 1); + $email = trim(substr($parsestring, 0, $ka_pos - 1)); + } + } else { + $email = $adrstring; + } + if (($ha_pos != false) && ($hz_pos != false)) { + $email = trim(substr($parsestring, $ha_pos + 1, $hz_pos - $ha_pos - 1)); + $personal = substr($parsestring, 0, $ha_pos - 1); + } + if ($at_pos != false) { + $mailbox = substr($email, 0, strpos($email, '@')); + $host = substr($email, strpos($email, '@') + 1); + } else { + $mailbox = $email; + $host = $defaulthost; + } + $personal = trim($personal); + if (substr($personal, 0, 1) == '"') + $personal = substr($personal, 1); + if (substr($personal, strlen($personal) - 1, 1) == '"') + $personal = substr($personal, 0, strlen($personal) - 1); + $result["mailbox"] = trim($mailbox); + $result["host"] = trim($host); + if ($personal != "") + $result["personal"] = $personal; + $complete[] = $result; + return ($complete); +} + +/* + * Read the groupnames from groups.txt, and get additional informations + * of the groups from the newsserver + * + * when load=0, returns cached group list + * when load=1, checks if the cache should be used, and returns nothing + * when force_reload=true, rebuilds group list cache + */ +function groups_read($server, $port, $load = 0, $force_reload = false) +{ + global $gl_age, $file_groups, $spooldir, $config_name, $cache_index; + // is there a cached version, and is it actual enough? + $cachefile = $spooldir . '/' . $config_name . '-groups.dat'; + // if cache is new enough, don't recreate it + clearstatcache(TRUE, $cachefile); + if (! $force_reload && $load == 1 && file_exists($cachefile) && (filemtime($cachefile) + $cache_index > time())) { + return; + } + if (! $force_reload && file_exists($cachefile) && $load == 0) { + // cached file exists and is new enough, so lets read it out. + $file = fopen($cachefile, "r"); + $data = ""; + while (! feof($file)) { + $data .= fgets($file, 1000); + } + fclose($file); + $newsgroups = unserialize($data); + } else { + // force a refresh of the group list + $ns = nntp_open($server, $port); + if ($ns == false) + return false; + // $gf=fopen($file_groups,"r"); + $gfdata = file($file_groups); + // if we want to mark groups with new articles with colors, we will later + // need the format of the overview + $overviewformat = thread_overview_read($ns); + foreach ($gfdata as $gf) { + $gruppe = new newsgroupType(); + $tmp = preg_replace('/\t/', ' ', trim($gf)); + if (substr($tmp, 0, 1) == ":") { + $gruppe->text = substr($tmp, 1); + $newsgroups[] = $gruppe; + } elseif (strlen($tmp) > 0) { + // is there a description in groups.txt? + $gr = explode(" ", $tmp, 2); + if (isset($gr[1])) { // Yes + $gruppe->name = $gr[0]; + $desc = $gr[1]; + } else { // No + // no, get it from the newsserver. + $gruppe->name = $tmp; + if (is_file($spooldir . '/' . $tmp . '-title')) { + $response = file_get_contents($spooldir . '/' . $tmp . '-title'); + $desc = strrchr($response, "\t"); + } else { + $desc = "-"; + } + } + if (strcmp($desc, "") == 0) + $desc = "-"; + $gruppe->description = $desc; + fputs($ns, "GROUP " . $gruppe->name . "\r\n"); + $t = explode(" ", line_read($ns)); + + if ($t[0] == "211") + $gruppe->count = $t[1]; + else { + nntp_close($ns); + $ns = nntp_open($server, $port); + if ($ns == false) + return false; + fputs($ns, "GROUP " . $gruppe->name . "\r\n"); + $t = explode(" ", line_read($ns)); + if ($t[0] == "211") + $gruppe->count = $t[1]; + else + continue; + } + // mark group with new articles with colors + if ($gl_age) { + fputs($ns, 'XOVER ' . $t[3] . "\r\n"); + $tmp = explode(" ", line_read($ns)); + if ($tmp[0] == "224") { + $tmp = line_read($ns); + if ($tmp != ".") { + $head = thread_overview_interpret($tmp, $overviewformat, $gruppe->name); + $tmp = line_read($ns); + $gruppe->age = $head->date; + } + } + } + if ((strcmp(trim($gruppe->name), "") != 0) && (substr($gruppe->name, 0, 1) != "#")) + $newsgroups[] = $gruppe; + } + } + nntp_close($ns); + // write the data to the cachefile + file_put_contents($cachefile, serialize($newsgroups)); + } + if ($load == 0) { + return $newsgroups; + } else { + return; + } +} + +function groups_show($gruppen) +{ + global $gl_age, $frame, $spooldir, $config_dir, $logdir, $CONFIG, $OVERRIDES, $spoolnews; + if ($gruppen == false) + return; + global $file_thread, $text_groups; + $logfile = $logdir . '/debug.log'; + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + $c = count($gruppen); + $acttype = "keins"; + echo ''; + $subs = array(); + $nonsubs = array(); + $user = null; + // Get registered user settings + if (isset($_COOKIE['mail_name'])) { + if ($userdata = get_user_mail_auth_data($_COOKIE['mail_name'])) { + $userfile = $spooldir . '/' . strtolower($_COOKIE['mail_name']) . '-articleviews.dat'; + $user_config = unserialize(file_get_contents($config_dir . '/userconfig/' . strtolower($_COOKIE['mail_name']) . '.config')); + } + } + for ($i = 0; $i < $c; $i ++) { + unset($groupdisplay); + $g = $gruppen[$i]; + if (isset($g->text)) { + if ($acttype != "text") { + $acttype = "text"; + } + } else { + if ($acttype != "group") { + $acttype = "group"; + } + if (! isset($userdata[$g->name])) { + if (isset($user_config['hide_unsub']) && $user_config['hide_unsub'] == 'hide') { + continue; + } + } + unset($lastarticleinfo); + $found = 0; + // Get last article info from article database + // First check memcache + if ($memcacheD) { + $lar_memcache = $memcache_key_prefix . '_' . 'lastarticleinfo-' . $g->name; + $groupfile = $spooldir . '/' . $g->name . '-lastarticleinfo.dat'; + if ($lastarticleinfo = unserialize($memcacheD->get($lar_memcache))) { + if ($lastarticleinfo && file_exists($groupfile) && filemtime($groupfile) <= $lastarticleinfo['date']) { + if ($enable_memcache_logging) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . ' (cache hit) lastarticleinfo for ' . $g->name, FILE_APPEND); + } + $found = 1; + } else { + unset($lastarticleinfo); + } + } + } + if (! isset($lastarticleinfo['date'])) { + if ($CONFIG['article_database'] == '1') { + $database = $spooldir . '/' . $g->name . '-articles.db3'; + $article_dbh = article_db_open($database); + $article_query = $article_dbh->prepare('SELECT * FROM articles ORDER BY CAST(date AS int) DESC LIMIT 5'); + $article_query->execute(); + while ($row = $article_query->fetch()) { + if ($row['date'] > time()) { + continue; + } + $found = 1; + break; + } + $article_dbh = null; + } else { + $database = $spooldir . '/articles-overview.db3'; + $overview_dbh = overview_db_open($database); + $overview_query = $overview_dbh->prepare('SELECT * FROM overview WHERE newsgroup=:newsgroup ORDER BY CAST(date AS int) DESC LIMIT 5'); + $overview_query->execute([ + 'newsgroup' => $g->name + ]); + while ($row = $overview_query->fetch()) { + if ($row['date'] > time()) { + continue; + } + $found = 1; + break; + } + $overview_dbh = null; + } + if ($found == 1) { + $lastarticleinfo = $row; + if ($memcacheD) { + touch($groupfile, $lastarticleinfo['date']); + $nicole = $memcacheD->delete($lar_memcache); + $memcacheD->add($lar_memcache, serialize($row), $memcache_ttl); + if ($enable_memcache_logging) { + if ($nicole) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache update) $lar_memcache", FILE_APPEND); + } else { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $lar_memcache", FILE_APPEND); + } + } + } + } + } + $new = false; + $new_style_on = ''; + $new_style_off = ''; + if (isset($userdata[$g->name]) && ($userdata[$g->name] < $lastarticleinfo['date'])) { + $new_style_on = '*'; + $new_style_off = ''; + $new = true; + } + /* Display group name and description */ + if (isset($userdata[$g->name])) { + $lineclass = "np_thread_line2"; + } else { + $lineclass = "np_thread_line1"; + } + if ($new) { + $latest_link = '&time=' . $userdata[$g->name]; + } else { + $latest_link = ''; + } + $groupdisplay = ''; + + $groupdisplay .= '
'; + echo 'LatestNewsgroupMessagesLast Message
'; + $groupdisplay .= ''; + if ((isset($_SESSION['theme'])) && file_exists('../common/themes/' . $_SESSION['theme'] . '/images/latest.png')) { + $latest_image = '../common/themes/' . $_SESSION['theme'] . '/images/latest.png'; + } else { + $latest_image = '../common/images/latest.png'; + } + if ($new) { + if ((isset($_SESSION['theme'])) && file_exists('../common/themes/' . $_SESSION['theme'] . '/images/new-articles.png')) { + $latest_image = '../common/themes/' . $_SESSION['theme'] . '/images/new-articles.png'; + } else { + $latest_image = '../common/images/new-articles.png'; + } + $groupdisplay .= ''; + } else { + $groupdisplay .= ''; + } + $groupdisplay .= ''; + $groupdisplay .= ''; + $groupdisplay .= ''; + $groupdisplay .= '' . $new_style_on . group_display_name($g->name) . $new_style_off . "\n"; + if ($new) { + echo ''; + } + if ($g->description != "-") { + $groupdisplay .= '

' . $g->description . '

'; + } + if (isset($userdata[$g->name])) { + $groupdisplay .= '

'; + $groupdisplay .= '(unsubscribe)'; + if ($new) { + $groupdisplay .= '(mark read)'; + } + $groupdisplay .= ''; + if ($gl_age) + $datecolor = thread_format_date_color($g->age); + $groupdisplay .= ''; + if ($datecolor != "") + $groupdisplay .= '' . $g->count . ''; + else + $groupdisplay .= $g->count; + $groupdisplay .= ''; + + /* Display latest article info */ + $groupdisplay .= '

'; + + if ($found == 1) { + $poster = address_decode($lastarticleinfo['name'], "nowhere"); + $lastarticleinfo['from'] = $poster[0]['mailbox'] . "@" . $poster[0]['host']; + if (isset($poster[0]['personal'])) { + $lastarticleinfo['name'] = $poster[0]['personal']; + } else { + $lastarticleinfo['name'] = $poster[0]['mailbox']; + } + $fromoutput[0] = $poster[0]['mailbox'] . "@" . $poster[0]['host']; + $groupdisplay .= get_date_interval(date("D, j M Y H:i T", $lastarticleinfo['date'])); + $groupdisplay .= '
'; + $groupdisplay .= 'by: '; + $groupdisplay .= create_name_link(mb_decode_mimeheader(html_entity_decode($lastarticleinfo['name'])), $lastarticleinfo['from']); + $groupdisplay .= '
'; + } else { + unset($lastarticleinfo); + } + } + if (isset($groupdisplay)) { + $groupdisplay .= "\n"; + flush(); + if (isset($userdata[$g->name])) { + $subs[] = $groupdisplay; + } else { + $nonsubs[] = $groupdisplay; + } + } + } + foreach ($subs as $sub) { + echo $sub; + } + foreach ($nonsubs as $nonsub) { + echo $nonsub; + } + echo "
"; + echo ''; + echo '
'; + if (isset($user_config['hide_unsub']) && $user_config['hide_unsub'] == 'hide') { + echo ' Unsubscribed groups are HIDDEN.'; + echo ' Select groups from Grouplist to add groups'; + } + if (isset($userdata)) { + show_groups_hide_toggle(); + } + echo '
'; +} + +function show_groups_hide_toggle() +{ + global $user_config; + echo '
'; + echo ' Unsubscribed Groups: '; + if ($user_config['hide_unsub'] == 'hide') { + echo 'Show'; + echo ' '; + echo 'Hide'; + echo ' '; + } else { + echo 'Show'; + echo ' '; + echo 'Hide'; + echo ' '; + } + echo ''; + echo '
'; +} + +/* + * print the group names from an array to the webpage + */ +function groups_show_frames($gruppen) +{ + global $gl_age, $frame, $spooldir; + if ($gruppen == false) + return; + global $file_thread, $text_groups; + $c = count($gruppen); + echo '
'; + $acttype = "keins"; + for ($i = 0; $i < $c; $i ++) { + $g = $gruppen[$i]; + if (isset($g->text)) { + if ($acttype != "text") { + $acttype = "text"; + if ($i > 0) + echo '
'; + echo '
'; + } + echo $g->text; + } else { + if ($acttype != "group") { + $acttype = "group"; + if ($i > 0) + echo '
'; + echo '
'; + } + echo '
'; + echo '' . group_display_name($g->name) . "\n"; + if ($gl_age) + $datecolor = thread_format_date_color($g->age); + echo '('; + if ($datecolor != "") + echo '' . $g->count . ''; + else + echo $g->count; + echo ')'; + if ($g->description != "-") + echo '
' . $g->description . ''; + echo '
'; + } + echo "\n"; + flush(); + } + echo "
\n"; +} + +/* + * gets a list of available articles in the group $groupname + */ +/* + * function getArticleList(&$ns,$groupname) { + * fputs($ns,"LISTGROUP $groupname \r\n"); + * $line=line_read($ns); + * $line=line_read($ns); + * while(strcmp($line,".") != 0) { + * $articleList[] = trim($line); + * $line=line_read($ns); + * } + * if (!isset($articleList)) $articleList="-"; + * return $articleList; + * } + */ + +/* + * Decode quoted-printable or base64 encoded headerlines + * + * $value: The to be decoded line + * + * returns the decoded line + */ +function headerDecode($value) +{ + $value = preg_replace_callback('/(=\?[^\?]+\?Q\?)([^\?]+)(\?=)/i', function ($matches) { + return $matches[1] . str_replace('_', '=20', $matches[2]) . $matches[3]; + }, $value); + return mb_decode_mimeheader($value); +} + +/* + * calculates an Unix timestamp out of a Date-Header in an article + * + * $value: Value of the Date: header + * + * returns an Unix timestamp + */ +function getTimestamp($value) +{ + global $CONFIG; + + return strtotime($value); +} + +function parse_header($hdr, $number = "") +{ + for ($i = count($hdr) - 1; $i > 0; $i --) + if (preg_match("/^(\x09|\x20)/", $hdr[$i])) + $hdr[$i - 1] = $hdr[$i - 1] . " " . ltrim($hdr[$i]); + $header = new headerType(); + $header->isAnswer = false; + for ($count = 0; $count < count($hdr); $count ++) { + $variable = substr($hdr[$count], 0, strpos($hdr[$count], " ")); + $value = trim(substr($hdr[$count], strpos($hdr[$count], " ") + 1)); + switch (strtolower($variable)) { + case "from:": + $fromline = address_decode(headerDecode($value), "nowhere"); + if (! isset($fromline[0]["host"])) + $fromline[0]["host"] = ""; + $header->from = $fromline[0]["mailbox"] . "@" . $fromline[0]["host"]; + $header->username = $fromline[0]["mailbox"]; + if (! isset($fromline[0]["personal"])) { + $header->name = ""; + } else { + $header->name = $fromline[0]["personal"]; + } + break; + case "message-id:": + $header->id = $value; + break; + case "subject:": + $header->subject = headerDecode($value); + break; + case "newsgroups:": + $header->newsgroups = $value; + break; + case "organization:": + $header->organization = headerDecode($value); + break; + case "content-transfer-encoding:": + $header->content_transfer_encoding = trim(strtolower($value)); + break; + case "content-disposition:": + $getname = preg_split("/name\=/", $value, 2); + if (isset($getname[1])) { + $header->content_type_name = array( + $getname[1] + ); + } + break; + case "content-type:": + $header->content_type = array(); + $subheader = explode(";", $value); + $header->content_type[0] = strtolower(trim($subheader[0])); + for ($i = 1; $i < count($subheader); $i ++) { + $gleichpos = strpos($subheader[$i], "="); + if ($gleichpos) { + $subvariable = trim(substr($subheader[$i], 0, $gleichpos)); + $subvalue = trim(substr($subheader[$i], $gleichpos + 1)); + if (($subvalue[0] == '"') && ($subvalue[strlen($subvalue) - 1] == '"')) + $subvalue = substr($subvalue, 1, strlen($subvalue) - 2); + switch ($subvariable) { + case "charset": + $header->content_type_charset = array( + strtolower($subvalue) + ); + break; + case "name": + $header->content_type_name = array( + $subvalue + ); + break; + case "boundary": + $header->content_type_boundary = $subvalue; + break; + case "format": + $header->content_type_format = array( + $subvalue + ); + } + } + } + break; + case "references:": + $ref = trim($value); + while (strpos($ref, "> <") != false) { + $header->references[] = substr($ref, 0, strpos($ref, " ")); + $ref = substr($ref, strpos($ref, "> <") + 2); + } + $header->references[] = trim($ref); + break; + case "date:": + $header->date = getTimestamp(trim($value)); + break; + case "followup-to:": + $header->followup = trim($value); + break; + case "x-newsreader:": + case "x-mailer:": + case "x-rslight-to:": + $header->rslight_to = trim($value); + break; + case "x-rslight-site:": + $header->rslight_site = trim($value); + break; + case "user-agent:": + $header->user_agent = trim($value); + break; + case "x-face:": // not ready + // echo "

-".base64_decode($value)."-

"; + break; + case "x-no-archive:": + $header->xnoarchive = strtolower(trim($value)); + } + } + if (! isset($header->content_type[0])) + $header->content_type[0] = "text/plain"; + if (! isset($header->content_transfer_encoding)) + $header->content_transfer_encoding = "8bit"; + if ($number != "") + $header->number = $number; + return $header; +} + +/* + * convert the charset of a text + */ +function recode_charset($text, $source = false, $dest = false) +{ + global $iconv_enable, $www_charset; + if ($dest == false) + $dest = $www_charset; + if (($iconv_enable) && ($source != false)) { + $return = iconv($source, $dest . "//TRANSLIT", $text); + if ($return != "") + return $return; + else + return $text; + } else { + return $text; + } +} + +function decode_body($body, $encoding) +{ + $bodyzeile = ""; + switch ($encoding) { + case "base64": + $body = base64_decode($body); + break; + case "quoted-printable": + $body = Quoted_printable_decode($body); + $body = str_replace("=\n", "", $body); + // default: + // $body=str_replace("\n..\n","\n.\n",$body); + } + + return $body; +} + +/* + * makes URLs clickable + * + * $text: A text-line probably containing links. + * + * the function returns the text-line with HTML-Links to the links or + * email-adresses. + */ +function html_parse($text) +{ + global $frame_externallink; + if ((isset($frame_externallink)) && ($frame_externallink != "")) { + $target = ' TARGET="' . $frame_externallink . '" '; + } else { + $target = ' '; + } + $ntext = ""; + // split every line into it's words + $words = explode(" ", $text); + $n = count($words); + $is_link = 0; + for ($i = 0; $i < $n; $i ++) { + $word = $words[$i]; + // add the spaces between the words + if ($i > 0) + $ntext .= " "; + $ntext .= $word; + } + return ($ntext); +} + +function display_links_in_body($text) +{ + global $config_dir; + preg_match_all('/(https?|ftp|scp|news|gopher|gemini|telnet):\/\/[a-zA-Z0-9.?%=\-\+\;\:\,\~\@\!\(\)\$\#&_\/]+/', $text, $matches); + $found = array(); + $isquote = false; + if (strpos($text, ">") == 0) { + $isquote = true; + echo '
'; + } + foreach ($matches[0] as $match) { + if (! $match) { + continue; + } + if (in_array($match, $found)) { + continue; + } + $found[] = $match; + $linkurl = preg_replace("/(<|>)/", ' ', htmlspecialchars_decode($match)); + $url = preg_replace("/(<|>)/", ' ', $match); + $pattern = preg_quote($url); + $pattern = "!$pattern!"; + $text = preg_replace($pattern, '' . $url . '', $text, 1); + } + if (file_exists($config_dir . '/rewrite_body.inc.php')) { + include ($config_dir . '/rewrite_body.inc.php'); + } + + echo $text; + if ($isquote) { + echo '
'; + } +} + +/* + * read the header of an article in plaintext into an array + * $articleNumber can be the number of an article or its message-id. + */ +function readPlainHeader(&$ns, $group, $articleNumber) +{ + fputs($ns, "GROUP $group\r\n"); + $line = line_read($ns); + fputs($ns, "HEAD $articleNumber\r\n"); + $line = line_read($ns); + if (substr($line, 0, 3) != "221") { + echo $text_error["article_not_found"]; + $header = false; + } else { + $line = line_read($ns); + $body = ""; + while (strcmp(trim($line), ".") != 0) { + $body .= $line . "\n"; + $line = line_read($ns); + } + return explode("\n", str_replace("\r\n", "\n", $body)); + } +} + +/* + * cancel an article on the newsserver + * + * DO NOT USE THIS FUNCTION, IF YOU DON'T KNOW WHAT YOU ARE DOING! + * + * $ns: The handler of the NNTP-Connection + * $group: The group of the article + * $id: the Number of the article inside the group or the message-id + */ +function message_cancel($subject, $from, $newsgroups, $ref, $body, $id) +{ + global $server, $port, $send_poster_host, $CONFIG, $text_error; + global $www_charset; + flush(); + $ns = nntp_open($server, $port); + if ($ns != false) { + fputs($ns, "POST\r\n"); + $weg = line_read($ns); + fputs($ns, 'Subject: ' . quoted_printable_encode($subject) . "\r\n"); + fputs($ns, 'From: ' . $from . "\r\n"); + fputs($ns, 'Newsgroups: ' . $newsgroups . "\r\n"); + fputs($ns, "Mime-Version: 1.0\r\n"); + fputs($ns, "Content-Type: text/plain; charset=" . $www_charset . "\r\n"); + fputs($ns, "Content-Transfer-Encoding: 8bit\r\n"); + if ($send_poster_host) + fputs($ns, 'X-HTTP-Posting-Host: ' . gethostbyaddr(getenv("REMOTE_ADDR")) . "\r\n"); + if ($ref != false) + fputs($ns, 'References: ' . $ref . "\r\n"); + if (isset($CONFIG['organization'])) + fputs($ns, 'Organization: ' . quoted_printable_encode($CONFIG['organization']) . "\r\n"); + fputs($ns, "Control: cancel " . $id . "\r\n"); + $body = str_replace("\n.\r", "\n..\r", $body); + $body = str_replace("\r", '', $body); + $b = explode("\n", $body); + $body = ""; + for ($i = 0; $i < count($b); $i ++) { + if ((strpos(substr($b[$i], 0, strpos($b[$i], " ")), ">") != false) | (strcmp(substr($b[$i], 0, 1), ">") == 0)) { + $body .= textwrap(stripSlashes($b[$i]), 78, "\r\n") . "\r\n"; + } else { + $body .= textwrap(stripSlashes($b[$i]), 74, "\r\n") . "\r\n"; + } + } + fputs($ns, "\r\n" . $body . "\r\n.\r\n"); + $message = line_read($ns); + nntp_close($ns); + } else { + $message = $text_error["post_failed"]; + } + return $message; +} + +function rslight_encrypt($data, $key) +{ + $encryption_key = base64_decode($key); + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); + $encrypted = openssl_encrypt($data, 'aes-256-cbc', $encryption_key, 0, $iv); + return base64_encode($encrypted . '::' . $iv); +} + +function _rawurlencode($string) +{ + $string = rawurlencode(str_replace('+', '%2B', $string)); + return $string; +} + +function _rawurldecode($string) +{ + $string = rawurldecode(str_replace('%2B', '+', $string)); + return $string; +} + +function rslight_decrypt($data, $key) +{ + $encryption_key = base64_decode($key); + list ($encrypted_data, $iv) = explode('::', base64_decode($data), 2); + return openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv); +} + +function group_display_name($gname) +{ + global $config_dir; + $namelist = file($config_dir . "rename.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + foreach ($namelist as $name) { + if ($name[0] == '#') { + continue; + } + $nameitem = explode(':', $name); + if (! strcmp(trim($nameitem[0]), trim($gname))) { + return $nameitem[1]; + } + } + return $gname; +} + +function check_bbs_auth($username, $password) +{ + global $config_dir, $spooldir, $CONFIG; + + $logfile = $spooldir . '/log/auth.log'; + if ($username == '' && $password == '') { + return false; + } + + $workpath = $config_dir . "users/"; + $username = trim(strtolower($username)); + $userFilename = $workpath . $username; + $banned_list = file($config_dir . '/banned_users.conf'); + $keyFilename = $config_dir . "/userconfig/" . $username; + + foreach ($banned_list as $banned) { + if ($banned[0] == '#') + continue; + if (strtolower(trim($username)) == strtolower(trim($banned))) { + file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username . ' (user is banned)', FILE_APPEND); + return false; + } + } + + // Create accounts for $anonymous and $CONFIG['server_auth_user'] if not exist + if ($username == strtolower($CONFIG['anonusername'])) { + if (filemtime($config_dir . "rslight.inc.php") > filemtime($userFilename)) { + if ($userFileHandle = fopen($userFilename, 'w+')) { + fwrite($userFileHandle, password_hash($CONFIG['anonuserpass'], PASSWORD_DEFAULT)); + fclose($userFileHandle); + } + } + } + if ($username == strtolower($CONFIG['server_auth_user'])) { + if (filemtime($config_dir . "rslight.inc.php") > filemtime($userFilename)) { + if ($userFileHandle = fopen($userFilename, 'w+')) { + fwrite($userFileHandle, password_hash($CONFIG['server_auth_pass'], PASSWORD_DEFAULT)); + fclose($userFileHandle); + } + } + } + + if (trim($username) == strtolower($CONFIG['anonusername']) && $CONFIG['anonuser'] != true) { + file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username . ' (' . $CONFIG["anonusername"] . ' is disabled)', FILE_APPEND); + return FALSE; + } + + if ($userFileHandle = fopen($userFilename, 'r')) { + $userFileInfo = fread($userFileHandle, filesize($userFilename)); + fclose($userFileHandle); + if (password_verify($password, $userFileInfo)) { + touch($userFilename); + $ok = TRUE; + } else { + file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username . ' (password incorrect)', FILE_APPEND); + return FALSE; + } + } else { + $ok = FALSE; + } + if ($ok) { + if ($username != 'localuser') { + file_put_contents($logfile, "\n" . format_log_date() . " AUTH OK for: " . $username, FILE_APPEND); + } + return TRUE; + } else { + if (isset($CONFIG['auto_create']) && $CONFIG['auto_create'] == true) { + if ($userFileHandle = @fopen($userFilename, 'w+')) { + fwrite($userFileHandle, password_hash($password, PASSWORD_DEFAULT)); + fclose($userFileHandle); + chmod($userFilename, 0666); + } + $newkey = base64_encode(openssl_random_pseudo_bytes(44)); + if ($userFileHandle = @fopen($keyFilename, 'w+')) { + fwrite($userFileHandle, 'encryptionkey:' . $newkey); + fclose($userFileHandle); + chmod($userFilename, 0666); + } + file_put_contents($logfile, "\n" . format_log_date() . " AUTH OK for: " . $username . ' (auto created user)', FILE_APPEND); + return TRUE; + } else { + file_put_contents($logfile, "\n" . format_log_date() . " AUTH Failed for: " . $username, FILE_APPEND); + return FALSE; + } + } +} + +function check_encryption_groups($request) +{ + global $config_dir; + $groupsFilename = $config_dir . "encryption_ok.txt"; + if ($groupsFileHandle = @fopen($groupsFilename, 'r')) { + while (! feof($groupsFileHandle)) { + $buffer = fgets($groupsFileHandle); + $buffer = str_replace(array( + "\r", + "\n" + ), '', $buffer); + if (! strcmp($buffer, $request)) { + fclose($groupsFileHandle); + return TRUE; + } + } + fclose($groupsFileHandle); + } else { + return FALSE; + } +} + +function set_user_config($username, $request, $newval) +{ + global $config_dir; + $userconfigpath = $config_dir . "userconfig/"; + $username = strtolower($username); + $userFilename = $userconfigpath . $username; + $userData = file($userFilename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $userFileHandle = fopen($userFilename, 'w'); + + $found = 0; + foreach ($userData as $data) { + if (strpos($data, $request . ':') !== FALSE) { + fputs($userFileHandle, $request . ':' . $newval . "\r\n"); + $found = 1; + } else { + fputs($userFileHandle, $data . "\r\n"); + } + } + if ($found == 0) { + fputs($userFileHandle, $request . ':' . $newval . "\r\n"); + } + fclose($userFileHandle); + return; +} + +function get_user_config($username, $request) +{ + global $config_dir; + $userconfigpath = $config_dir . "userconfig/"; + $username = strtolower($username); + $userFilename = $userconfigpath . $username; + + if ($userFileHandle = @fopen($userFilename, 'r')) { + while (! feof($userFileHandle)) { + $buffer = fgets($userFileHandle); + if (strpos($buffer, $request . ':') !== FALSE) { + $userdataline = $buffer; + fclose($userFileHandle); + $userdatafound = explode(':', $userdataline); + return trim($userdatafound[1]); + } + } + fclose($userFileHandle); + return FALSE; + } else { + return FALSE; + } +} + +function is_multibyte($s) +{ + return mb_strlen($s, 'utf-8') < strlen($s); +} + +function check_spam($subject, $from, $newsgroups, $ref, $body, $msgid, $useheaders = false) +{ + global $msgid_generate, $msgid_fqdn, $spooldir, $logdir; + global $CONFIG; + $spamdir = $spooldir . '/spam'; + if (! is_dir($spamdir)) { + mkdir($spamdir); + } + $logfile = $logdir . '/spam.log'; + $spamfile = tempnam($spooldir, 'spam-'); + if ($useheaders) { + // Add headers + $head = ''; + if (trim($subject) != '') { + $head .= 'Subject: ' . $subject . "\r\n"; + } + if (trim($from) != '') { + $head .= 'From: ' . $from . "\r\n"; + } + if (trim($newsgroups) != '') { + $head .= 'Newsgroups: ' . $newsgroups . "\r\n"; + } + if (trim($ref) != '') { + $head .= 'References: ' . $ref . "\r\n"; + } + if (trim($msgid) != '') { + $head .= 'Message-ID: ' . $msgid . "\r\n"; + } + $message = $head . "\r\n" . $body; + } else { + $message = $body; + } + file_put_contents($spamfile, $message); + $spamcommand = $CONFIG['spamc'] . ' -E < ' . $spamfile; + ob_start(); + passthru($spamcommand, $res); + $spamresult = ob_get_contents(); + ob_end_clean(); + $spam_fail = 1; + foreach (explode(PHP_EOL, $spamresult) as $line) { + $line = str_replace(array( + "\n\r", + "\n", + "\r" + ), '', $line); + if (strpos($line, 'X-Spam-Checker-Version:') !== FALSE) { + $spamcheckerversion = $line; + $spam_fail = 0; + } + if (strpos($line, 'X-Spam-Level:') !== FALSE) { + $spamlevel = $line; + } + if ((strpos($line, "X-Spam-Flag: YES") === 0) && ($res !== 1)) { + $res = 1; + } + } + unlink($spamfile); + if ($res === 1) { + file_put_contents($logfile, "\n" . format_log_date() . " spamc:\tSPAM\t" . $msgid . "\t" . $newsgroups . "\t" . preg_replace('/\t/', ' ', $from), FILE_APPEND); + file_put_contents($spamdir . '/' . $msgid, $spamresult); + } else { + file_put_contents($logfile, "\n" . format_log_date() . " spamc:\tHAM\t" . $msgid . "\t" . $newsgroups . "\t" . preg_replace('/\t/', ' ', $from), FILE_APPEND); + } + return array( + 'res' => $res, + 'spamresult' => $spamresult, + 'spamcheckerversion' => $spamcheckerversion, + 'spamlevel' => $spamlevel, + 'spam_fail' => $spam_fail + ); +} + +function format_log_date() +{ + return date('M d H:i:s'); +} + +function create_name_link($name, $data = null) +{ + global $CONFIG; + $name = preg_replace('/\"/', '', $name); + if ($data) { + $data = urlencode(base64_encode($data)); + } + if ((strpos($name, '...@') !== false && (isset($CONFIG['hide_email']) && $CONFIG['hide_email'] == true)) && ! $data) { + $return = '' . substr(htmlspecialchars($name), 0, 20) . ''; + } else { + if (isset($_COOKIE['mail_name'])) { + $return = '' . substr(htmlspecialchars($name), 0, 20) . ''; + } else { + $return = '' . substr(htmlspecialchars($name), 0, 20) . ''; + } + } + return ($return); +} + +function truncate_email($address) +{ + $before_at = explode('@', $address); + $namelen = strlen($before_at[0]); + if ($namelen > 3) { + $endname = $namelen - 3; + if ($endname > 8) + $endname = 8; + if ($endname < 3) + $endname ++; + if ($endname < 3) + $endname ++; + } else { + $endname = $namelen; + } + return substr($before_at[0], 0, $endname) . '...' . substr($address, $namelen, strlen($address)); +} + +function get_date_interval($value) +{ + $current = time(); + $datetime1 = date_create($value); + $datetime2 = date_create("@$current"); + $interval = date_diff($datetime1, $datetime2); + if (! $interval) { + return '(date error)'; + } + $years = $interval->format('%y') . " Years "; + $months = $interval->format('%m') . " Months "; + $days = $interval->format('%d') . " Days "; + $hours = $interval->format('%h') . " Hours "; + $minutes = $interval->format('%i') . " Minutes "; + if ($interval->format('%y') == 1) { + $years = $interval->format('%y') . " Year "; + } + if ($interval->format('%m') == 1) { + $months = $interval->format('%m') . " Month "; + } + if ($interval->format('%d') == 1) { + $days = $interval->format('%d') . " Day "; + } + if ($interval->format('%h') == 1) { + $hours = $interval->format('%h') . " Hour "; + } + if ($interval->format('%i') == 1) { + $minutes = $interval->format('%i') . " Minute "; + } + if ($interval->format('%y') == 0) { + $years = ''; + } + if ($interval->format('%m') == 0) { + $months = ''; + } + if ($interval->format('%d') == 0) { + $days = ''; + } + if ($interval->format('%h') == 0) { + $hours = ''; + } + if ($interval->format('%i') == 0) { + $minutes = ''; + } + if ($years > 0) { + $days = ''; + $hours = ''; + $minutes = ''; + } + if ($months > 0) { + $hours = ''; + $minutes = ''; + } + if ($days > 0) { + $minutes = ''; + } + $variance = $interval->format($years . $months . $days . $hours . $minutes . ' ago'); + if (strlen($variance) < 5) { + $variance = " now"; + } + return $variance; +} + +function get_newsgroups_by_msgid($msgid, $noarray = false) +{ + global $spooldir, $config_dir, $logdir, $CONFIG; + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + if ($memcacheD) { + $key = $memcache_key_prefix . '_' . 'get_newsgroups_by_msgid-' . $msgid; + if ($groups = $memcacheD->get($key)) { + if ($enable_memcache_logging) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $key", FILE_APPEND); + } + } + } + if (! $groups) { + $database = $spooldir . '/articles-overview.db3'; + $table = 'overview'; + $overview_dbh = overview_db_open($database, $table); + $overview_stmt = $overview_dbh->prepare("SELECT newsgroup FROM overview WHERE msgid=:msgid"); + $overview_stmt->bindParam(':msgid', $msgid); + $overview_stmt->execute(); + + $found = false; + $groups = array(); + while ($row = $overview_stmt->fetch()) { + $groups[] = $row['newsgroup']; + $found = true; + } + if (! $found) { + $groups = null; + } + $overview_dbh = null; + if ($groups && $memcacheD) { + $nicole = $memcacheD->add($key, $groups, $memcache_ttl); + if ($enable_memcache_logging && $nicole) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $key", FILE_APPEND); + } + } + } + if ($noarray) { + return (implode(",", $groups)); + } else { + return ($groups); + } +} + +function create_xref_from_msgid($msgid, $thisgroup = null, $thisnumber = null) +{ + global $spooldir, $CONFIG; + $database = $spooldir . '/articles-overview.db3'; + $table = 'overview'; + $overview_dbh = overview_db_open($database, $table); + $overview_stmt = $overview_dbh->prepare("SELECT * FROM overview WHERE msgid=:msgid"); + $overview_stmt->bindParam(':msgid', $msgid); + $overview_stmt->execute(); + + $found = false; + $xref = "Xref: " . $CONFIG['pathhost']; + while ($row = $overview_stmt->fetch()) { + if ($row['newsgroup'] == $thisgroup && $thisgroup != null) { + $found = true; + } + $xref .= ' ' . $row['newsgroup'] . ':' . $row['number']; + } + if (! $found) { + $xref .= ' ' . $thisgroup . ':' . $thisnumber; + } + $overview_dbh = null; + return ($xref); +} + +function get_search_snippet($body, $content_type = '', $content_transfer_encoding = null) +{ + if ($content_transfer_encoding == 'base64') { + $body = base64_decode($body); + } + if ($content_transfer_encoding == 'quoted-printable') { + $body = quoted_printable_decode($body); + } + if ($content_type !== '') { + $mysnippet = recode_charset($body, $content_type, "utf8"); + } else { + $mysnippet = $body; + } + if ($bodyend = strrpos($mysnippet, "\n---\n")) { + $mysnippet = substr($mysnippet, 0, $bodyend); + } else { + if ($bodyend = strrpos($mysnippet, "\n-- ")) { + $mysnippet = substr($mysnippet, 0, $bodyend); + } else { + if ($bodyend = strrpos($mysnippet, "\n.")) { + $mysnippet = substr($mysnippet, 0, $bodyend); + } + } + } + $mysnippet = preg_replace('/\n.{0,5}>(.*)/', '', $mysnippet); + + $snipstart = strpos($mysnippet, ":\n"); + if (substr_count(trim(substr($mysnippet, 0, $snipstart)), "\n") < 2) { + $mysnippet = substr($mysnippet, $snipstart + 1); + } else { + $mysnippet = substr($mysnippet, 0); + } + return $mysnippet; +} + +function mail_db_open($database, $table = 'messages') +{ + try { + $dbh = new PDO('sqlite:' . $database); + } catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit(); + } + $dbh->exec("CREATE TABLE IF NOT EXISTS messages( + id INTEGER PRIMARY KEY, + msgid TEXT UNIQUE, + mail_from TEXT, + mail_viewed TEXT, + rcpt_to TEXT, + rcpt_viewed TEXT, + rcpt_target TEXT, + date TEXT, + subject TEXT, + message TEXT, + from_hide TEXT, + to_hide TEXT)"); + return ($dbh); +} + +function threads_db_open($database, $table = "threads") +{ + try { + $dbh = new PDO('sqlite:' . $database); + } catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit(); + } + $dbh->exec("CREATE TABLE IF NOT EXISTS threads( + id INTEGER PRIMARY KEY, + headers TEXT, + unique (headers))"); + return ($dbh); +} + +function history_db_open($database, $table = 'history') +{ + try { + $dbh = new PDO('sqlite:' . $database); + } catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit(); + } + $dbh->exec("CREATE TABLE IF NOT EXISTS history( + id INTEGER PRIMARY KEY, newsgroup TEXT, number TEXT, msgid TEXT, - status TEXT, - statusdate TEXT, - statusreason TEXT, + status TEXT, + statusdate TEXT, + statusreason TEXT, statusnotes TEXT, - unique (newsgroup, msgid), - unique (newsgroup, number))"); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_status on ' . $table . '(status)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup on ' . $table . '(newsgroup)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_msgid on ' . $table . '(msgid)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup_number on ' . $table . '(newsgroup,number)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_statusdate on ' . $table . '(statusdate)'); - $stmt->execute(); - return ($dbh); -} - -function overview_db_open($database, $table = 'overview') -{ - try { - $dbh = new PDO('sqlite:' . $database); - } catch (PDOException $e) { - echo 'Connection failed: ' . $e->getMessage(); - exit(); - } - $dbh->exec("CREATE TABLE IF NOT EXISTS overview( - id INTEGER PRIMARY KEY, - newsgroup TEXT, - number TEXT, - msgid TEXT, - date TEXT, - datestring TEXT, - name TEXT, - subject TEXT, - refs TEXT, - bytes TEXT, - lines TEXT, - xref TEXT, - unique (newsgroup, msgid), - unique (newsgroup, number))"); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_date on ' . $table . '(date)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup on ' . $table . '(newsgroup)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_msgid on ' . $table . '(msgid)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup_number on ' . $table . '(newsgroup,number)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_name on ' . $table . '(name)'); - $stmt->execute(); - return ($dbh); -} - -function article_db_open($database, $table = 'articles') -{ - global $spooldir, $logdir, $config_name; - $logfile = $logdir . '/debug.log'; - $spoolpath = "/" . preg_replace("/\//", "\/", $spooldir) . "/"; - $group = preg_replace("/\-articles\.db3/", "", $database); - $group = preg_replace($spoolpath, "", $group); - $group = preg_replace("/\//", "", $group); - if (! preg_match('/\-articles\.db3\-new/', $database)) { - if (! get_section_by_group($group, true)) { - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Attempt to create: " . $database . " for: " . $group, FILE_APPEND); - return false; - } - } - try { - $dbh = new PDO('sqlite:' . $database); - } catch (PDOException $e) { - echo 'Connection failed: ' . $e->getMessage(); - exit(); - } - $dbh->exec("CREATE TABLE IF NOT EXISTS articles( - id INTEGER PRIMARY KEY, - newsgroup TEXT, - number TEXT UNIQUE, - msgid TEXT UNIQUE, - date TEXT, - name TEXT, - subject TEXT, - search_snippet TEXT, - article TEXT)"); - - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_number on ' . $table . '(number)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_date on ' . $table . '(date)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_msgid on ' . $table . '(msgid)'); - $stmt->execute(); - $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_name on ' . $table . '(name)'); - $stmt->execute(); - - $dbh->exec("CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5( - newsgroup, - number, - msgid, - date, - name, - subject, - search_snippet)"); - $dbh->exec("CREATE TRIGGER IF NOT EXISTS after_articles_insert AFTER INSERT ON $table BEGIN - INSERT INTO search_fts(newsgroup, number, msgid, date, name, subject, search_snippet) VALUES(new.newsgroup, new.number, new.msgid, new.date, new.name, new.subject, new.search_snippet); - END;"); - $dbh->exec("CREATE TRIGGER IF NOT EXISTS after_articles_delete AFTER DELETE ON $table BEGIN - DELETE FROM search_fts WHERE msgid = old.msgid; - END;"); - return ($dbh); -} - -function np_get_db_article($article, $group, $makearray = 1, $dbh = null) -{ - global $config_dir, $path, $groupconfig, $config_name, $logdir, $spooldir; - $logfile = $logdir . '/newsportal.log'; - - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - - $msg2 = ""; - $closeme = 0; - $ok_article = 0; - // Check memcache - if ($memcacheD) { - $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $article; - if ($msg2 = $memcacheD->get($article_key)) { - $ok_article = 1; - if ($enable_memcache_logging) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $article_key", FILE_APPEND); - } - } - } - if (! $ok_article) { - $database = $spooldir . '/' . $group . '-articles.db3'; - if (! $dbh) { - if (! is_file($database)) { - return FALSE; - } - $dbh = article_db_open($database); - $closeme = 1; - } - // By Message-ID - if (! is_numeric($article)) { - $stmt = $dbh->prepare("SELECT * FROM articles WHERE msgid like :terms"); - $stmt->bindParam(':terms', $article); - $stmt->execute(); - while ($found = $stmt->fetch()) { - $msg2 = $found['article']; - $ok_article = 1; - break; - } - } else { - $stmt = $dbh->prepare("SELECT * FROM articles WHERE number = :terms"); - $stmt->bindParam(':terms', $article); - $stmt->execute(); - while ($found = $stmt->fetch()) { - $msg2 = $found['article']; - $ok_article = 1; - break; - } - } - if ($ok_article == 1 && $memcacheD) { - $nicole = $memcacheD->add($article_key, $msg2, $memcache_ttl); - if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $article_key", FILE_APPEND); - } - } - } - if ($closeme == 1) { - $dbh = null; - } - if ($ok_article !== 1) { - // file_put_contents($logfile, "\n".format_log_date()." ".$config_name." DEBUG: ".$article." from ".$group." not found in database", FILE_APPEND); - return FALSE; - } - // file_put_contents($logfile, "\n".format_log_date()." ".$config_name." DEBUG: fetched: ".$article." from ".$group, FILE_APPEND); - if ($makearray == 1) { - $thisarticle = preg_split("/\r\n|\n|\r/", trim($msg2)); - array_pop($thisarticle); - return $thisarticle; - } else { - return trim($msg2); - } -} - -function get_poster_name($name) -{ - $fromline = address_decode($name, "nowhere"); - if (! isset($fromline[0]["host"])) - $fromline[0]["host"] = ""; - $name_from = $fromline[0]["mailbox"] . "@" . $fromline[0]["host"]; - $name_username = $fromline[0]["mailbox"]; - if (! isset($fromline[0]["personal"])) { - $poster_name = $fromline[0]["mailbox"]; - } else { - $poster_name = $fromline[0]["personal"]; - } - if (trim($poster_name) == '') { - $fromoutput = explode("<", html_entity_decode($name)); - if (strlen($fromoutput[0]) < 1) { - $poster_name = $fromoutput[1]; - } else { - $poster_name = $fromoutput[0]; - } - } - $thisposter['name'] = $poster_name; - $thisposter['from'] = $name_from; - return ($thisposter); -} - -/* - * This function returns false on success - * or return value contains error info - * 'added' etc. - */ -function save_config_value($configfile, $name, $value, $value_unique = false) -{ - global $spooldir; - $return_val = false; - $tempfile = tempnam($spooldir, 'rslight-'); - if (file_exists($tempfile)) { - unlink($tempfile); - } - $lines = file($configfile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - $found = false; - foreach ($lines as $line) { - $current = explode(':', $line); - if ($value_unique && (strcmp($current[1], $value) == 0)) { - // Found value. Write once - if (! $found) { - file_put_contents($tempfile, $name . ":" . $value . "\n", FILE_APPEND); - } - $found = true; - continue; - } - if (strcmp($current[0], $name) == 0) { - // $name matches option. Overwrite - file_put_contents($tempfile, $name . ":" . $value . "\n", FILE_APPEND); - $found = true; - } else { - // $name does not match option. Keep current line - file_put_contents($tempfile, $line . "\n", FILE_APPEND); - } - } - if (! $found) { - // $name not found in options. Add to file. - file_put_contents($tempfile, $name . ":" . $value . "\n", FILE_APPEND); - } - copy($tempfile, $configfile); - unlink($tempfile); - return $return_val; -} - -function get_config_file_value($configfile, $request) -{ - if ($configFileHandle = @fopen($configfile, 'r')) { - while (! feof($configFileHandle)) { - $buffer = fgets($configFileHandle); - if (strpos($buffer, $request . ':') !== FALSE) { - $dataline = $buffer; - fclose($configFileHandle); - $datafound = explode(':', $dataline); - return trim($datafound[1]); - } - } - fclose($configFileHandle); - return FALSE; - } else { - return FALSE; - } -} - -// This function is specific to $config_dir configuration values -function get_config_value($configfile, $request) -{ - global $config_dir; - - if ($configFileHandle = @fopen($config_dir . '/' . $configfile, 'r')) { - while (! feof($configFileHandle)) { - $buffer = fgets($configFileHandle); - if (strpos($buffer, $request . ':') !== FALSE) { - $dataline = $buffer; - fclose($configFileHandle); - $datafound = explode(':', $dataline); - return trim($datafound[1]); - } - } - fclose($configFileHandle); - return FALSE; - } else { - return FALSE; - } -} - -function disable_page_by_user_agent($client_device, $useragent, $script = "Page") -{ - global $logdir, $config_name; - if ($client_device == $useragent) { - $logfile = $logdir . '/device.log'; - file_put_contents($logfile, "\n" . date('M d H:i:s') . " " . $config_name . " " . $script . " disabled for '" . $useragent . "' Exiting...", FILE_APPEND); - if ($client_device == "bot") { - $_SESSION['bot'] = true; - } - return true; - } else { - return false; - } -} - -function throttle_hits($client_device) -{ - global $CONFIG, $OVERRIDES, $logdir, $config_name; - $client_device = get_client_user_agent_info(); - $_SESSION['rsactive'] = true; - - // $loadrate = allowed article request per second - $loadrate = .15; - if ($client_device == "bot") { - $_SESSION['bot'] = 'true'; - if (isset($OVERRIDES['throttle_hits_bot_loadrate']) && trim($OVERRIDES['throttle_hits_bot_loadrate']) != '') { - $loadrate = $OVERRIDES['throttle_hits_bot_loadrate']; - } - } - - $logfile = $logdir . '/newsportal.log'; - if (! isset($_SESSION['starttime'])) { - $_SESSION['starttime'] = time(); - $_SESSION['views'] = 0; - } - $_SESSION['views'] ++; - // $rate = current hits / seconds since start of session - $rate = fdiv($_SESSION['views'], (time() - $_SESSION['starttime'])); - // if $rate > greater than $loadrate, throttle hits - // but allow 50 hits at start of session to allow loading everything - if (($rate > $loadrate) && ($_SESSION['views'] > 50)) { - header("HTTP/1.0 429 Too Many Requests"); - if (! isset($_SESSION['throttled'])) { - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Too many requests from " . $_SERVER['REMOTE_ADDR'] . " throttling" . " (" . $rate . " > " . $loadrate . ")", FILE_APPEND); - $_SESSION['throttled'] = true; - } - exit(0); - } - if (isset($_SESSION['throttled'])) { - unset($_SESSION['throttled']); - } -} - -function get_client_user_agent_info() -{ - global $config_dir; - // Try to get browser info to use for extra formatting of page - $ua = strtolower($_SERVER["HTTP_USER_AGENT"]); - $devices = array( - "bot", - "spider", - "mobile", - "lynx", - "w3m", - "links", - "ipad", - "tablet" - ); - $client_device = "desktop"; - foreach ($devices as $device) { - if (strpos($ua, $device) !== false) { - $client_device = $device; - break; - } - } - if ($client_device == "spider") { - $client_device = "bot"; - } - // Log client device if enabled by semaphore - if (file_exists($config_dir . '/devicelog.enable')) { - $client_ip = getenv("REMOTE_ADDR"); - $logfile = $logdir . '/device.log'; - file_put_contents($logfile, "\n" . date('M d H:i:s') . " " . $config_name . " Client: " . $client_ip . " browser: " . $client_device, FILE_APPEND); - file_put_contents($logfile, "\nFull UA: " . $ua, FILE_APPEND); - } - return $client_device; -} - -function get_user_mail_auth_data($user) -{ - global $spooldir; - $userdata = array(); - $user = strtolower($user); - $pkey_config = get_user_config($user, "pkey"); - if (! isset($_COOKIE['pkey'])) { - $_COOKIE['pkey'] = null; - } - $pkey_cookie = $_COOKIE['pkey']; - if ((! isset($_COOKIE['pkey'])) || $pkey_config == false || $pkey_cookie == false) { - return false; - } - if ($pkey_config == $pkey_cookie) { - $userfile = $spooldir . '/' . $user . '-articleviews.dat'; - if (is_file($userfile)) { - $userdata = unserialize(file_get_contents($userfile)); - if (isset($userdata['DO.NOT.DELETE'])) { - $userdata['DO.NOT.DELETE'] = time(); - } - } else { - $userdata['DO.NOT.DELETE'] = time(); - } - return $userdata; - } - return false; -} - -function write_access_log() -{ - global $logdir; - $accessfile = $logdir . '/access.log'; - $currentPageUrl = $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]; - file_put_contents($accessfile, "\n" . format_log_date() . " " . $currentPageUrl, FILE_APPEND); -} - -function verify_gpg_signature($res, $signed_text) -{ - $result = gnupg_verify($res, $signed_text, false); - if ($result == false) { - return false; - } - if ((($result[0]['summary'] > 3)) || $result[0]['validity'] == 2) { - return false; // Bad signature - } else { - return true; // Good signature - } -} - -function mb_wordwrap($string, $width = 75, $break = "\n", $cut = false) -{ - $string = (string) $string; - if ($string === '') { - return ''; - } - $break = (string) $break; - if ($break === '') { - trigger_error('Break string cannot be empty', E_USER_ERROR); - } - $width = (int) $width; - if ($width === 0 && $cut) { - trigger_error('Cannot force cut when width is zero', E_USER_ERROR); - } - if (strlen($string) === mb_strlen($string)) { - return wordwrap($string, $width, $break, $cut); - } - $stringWidth = mb_strlen($string); - $breakWidth = mb_strlen($break); - $result = ''; - $lastStart = $lastSpace = 0; - for ($current = 0; $current < $stringWidth; $current ++) { - $char = mb_substr($string, $current, 1); - $possibleBreak = $char; - if ($breakWidth !== 1) { - $possibleBreak = mb_substr($string, $current, $breakWidth); - } - if ($possibleBreak === $break) { - $result .= mb_substr($string, $lastStart, $current - $lastStart + $breakWidth); - $current += $breakWidth - 1; - $lastStart = $lastSpace = $current + 1; - continue; - } - if ($char === ' ') { - if ($current - $lastStart >= $width) { - $result .= mb_substr($string, $lastStart, $current - $lastStart) . $break; - $lastStart = $current + 1; - } - $lastSpace = $current; - continue; - } - if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { - $result .= mb_substr($string, $lastStart, $current - $lastStart) . $break; - $lastStart = $lastSpace = $current; - continue; - } - if ($current - $lastStart >= $width && $lastStart < $lastSpace) { - $result .= mb_substr($string, $lastStart, $lastSpace - $lastStart) . $break; - $lastStart = $lastSpace = $lastSpace + 1; - continue; - } - } - if ($lastStart !== $current) { - $result .= mb_substr($string, $lastStart, $current - $lastStart); - } - return $result; -} - -function is_moderated($newsgroups) -{ - global $CONFIG, $OVERRIDES, $spooldir; - $moderated_groups_file = $spooldir . '/moderated_groups.dat'; - $unmoderated_groups_file = $spooldir . '/unmoderated_groups.dat'; - $moderated_groups = array(); - $unmoderated_groups = array(); - - $ngroups = preg_split("/[\s,]+/", $newsgroups); - foreach ($ngroups as $group) { - if (file_exists($moderated_groups_file)) { - $moderated_groups = file($moderated_groups_file, FILE_IGNORE_NEW_LINES); - if (in_array($group, $moderated_groups)) { - return true; - } - } - if (file_exists($unmoderated_groups_file)) { - $unmoderated_groups = file($unmoderated_groups_file, FILE_IGNORE_NEW_LINES); - if (in_array($group, $unmoderated_groups)) { - return false; - } - } - } - if ($CONFIG['remote_server'] == '') { - return false; - } - $ns = nntp2_open(); - if (! $ns) { - return false; - } - - foreach ($ngroups as $group) { - fputs($ns, "list active $group\r\n"); - while ($weg = line_read($ns)) { - if (strcmp($weg, ".") == 0) { - nntp_close($ns); - return false; - } - if (strpos($weg, $group . ' ') !== false) { - if (str_ends_with($weg, 'm')) { - nntp_close($ns); - if (! in_array($newsgroups, $moderated_groups)) { - file_put_contents($moderated_groups_file, $group . "\n", FILE_APPEND); - } - return true; - } else { - nntp_close($ns); - if (! in_array($newsgroups, $unmoderated_groups)) { - file_put_contents($unmoderated_groups_file, $group . "\n", FILE_APPEND); - } - return false; - } - } - } - } - nntp_close($ns); - return false; -} - -function get_next_article_number($group) -{ - $ok_article = get_article_list($group); - sort($ok_article); - $local = $ok_article[key(array_slice($ok_article, - 1, 1, true))]; - if (! is_numeric($local)) { - $local = 0; - } - $local = $local + 1; - if ($local < 1) { - $local = 1; - } - while (is_deleted_post($group, $local)) { - $local ++; - } - return $local; -} - -function check_duplicate_msgid($msgid, $group) -{ - global $spooldir, $logdir; - - $found = false; - - $database = $spooldir . '/articles-overview.db3'; - $table = 'overview'; - $dbh = overview_db_open($database, $table); - $stmt = $dbh->prepare("SELECT * FROM $table WHERE msgid=:msgid AND newsgroup=:newsgroup"); - $stmt->bindParam(':msgid', $msgid); - $stmt->bindParam(':newsgroup', $group); - $stmt->execute(); - while ($row = $stmt->fetch()) { - if ($row['msgid'] == $msgid) { - $found = true; - } - } - $dbh = null; - - $database = $spooldir . '/history.db3'; - $table = 'history'; - $dbh = history_db_open($database, $table); - $stmt = $dbh->prepare("SELECT * FROM $table WHERE msgid=:msgid AND newsgroup=:newsgroup"); - $stmt->bindParam(':msgid', $msgid); - $stmt->bindParam(':newsgroup', $group); - $stmt->execute(); - while ($row = $stmt->fetch()) { - if ($row['msgid'] == $msgid) { - $found = true; - } - } - $dbh = null; - - return $found; -} - -function insert_article_from_array($this_article, $check_duplicates = true) -{ - global $CONFIG, $config_name, $config_dir, $spooldir, $logdir; - $logfile = $logdir . '/spoolnews.log'; - $group = $this_article['group']; - $grouppath = $path . preg_replace('/\./', '/', $group); - - if ($check_duplicates) { - if (check_duplicate_msgid($this_article['mid'], $group)) { - echo "\n(newsportal)Duplicate Message-ID for: " . $group . ":" . $this_article['mid']; - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Duplicate Message-ID for: " . $group . ":" . $this_article['mid'], FILE_APPEND); - return "441 Insert failed (duplicate)\r\n"; - } - } - - if ($this_article['epochdate'] > time()) { - echo "\n(newsportal)Article date in future. Skipping: " . $group . ":" . $this_article['mid']; - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Article date in future. Skipping: " . $group . ":" . $this_article['mid'], FILE_APPEND); - return "441 Insert failed (article date in future)\r\n"; - } - - // Open articles Database - if ($CONFIG['article_database'] == '1') { - $article_dbh = article_db_open($spooldir . '/' . $group . '-articles.db3'); - if (! $article_dbh) { - return "441 Cannot open " . $spooldir . '/' . $group . "-articles.db3\r\n"; - } - $article_sql = 'INSERT OR IGNORE INTO articles(newsgroup, number, msgid, date, name, subject, article, search_snippet) VALUES(?,?,?,?,?,?,?,?)'; - $article_stmt = $article_dbh->prepare($article_sql); - } - // Open overview database - $database = $spooldir . '/articles-overview.db3'; - $table = 'overview'; - $overview_dbh = overview_db_open($database, $table); - if (! $overview_dbh) { - $article_dbh = null; - return "441 Cannot open " . $database . "\r\n"; - } - $overview_sql = 'INSERT OR IGNORE INTO overview(newsgroup, number, msgid, date, datestring, name, subject, refs, bytes, lines, xref) VALUES(?,?,?,?,?,?,?,?,?,?,?)'; - $overview_stmt = $overview_dbh->prepare($overview_sql); - - // Overview - $overview_stmt->execute([ - $group, - $this_article['local'], - $this_article['mid'], - $this_article['epochdate'], - $this_article['stringdate'], - $this_article['from'], - $this_article['subject'], - $this_article['references'], - $this_article['bytes'], - $this_article['lines'], - $this_article['xref'] - ]); - $overview_dbh = null; - $references = ""; - // Articles - if ($CONFIG['article_database'] == '1') { - $article_stmt->execute([ - $group, - $this_article['local'], - $this_article['mid'], - $this_article['epochdate'], - $this_article['from'], - $this_article['subject'], - $this_article['article'], - $this_article['snippet'] - ]); - unlink($grouppath . "/" . $this_article['local']); - $article_dbh = null; - // Add to memcache - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - if ($memcacheD) { - $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $this_article['local']; - $nicole = $memcacheD->add($article_key, $this_article['article'], $memcache_ttl); - if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) (new) $article_key", FILE_APPEND); - } - } - } else { - if ($article_date > time()) - $article_date = time(); - touch($grouppath . "/" . $this_article['local'], $article_date); - } - echo "\nSpooling: " . $group . " " . $this_article['local']; - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Spooling: " . $group . ":" . $this_article['local'], FILE_APPEND); - $status = "spooled"; - $statusdate = time(); - $statusreason = "imported"; - add_to_history($group, $this_article['local'], $this_article['mid'], $status, $statusdate, $statusreason, $statusnotes); -} - -function is_deleted_post($group, $number) -{ - global $spooldir; - $database = $spooldir . '/history.db3'; - $table = 'history'; - $dbh = history_db_open($database, $table); - $stmt = $dbh->prepare("SELECT * FROM $table WHERE newsgroup=:newsgroup AND number=:newsnum"); - $stmt->bindParam(':newsgroup', $group); - $stmt->bindParam(':newsnum', $number); - $stmt->execute(); - $status = false; - while ($row = $stmt->fetch()) { - if ($row['status'] == "deleted") { - $status = "430 Article Deleted"; - break; - } - } - $dbh = null; - return $status; -} - -function add_to_history($group, $number, $msgid, $status, $statusdate, $statusreason = null, $statusnotes = null) -{ - global $spooldir; - $history = $spooldir . '/history.db3'; - $history_dbh = history_db_open($history); - $history_sql = 'INSERT OR REPLACE INTO history(newsgroup, number, msgid, status, statusdate, statusreason, statusnotes) VALUES(?,?,?,?,?,?,?)'; - $history_stmt = $history_dbh->prepare($history_sql); - $history_stmt->execute([ - $group, - $number, - $msgid, - $status, - $statusdate, - $statusreason, - $statusnotes - ]); - $history_dbh = null; -} - -function clear_history_by_group($group) -{ - global $spooldir; - $history = $spooldir . '/history.db3'; - $history_dbh = history_db_open($history); - $clear_stmt = $history_dbh->prepare("DELETE FROM history WHERE newsgroup=:group"); - $clear_stmt->bindParam(':group', $group); - $clear_stmt->execute(); - $history_dbh = null; -} - -/* get_data_from_msgid uses overview database */ -/* get_db_data_from_msgid uses overview database */ -function get_db_data_from_msgid($msgid, $group) -{ - global $spooldir, $config_dir, $logdir; - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - - if ($memcacheD) { - $row_cache = $memcache_key_prefix . '_' . 'get_db_data_from_msgid-' . $msgid; - if ($row = $memcacheD->get($row_cache)) { - if ($enable_memcache_logging) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $row_cache", FILE_APPEND); - } - return $row; - } - } - - $database = $spooldir . '/' . $group . '-articles.db3'; - if (! is_file($database)) { - return false; - } - $articles_dbh = article_db_open($database); - $articles_query = $articles_dbh->prepare('SELECT * FROM articles WHERE msgid=:messageid'); - $articles_query->execute([ - 'messageid' => $msgid - ]); - $found = 0; - while ($row = $articles_query->fetch()) { - $found = 1; - break; - } - $dbh = null; - if ($found) { - if ($memcacheD) { - $nicole = $memcacheD->add($row_cache, $row, $memcache_ttl); - if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $row_cache", FILE_APPEND); - } - } - return $row; - } else { - return false; - } -} - -function get_group_array_from_msgid($msgid) -{ - global $spooldir; - $database = $spooldir . '/articles-overview.db3'; - $overview_dbh = overview_db_open($database); - $overview_query = $overview_dbh->prepare('SELECT * FROM overview WHERE msgid=:messageid'); - $overview_query->execute([ - 'messageid' => $msgid - ]); - $newsgroups = array(); - $found = false; - while ($row = $overview_query->fetch()) { - $newsgroups[] = $row['newsgroup']; - $found = true; - } - $dbh = null; - if ($found) { - return $newsgroups; - } else { - return false; - } -} - -/* get_data_from_msgid uses overview database */ -/* get_db_data_from_msgid uses overview database */ -function get_data_from_msgid($msgid, $thisgroup = null) -{ - global $spooldir, $config_dir, $logdir, $CONFIG; - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - - if ($CONFIG['article_database'] == '1' && isset($thisgroup)) { - return get_db_data_from_msgid($msgid, $thisgroup); - } - - if ($memcacheD) { - $row_cache = $memcache_key_prefix . '_' . 'get_data_from_msgid-' . $msgid; - if ($row = $memcacheD->get($row_cache)) { - if ($enable_memcache_logging) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $row_cache", FILE_APPEND); - } - return $row; - } - } - - $database = $spooldir . '/articles-overview.db3'; - $articles_dbh = overview_db_open($database); - if ($thisgroup != null) { - $articles_query = $articles_dbh->prepare('SELECT * FROM overview WHERE msgid=:messageid AND newsgroup=:newsgroup'); - $articles_query->execute([ - 'messageid' => $msgid, - 'newsgroup' => $thisgroup - ]); - } else { - $articles_query = $articles_dbh->prepare('SELECT * FROM overview WHERE msgid=:messageid'); - $articles_query->execute([ - 'messageid' => $msgid - ]); - } - $found = 0; - while ($row = $articles_query->fetch()) { - $found = 1; - break; - } - $dbh = null; - if ($found) { - if ($memcacheD) { - $nicole = $memcacheD->add($row_cache, $row, $memcache_ttl); - if ($enable_memcache_logging && $nicole) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $row_cache", FILE_APPEND); - } - } - return $row; - } else { - return false; - } -} - -function prune_dir_by_days($path, $days) -{ - if ($filenames = array_diff(scandir($path), array( - '..', - '.' - ))) { - foreach ($filenames as $file) { - $filelastmodified = filemtime($path . $file); - if ((time() - $filelastmodified) > $days * 86400) { - if (is_file($path . $file)) { - unlink($path . $file); - } - } - } - } else { - return false; - } - return true; -} - -function check_registered_email_addresses($email) -{ - global $config_dir; - $users = scandir($config_dir . "/userconfig"); - foreach ($users as $user) { - if (strcmp(get_user_config($user, 'email'), $email) == 0) { - return $user; - } - } - return false; -} - -function send_admin_message($admin, $from, $subject, $message) -{ - global $config_dir, $spooldir; - if (($to = get_config_value('aliases.conf', strtolower($admin))) == false) { - $to = strtolower($admin); - } - $to = trim($to); - $from = $to; - $database = $spooldir . '/mail.db3'; - $dbh = mail_db_open($database); - if (! $dbh) { - echo "Database error\n"; - return false; - } - $date = time(); - $msgid = '<' . md5(strtolower($to) . strtolower($from) . strtolower($subject) . strtolower($message)) . '>'; - $sql = 'INSERT OR IGNORE INTO messages(msgid, mail_from, rcpt_to, rcpt_target, date, subject, message, from_hide, to_hide, mail_viewed, rcpt_viewed) VALUES(?,?,?,?,?,?,?,?,?,?,?)'; - $stmt = $dbh->prepare($sql); - $target = "local"; - $mail_viewed = "true"; - $rcpt_viewed = null; - $q = $stmt->execute([ - $msgid, - $from, - $to, - $target, - $date, - $subject, - $message, - null, - null, - false, - false - ]); - - $dbh = null; - return true; -} - -function delete_message($messageid, $group = null, $overview_dbh = null) -{ - global $logfile, $logdir, $config_dir, $spooldir, $CONFIG, $webserver_group; - if (file_exists($config_dir . '/memcache.inc.php')) { - include $config_dir . '/memcache.inc.php'; - } - if ($group == null) { - $message = get_data_from_msgid($messageid); - $groups = $message['newsgroup']; - $grouplist = preg_split("/( |\,)/", $groups); - } else { - $grouplist[0] = $group; - } - - /* Find section */ - $menulist = file($config_dir . "menu.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - foreach ($grouplist as $group) { - foreach ($menulist as $menu) { - if ($menu[0] == '#') { - continue; - } - $menuitem = explode(':', $menu); - $glfp = fopen($config_dir . $menuitem[0] . "/groups.txt", 'r'); - while ($gl = fgets($glfp)) { - $group_name = preg_split("/( |\t)/", $gl, 2); - if (strtolower(trim($group)) == strtolower(trim($group_name[0]))) { - $config_name = $menuitem[0]; - // echo "\nFOUND: " . $group . " IN: " . $config_name; - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " FOUND: " . $group . " IN: " . $config_name, FILE_APPEND); - break 2; - } - } - } - if ($CONFIG['article_database'] == '1') { - $database = $spooldir . '/' . $group . '-articles.db3'; - if (is_file($database)) { - $articles_dbh = article_db_open($database); - $articles_stmt = $articles_dbh->prepare('DELETE FROM articles WHERE msgid=:messageid'); - $articles_stmt->execute([ - 'messageid' => $messageid - ]); - $articles_dbh = null; - } - } - // Handle overview and history - if ($overview_dbh == null) { - $database = $spooldir . '/articles-overview.db3'; - $overview_dbh = overview_db_open($database); - if (! $overview_dbh) { - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " FAILED opening " . $database, FILE_APPEND); - return; - } - $close_ovdb = true; - } - $overview_stmt_del = $overview_dbh->prepare('DELETE FROM overview WHERE newsgroup=:newsgroup AND msgid=:msgid'); - $overview_query = $overview_dbh->prepare('SELECT * FROM overview WHERE newsgroup=:newsgroup AND msgid=:msgid'); - $overview_query->execute([ - ':newsgroup' => $group, - ':msgid' => $messageid - ]); - $grouppath = preg_replace('/\./', '/', $group); - $status = "deleted"; - $statusdate = time(); - $statusreason = "nocem"; - $statusnotes = null; - while ($row = $overview_query->fetch()) { - if (isset($row['number'])) { - // echo "\nFOUND: " . $messageid . " IN: " . $group; - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " DELETING: " . $messageid . " IN: " . $group, FILE_APPEND); - } - if (is_file($spooldir . '/articles/' . $grouppath . '/' . $row['number'])) { - unlink($spooldir . '/articles/' . $grouppath . '/' . $row['number']); - } - delete_message_from_overboard($config_name, $group, $messageid); - add_to_history($group, $row['number'], $row['msgid'], $status, $statusdate, $statusreason, $statusnotes); - thread_cache_removearticle($group, $row['number']); - $overview_stmt_del->execute([ - ':newsgroup' => $group, - ':msgid' => $messageid - ]); - // Delete article from memcache - if ($memcacheD) { - $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $row['number']; - $result = $memcacheD->delete($article_key); - if ($enable_memcache_logging) { - if ($result) { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Deleted $article_key", FILE_APPEND); - } else { - file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Failed to delete (or not found) $article_key", FILE_APPEND); - } - } - } - } - } - if ($close_ovdb) { - $overview_dbh = null; - } - return; -} - -// This function returns FALSE if article is OK -// Else returns a string with reason for failure -function check_article_integrity($rawmessage) -{ - global $CONFIG, $logfile; - $returnval = false; - $count_rawmessage = count($rawmessage); - $message = new messageType(); - $rawheader = array(); - $i = 0; - while ($rawmessage[$i] != "") { - $rawheader[] = $rawmessage[$i]; - $i ++; - } - // Parse the Header: - $message->header = parse_header($rawheader); - // Check if date is in future - if ($message->header->date > time()) { - $returnval = " Skipping message (date in future): " . $message->header->id; - return $returnval; - } - // Now we know if the message is a mime-multipart message: - $content_type = explode("/", $message->header->content_type[0]); - if ($content_type[0] == "multipart") { - $message->header->content_type = array(); - // We have multible bodies, so we split the message into its parts - $boundary = "--" . $message->header->content_type_boundary; - // lets find the first part - while ($rawmessage[$i] != $boundary) { - $i ++; - if ($i > $count_rawmessage) { - $returnval = " Skipping malformed message: " . $message->header->id; - return $returnval; - } - } - } - return $returnval; -} - -function wrap_post($body) -{ - $line_length = 72; - $lines = preg_split("/\n/", $body); - $wrapped = ''; - foreach ($lines as $line) { - if (trim($line) == '') { - $wrapped .= "\n"; - continue; - } - if ($line[0] == '>') { - $depth = 0; - while ($line[$depth] == '>') { - $depth ++; - if ($depth > 30) { - break; - } - } - if (strlen($line) > $line_length) { - // HERE is where we wrap quoted lines (not so easy) - $start = substr($line, 0, $depth + 1); - $end = substr($line, $depth + 1); - $line_wrapped = $start . mb_wordwrap($end, $line_length); - $line_wrapped = preg_split("/\n/", $line_wrapped); - foreach ($line_wrapped as $lw) { - if ($lw[0] != '>') { - $i = 0; - while ($i < $depth) { - $wrapped .= '>'; - $i ++; - } - $wrapped .= ' '; - } - $wrapped .= $lw . "\n"; - } - } else { - $wrapped .= $line . "\n"; - } - } else { - if (strlen($line) > $line_length) { - // HERE is where we wrap NON quoted lines (easy) - $wrapped .= mb_wordwrap($line, $line_length) . "\n"; - } else { - $wrapped .= $line . "\n"; - } - } - } - return $wrapped; -} - -function delete_message_from_overboard($config_name, $group, $messageid) -{ - GLOBAL $spooldir; - $cachefile = $spooldir . "/" . $config_name . "-overboard.dat"; - if (is_file($cachefile)) { - $cached_overboard = unserialize(file_get_contents($cachefile)); - if ($target = $cached_overboard['msgids'][$messageid]) { - unset($cached_overboard['threads'][$target['date']]); - unset($cached_overboard['msgids'][$messageid]); - unset($cached_overboard['threadlink'][$messageid]); - file_put_contents($cachefile, serialize($cached_overboard)); - } - } - $cachefile = $spooldir . "/" . $group . "-overboard.dat"; - if (is_file($cachefile)) { - $cached_overboard = unserialize(file_get_contents($cachefile)); - if ($target = $cached_overboard['msgids'][$messageid]) { - unset($cached_overboard['threads'][$target['date']]); - unset($cached_overboard['msgids'][$messageid]); - unset($cached_overboard['threadlink'][$messageid]); - file_put_contents($cachefile, serialize($cached_overboard)); - } - } -} \ No newline at end of file + unique (newsgroup, msgid), + unique (newsgroup, number))"); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_status on ' . $table . '(status)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup on ' . $table . '(newsgroup)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_msgid on ' . $table . '(msgid)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup_number on ' . $table . '(newsgroup,number)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_statusdate on ' . $table . '(statusdate)'); + $stmt->execute(); + return ($dbh); +} + +function overview_db_open($database, $table = 'overview') +{ + try { + $dbh = new PDO('sqlite:' . $database); + } catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit(); + } + $dbh->exec("CREATE TABLE IF NOT EXISTS overview( + id INTEGER PRIMARY KEY, + newsgroup TEXT, + number TEXT, + msgid TEXT, + date TEXT, + datestring TEXT, + name TEXT, + subject TEXT, + refs TEXT, + bytes TEXT, + lines TEXT, + xref TEXT, + unique (newsgroup, msgid), + unique (newsgroup, number))"); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_date on ' . $table . '(date)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup on ' . $table . '(newsgroup)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_msgid on ' . $table . '(msgid)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_newsgroup_number on ' . $table . '(newsgroup,number)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS id_name on ' . $table . '(name)'); + $stmt->execute(); + return ($dbh); +} + +function article_db_open($database, $table = 'articles') +{ + global $spooldir, $logdir, $config_name; + $logfile = $logdir . '/debug.log'; + $spoolpath = "/" . preg_replace("/\//", "\/", $spooldir) . "/"; + $group = preg_replace("/\-articles\.db3/", "", $database); + $group = preg_replace($spoolpath, "", $group); + $group = preg_replace("/\//", "", $group); + if (! preg_match('/\-articles\.db3\-new/', $database)) { + if (! get_section_by_group($group, true)) { + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Attempt to create: " . $database . " for: " . $group, FILE_APPEND); + return false; + } + } + try { + $dbh = new PDO('sqlite:' . $database); + } catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit(); + } + $dbh->exec("CREATE TABLE IF NOT EXISTS articles( + id INTEGER PRIMARY KEY, + newsgroup TEXT, + number TEXT UNIQUE, + msgid TEXT UNIQUE, + date TEXT, + name TEXT, + subject TEXT, + search_snippet TEXT, + article TEXT)"); + + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_number on ' . $table . '(number)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_date on ' . $table . '(date)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_msgid on ' . $table . '(msgid)'); + $stmt->execute(); + $stmt = $dbh->query('CREATE INDEX IF NOT EXISTS db_name on ' . $table . '(name)'); + $stmt->execute(); + + $dbh->exec("CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5( + newsgroup, + number, + msgid, + date, + name, + subject, + search_snippet)"); + $dbh->exec("CREATE TRIGGER IF NOT EXISTS after_articles_insert AFTER INSERT ON $table BEGIN + INSERT INTO search_fts(newsgroup, number, msgid, date, name, subject, search_snippet) VALUES(new.newsgroup, new.number, new.msgid, new.date, new.name, new.subject, new.search_snippet); + END;"); + $dbh->exec("CREATE TRIGGER IF NOT EXISTS after_articles_delete AFTER DELETE ON $table BEGIN + DELETE FROM search_fts WHERE msgid = old.msgid; + END;"); + return ($dbh); +} + +function np_get_db_article($article, $group, $makearray = 1, $dbh = null) +{ + global $config_dir, $path, $groupconfig, $config_name, $logdir, $spooldir; + $logfile = $logdir . '/newsportal.log'; + + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + + $msg2 = ""; + $closeme = 0; + $ok_article = 0; + // Check memcache + if ($memcacheD) { + $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $article; + if ($msg2 = $memcacheD->get($article_key)) { + $ok_article = 1; + if ($enable_memcache_logging) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $article_key", FILE_APPEND); + } + } + } + if (! $ok_article) { + $database = $spooldir . '/' . $group . '-articles.db3'; + if (! $dbh) { + if (! is_file($database)) { + return FALSE; + } + $dbh = article_db_open($database); + $closeme = 1; + } + // By Message-ID + if (! is_numeric($article)) { + $stmt = $dbh->prepare("SELECT * FROM articles WHERE msgid like :terms"); + $stmt->bindParam(':terms', $article); + $stmt->execute(); + while ($found = $stmt->fetch()) { + $msg2 = $found['article']; + $ok_article = 1; + break; + } + } else { + $stmt = $dbh->prepare("SELECT * FROM articles WHERE number = :terms"); + $stmt->bindParam(':terms', $article); + $stmt->execute(); + while ($found = $stmt->fetch()) { + $msg2 = $found['article']; + $ok_article = 1; + break; + } + } + if ($ok_article == 1 && $memcacheD) { + $nicole = $memcacheD->add($article_key, $msg2, $memcache_ttl); + if ($enable_memcache_logging && $nicole) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $article_key", FILE_APPEND); + } + } + } + if ($closeme == 1) { + $dbh = null; + } + if ($ok_article !== 1) { + // file_put_contents($logfile, "\n".format_log_date()." ".$config_name." DEBUG: ".$article." from ".$group." not found in database", FILE_APPEND); + return FALSE; + } + // file_put_contents($logfile, "\n".format_log_date()." ".$config_name." DEBUG: fetched: ".$article." from ".$group, FILE_APPEND); + if ($makearray == 1) { + $thisarticle = preg_split("/\r\n|\n|\r/", trim($msg2)); + array_pop($thisarticle); + return $thisarticle; + } else { + return trim($msg2); + } +} + +function get_poster_name($name) +{ + $fromline = address_decode($name, "nowhere"); + if (! isset($fromline[0]["host"])) + $fromline[0]["host"] = ""; + $name_from = $fromline[0]["mailbox"] . "@" . $fromline[0]["host"]; + $name_username = $fromline[0]["mailbox"]; + if (! isset($fromline[0]["personal"])) { + $poster_name = $fromline[0]["mailbox"]; + } else { + $poster_name = $fromline[0]["personal"]; + } + if (trim($poster_name) == '') { + $fromoutput = explode("<", html_entity_decode($name)); + if (strlen($fromoutput[0]) < 1) { + $poster_name = $fromoutput[1]; + } else { + $poster_name = $fromoutput[0]; + } + } + $thisposter['name'] = $poster_name; + $thisposter['from'] = $name_from; + return ($thisposter); +} + +/* + * This function returns false on success + * or return value contains error info + * 'added' etc. + */ +function save_config_value($configfile, $name, $value, $value_unique = false) +{ + global $spooldir; + $return_val = false; + $tempfile = tempnam($spooldir, 'rslight-'); + if (file_exists($tempfile)) { + unlink($tempfile); + } + $lines = file($configfile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $found = false; + foreach ($lines as $line) { + $current = explode(':', $line); + if ($value_unique && (strcmp($current[1], $value) == 0)) { + // Found value. Write once + if (! $found) { + file_put_contents($tempfile, $name . ":" . $value . "\n", FILE_APPEND); + } + $found = true; + continue; + } + if (strcmp($current[0], $name) == 0) { + // $name matches option. Overwrite + file_put_contents($tempfile, $name . ":" . $value . "\n", FILE_APPEND); + $found = true; + } else { + // $name does not match option. Keep current line + file_put_contents($tempfile, $line . "\n", FILE_APPEND); + } + } + if (! $found) { + // $name not found in options. Add to file. + file_put_contents($tempfile, $name . ":" . $value . "\n", FILE_APPEND); + } + copy($tempfile, $configfile); + unlink($tempfile); + return $return_val; +} + +function get_config_file_value($configfile, $request) +{ + if ($configFileHandle = @fopen($configfile, 'r')) { + while (! feof($configFileHandle)) { + $buffer = fgets($configFileHandle); + if (strpos($buffer, $request . ':') !== FALSE) { + $dataline = $buffer; + fclose($configFileHandle); + $datafound = explode(':', $dataline); + return trim($datafound[1]); + } + } + fclose($configFileHandle); + return FALSE; + } else { + return FALSE; + } +} + +// This function is specific to $config_dir configuration values +function get_config_value($configfile, $request) +{ + global $config_dir; + + if ($configFileHandle = @fopen($config_dir . '/' . $configfile, 'r')) { + while (! feof($configFileHandle)) { + $buffer = fgets($configFileHandle); + if (strpos($buffer, $request . ':') !== FALSE) { + $dataline = $buffer; + fclose($configFileHandle); + $datafound = explode(':', $dataline); + return trim($datafound[1]); + } + } + fclose($configFileHandle); + return FALSE; + } else { + return FALSE; + } +} + +function disable_page_by_user_agent($client_device, $useragent, $script = "Page") +{ + global $logdir, $config_name; + if ($client_device == $useragent) { + $logfile = $logdir . '/device.log'; + file_put_contents($logfile, "\n" . date('M d H:i:s') . " " . $config_name . " " . $script . " disabled for '" . $useragent . "' Exiting...", FILE_APPEND); + if ($client_device == "bot") { + $_SESSION['bot'] = true; + } + return true; + } else { + return false; + } +} + +function throttle_hits($client_device) +{ + global $CONFIG, $OVERRIDES, $logdir, $config_name; + $logfile = $logdir . '/newsportal.log'; + + $client_device = get_client_user_agent_info(); + $client_device = strtolower($client_device); + $useragent = strtolower($useragent); + + $_SESSION['rsactive'] = true; + + if (isset($OVERRIDES['block_by_user_agent'])) { + $ua = strtolower($_SERVER["HTTP_USER_AGENT"]); + foreach($OVERRIDES['block_by_user_agent'] as $user_agent) { + if(stripos($ua, $user_agent) !== false) { + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Blocking " . $_SERVER['REMOTE_ADDR'] . " '" . $user_agent . "' listed in block list", FILE_APPEND); + $_SESSION['throttled'] = true; + header("HTTP/1.0 403 Forbidden"); + exit; + } + } + } + // $loadrate = allowed article request per second + $loadrate = .15; + if ($client_device == "bot") { + $_SESSION['bot'] = 'true'; + if (isset($OVERRIDES['throttle_hits_bot_loadrate']) && trim($OVERRIDES['throttle_hits_bot_loadrate']) != '') { + $loadrate = $OVERRIDES['throttle_hits_bot_loadrate']; + } + } + + if (! isset($_SESSION['starttime'])) { + $_SESSION['starttime'] = time(); + $_SESSION['views'] = 0; + } + $_SESSION['views'] ++; + // $rate = current hits / seconds since start of session + $rate = fdiv($_SESSION['views'], (time() - $_SESSION['starttime'])); + // if $rate > greater than $loadrate, throttle hits + // but allow 50 hits at start of session to allow loading everything + if (($rate > $loadrate) && ($_SESSION['views'] > 50)) { + header("HTTP/1.0 429 Too Many Requests"); + if (! isset($_SESSION['throttled'])) { + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Too many requests from " . $_SERVER['REMOTE_ADDR'] . " throttling" . " (" . $rate . " > " . $loadrate . ")", FILE_APPEND); + $_SESSION['throttled'] = true; + } + exit(0); + } + if (isset($_SESSION['throttled'])) { + unset($_SESSION['throttled']); + } +} + +function get_client_user_agent_info() +{ + global $config_dir; + // Try to get browser info to use for extra formatting of page + $ua = strtolower($_SERVER["HTTP_USER_AGENT"]); + $devices = array( + "bot", + "spider", + "mobile", + "lynx", + "w3m", + "links", + "ipad", + "tablet" + ); + $client_device = "desktop"; + foreach ($devices as $device) { + if (strpos($ua, $device) !== false) { + $client_device = $device; + break; + } + } + if ($client_device == "spider") { + $client_device = "bot"; + } + // Log client device if enabled by semaphore + if (file_exists($config_dir . '/devicelog.enable')) { + $client_ip = getenv("REMOTE_ADDR"); + $logfile = $logdir . '/device.log'; + file_put_contents($logfile, "\n" . date('M d H:i:s') . " " . $config_name . " Client: " . $client_ip . " browser: " . $client_device, FILE_APPEND); + file_put_contents($logfile, "\nFull UA: " . $ua, FILE_APPEND); + } + return $client_device; +} + +function get_user_mail_auth_data($user) +{ + global $spooldir; + $userdata = array(); + $user = strtolower($user); + $pkey_config = get_user_config($user, "pkey"); + if (! isset($_COOKIE['pkey'])) { + $_COOKIE['pkey'] = null; + } + $pkey_cookie = $_COOKIE['pkey']; + if ((! isset($_COOKIE['pkey'])) || $pkey_config == false || $pkey_cookie == false) { + return false; + } + if ($pkey_config == $pkey_cookie) { + $userfile = $spooldir . '/' . $user . '-articleviews.dat'; + if (is_file($userfile)) { + $userdata = unserialize(file_get_contents($userfile)); + if (isset($userdata['DO.NOT.DELETE'])) { + $userdata['DO.NOT.DELETE'] = time(); + } + } else { + $userdata['DO.NOT.DELETE'] = time(); + } + return $userdata; + } + return false; +} + +function write_access_log() +{ + global $logdir; + $accessfile = $logdir . '/access.log'; + $currentPageUrl = $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]; + file_put_contents($accessfile, "\n" . format_log_date() . " " . $currentPageUrl, FILE_APPEND); +} + +function verify_gpg_signature($res, $signed_text) +{ + $result = gnupg_verify($res, $signed_text, false); + if ($result == false) { + return false; + } + if ((($result[0]['summary'] > 3)) || $result[0]['validity'] == 2) { + return false; // Bad signature + } else { + return true; // Good signature + } +} + +function mb_wordwrap($string, $width = 75, $break = "\n", $cut = false) +{ + $string = (string) $string; + if ($string === '') { + return ''; + } + $break = (string) $break; + if ($break === '') { + trigger_error('Break string cannot be empty', E_USER_ERROR); + } + $width = (int) $width; + if ($width === 0 && $cut) { + trigger_error('Cannot force cut when width is zero', E_USER_ERROR); + } + if (strlen($string) === mb_strlen($string)) { + return wordwrap($string, $width, $break, $cut); + } + $stringWidth = mb_strlen($string); + $breakWidth = mb_strlen($break); + $result = ''; + $lastStart = $lastSpace = 0; + for ($current = 0; $current < $stringWidth; $current ++) { + $char = mb_substr($string, $current, 1); + $possibleBreak = $char; + if ($breakWidth !== 1) { + $possibleBreak = mb_substr($string, $current, $breakWidth); + } + if ($possibleBreak === $break) { + $result .= mb_substr($string, $lastStart, $current - $lastStart + $breakWidth); + $current += $breakWidth - 1; + $lastStart = $lastSpace = $current + 1; + continue; + } + if ($char === ' ') { + if ($current - $lastStart >= $width) { + $result .= mb_substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $current + 1; + } + $lastSpace = $current; + continue; + } + if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { + $result .= mb_substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $lastSpace = $current; + continue; + } + if ($current - $lastStart >= $width && $lastStart < $lastSpace) { + $result .= mb_substr($string, $lastStart, $lastSpace - $lastStart) . $break; + $lastStart = $lastSpace = $lastSpace + 1; + continue; + } + } + if ($lastStart !== $current) { + $result .= mb_substr($string, $lastStart, $current - $lastStart); + } + return $result; +} + +function is_moderated($newsgroups) +{ + global $CONFIG, $OVERRIDES, $spooldir; + $moderated_groups_file = $spooldir . '/moderated_groups.dat'; + $unmoderated_groups_file = $spooldir . '/unmoderated_groups.dat'; + $moderated_groups = array(); + $unmoderated_groups = array(); + + $ngroups = preg_split("/[\s,]+/", $newsgroups); + foreach ($ngroups as $group) { + if (file_exists($moderated_groups_file)) { + $moderated_groups = file($moderated_groups_file, FILE_IGNORE_NEW_LINES); + if (in_array($group, $moderated_groups)) { + return true; + } + } + if (file_exists($unmoderated_groups_file)) { + $unmoderated_groups = file($unmoderated_groups_file, FILE_IGNORE_NEW_LINES); + if (in_array($group, $unmoderated_groups)) { + return false; + } + } + } + if ($CONFIG['remote_server'] == '') { + return false; + } + $ns = nntp2_open(); + if (! $ns) { + return false; + } + + foreach ($ngroups as $group) { + fputs($ns, "list active $group\r\n"); + while ($weg = line_read($ns)) { + if (strcmp($weg, ".") == 0) { + nntp_close($ns); + return false; + } + if (strpos($weg, $group . ' ') !== false) { + if (str_ends_with($weg, 'm')) { + nntp_close($ns); + if (! in_array($newsgroups, $moderated_groups)) { + file_put_contents($moderated_groups_file, $group . "\n", FILE_APPEND); + } + return true; + } else { + nntp_close($ns); + if (! in_array($newsgroups, $unmoderated_groups)) { + file_put_contents($unmoderated_groups_file, $group . "\n", FILE_APPEND); + } + return false; + } + } + } + } + nntp_close($ns); + return false; +} + +function get_next_article_number($group) +{ + $ok_article = get_article_list($group); + sort($ok_article); + $local = $ok_article[key(array_slice($ok_article, - 1, 1, true))]; + if (! is_numeric($local)) { + $local = 0; + } + $local = $local + 1; + if ($local < 1) { + $local = 1; + } + while (is_deleted_post($group, $local)) { + $local ++; + } + return $local; +} + +function check_duplicate_msgid($msgid, $group) +{ + global $spooldir, $logdir; + + $found = false; + + $database = $spooldir . '/articles-overview.db3'; + $table = 'overview'; + $dbh = overview_db_open($database, $table); + $stmt = $dbh->prepare("SELECT * FROM $table WHERE msgid=:msgid AND newsgroup=:newsgroup"); + $stmt->bindParam(':msgid', $msgid); + $stmt->bindParam(':newsgroup', $group); + $stmt->execute(); + while ($row = $stmt->fetch()) { + if ($row['msgid'] == $msgid) { + $found = true; + } + } + $dbh = null; + + $database = $spooldir . '/history.db3'; + $table = 'history'; + $dbh = history_db_open($database, $table); + $stmt = $dbh->prepare("SELECT * FROM $table WHERE msgid=:msgid AND newsgroup=:newsgroup"); + $stmt->bindParam(':msgid', $msgid); + $stmt->bindParam(':newsgroup', $group); + $stmt->execute(); + while ($row = $stmt->fetch()) { + if ($row['msgid'] == $msgid) { + $found = true; + } + } + $dbh = null; + + return $found; +} + +function insert_article_from_array($this_article, $check_duplicates = true) +{ + global $CONFIG, $config_name, $config_dir, $spooldir, $logdir; + $logfile = $logdir . '/spoolnews.log'; + $group = $this_article['group']; + $grouppath = $path . preg_replace('/\./', '/', $group); + + if ($check_duplicates) { + if (check_duplicate_msgid($this_article['mid'], $group)) { + echo "\n(newsportal)Duplicate Message-ID for: " . $group . ":" . $this_article['mid']; + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Duplicate Message-ID for: " . $group . ":" . $this_article['mid'], FILE_APPEND); + return "441 Insert failed (duplicate)\r\n"; + } + } + + if ($this_article['epochdate'] > time()) { + echo "\n(newsportal)Article date in future. Skipping: " . $group . ":" . $this_article['mid']; + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Article date in future. Skipping: " . $group . ":" . $this_article['mid'], FILE_APPEND); + return "441 Insert failed (article date in future)\r\n"; + } + + // Open articles Database + if ($CONFIG['article_database'] == '1') { + $article_dbh = article_db_open($spooldir . '/' . $group . '-articles.db3'); + if (! $article_dbh) { + return "441 Cannot open " . $spooldir . '/' . $group . "-articles.db3\r\n"; + } + $article_sql = 'INSERT OR IGNORE INTO articles(newsgroup, number, msgid, date, name, subject, article, search_snippet) VALUES(?,?,?,?,?,?,?,?)'; + $article_stmt = $article_dbh->prepare($article_sql); + } + // Open overview database + $database = $spooldir . '/articles-overview.db3'; + $table = 'overview'; + $overview_dbh = overview_db_open($database, $table); + if (! $overview_dbh) { + $article_dbh = null; + return "441 Cannot open " . $database . "\r\n"; + } + $overview_sql = 'INSERT OR IGNORE INTO overview(newsgroup, number, msgid, date, datestring, name, subject, refs, bytes, lines, xref) VALUES(?,?,?,?,?,?,?,?,?,?,?)'; + $overview_stmt = $overview_dbh->prepare($overview_sql); + + // Overview + $overview_stmt->execute([ + $group, + $this_article['local'], + $this_article['mid'], + $this_article['epochdate'], + $this_article['stringdate'], + $this_article['from'], + $this_article['subject'], + $this_article['references'], + $this_article['bytes'], + $this_article['lines'], + $this_article['xref'] + ]); + $overview_dbh = null; + $references = ""; + // Articles + if ($CONFIG['article_database'] == '1') { + $article_stmt->execute([ + $group, + $this_article['local'], + $this_article['mid'], + $this_article['epochdate'], + $this_article['from'], + $this_article['subject'], + $this_article['article'], + $this_article['snippet'] + ]); + unlink($grouppath . "/" . $this_article['local']); + $article_dbh = null; + // Add to memcache + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + if ($memcacheD) { + $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $this_article['local']; + $nicole = $memcacheD->add($article_key, $this_article['article'], $memcache_ttl); + if ($enable_memcache_logging && $nicole) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) (new) $article_key", FILE_APPEND); + } + } + } else { + if ($article_date > time()) + $article_date = time(); + touch($grouppath . "/" . $this_article['local'], $article_date); + } + echo "\nSpooling: " . $group . " " . $this_article['local']; + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Spooling: " . $group . ":" . $this_article['local'], FILE_APPEND); + $status = "spooled"; + $statusdate = time(); + $statusreason = "imported"; + add_to_history($group, $this_article['local'], $this_article['mid'], $status, $statusdate, $statusreason, $statusnotes); +} + +function is_deleted_post($group, $number) +{ + global $spooldir; + $database = $spooldir . '/history.db3'; + $table = 'history'; + $dbh = history_db_open($database, $table); + $stmt = $dbh->prepare("SELECT * FROM $table WHERE newsgroup=:newsgroup AND number=:newsnum"); + $stmt->bindParam(':newsgroup', $group); + $stmt->bindParam(':newsnum', $number); + $stmt->execute(); + $status = false; + while ($row = $stmt->fetch()) { + if ($row['status'] == "deleted") { + $status = "430 Article Deleted"; + break; + } + } + $dbh = null; + return $status; +} + +function add_to_history($group, $number, $msgid, $status, $statusdate, $statusreason = null, $statusnotes = null) +{ + global $spooldir; + $history = $spooldir . '/history.db3'; + $history_dbh = history_db_open($history); + $history_sql = 'INSERT OR REPLACE INTO history(newsgroup, number, msgid, status, statusdate, statusreason, statusnotes) VALUES(?,?,?,?,?,?,?)'; + $history_stmt = $history_dbh->prepare($history_sql); + $history_stmt->execute([ + $group, + $number, + $msgid, + $status, + $statusdate, + $statusreason, + $statusnotes + ]); + $history_dbh = null; +} + +function clear_history_by_group($group) +{ + global $spooldir; + $history = $spooldir . '/history.db3'; + $history_dbh = history_db_open($history); + $clear_stmt = $history_dbh->prepare("DELETE FROM history WHERE newsgroup=:group"); + $clear_stmt->bindParam(':group', $group); + $clear_stmt->execute(); + $history_dbh = null; +} + +/* get_data_from_msgid uses overview database */ +/* get_db_data_from_msgid uses overview database */ +function get_db_data_from_msgid($msgid, $group) +{ + global $spooldir, $config_dir, $logdir; + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + + if ($memcacheD) { + $row_cache = $memcache_key_prefix . '_' . 'get_db_data_from_msgid-' . $msgid; + if ($row = $memcacheD->get($row_cache)) { + if ($enable_memcache_logging) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $row_cache", FILE_APPEND); + } + return $row; + } + } + + $database = $spooldir . '/' . $group . '-articles.db3'; + if (! is_file($database)) { + return false; + } + $articles_dbh = article_db_open($database); + $articles_query = $articles_dbh->prepare('SELECT * FROM articles WHERE msgid=:messageid'); + $articles_query->execute([ + 'messageid' => $msgid + ]); + $found = 0; + while ($row = $articles_query->fetch()) { + $found = 1; + break; + } + $dbh = null; + if ($found) { + if ($memcacheD) { + $nicole = $memcacheD->add($row_cache, $row, $memcache_ttl); + if ($enable_memcache_logging && $nicole) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $row_cache", FILE_APPEND); + } + } + return $row; + } else { + return false; + } +} + +function get_group_array_from_msgid($msgid) +{ + global $spooldir; + $database = $spooldir . '/articles-overview.db3'; + $overview_dbh = overview_db_open($database); + $overview_query = $overview_dbh->prepare('SELECT * FROM overview WHERE msgid=:messageid'); + $overview_query->execute([ + 'messageid' => $msgid + ]); + $newsgroups = array(); + $found = false; + while ($row = $overview_query->fetch()) { + $newsgroups[] = $row['newsgroup']; + $found = true; + } + $dbh = null; + if ($found) { + return $newsgroups; + } else { + return false; + } +} + +/* get_data_from_msgid uses overview database */ +/* get_db_data_from_msgid uses overview database */ +function get_data_from_msgid($msgid, $thisgroup = null) +{ + global $spooldir, $config_dir, $logdir, $CONFIG; + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + + if ($CONFIG['article_database'] == '1' && isset($thisgroup)) { + return get_db_data_from_msgid($msgid, $thisgroup); + } + + if ($memcacheD) { + $row_cache = $memcache_key_prefix . '_' . 'get_data_from_msgid-' . $msgid; + if ($row = $memcacheD->get($row_cache)) { + if ($enable_memcache_logging) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache hit) $row_cache", FILE_APPEND); + } + return $row; + } + } + + $database = $spooldir . '/articles-overview.db3'; + $articles_dbh = overview_db_open($database); + if ($thisgroup != null) { + $articles_query = $articles_dbh->prepare('SELECT * FROM overview WHERE msgid=:messageid AND newsgroup=:newsgroup'); + $articles_query->execute([ + 'messageid' => $msgid, + 'newsgroup' => $thisgroup + ]); + } else { + $articles_query = $articles_dbh->prepare('SELECT * FROM overview WHERE msgid=:messageid'); + $articles_query->execute([ + 'messageid' => $msgid + ]); + } + $found = 0; + while ($row = $articles_query->fetch()) { + $found = 1; + break; + } + $dbh = null; + if ($found) { + if ($memcacheD) { + $nicole = $memcacheD->add($row_cache, $row, $memcache_ttl); + if ($enable_memcache_logging && $nicole) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " (cache write) $row_cache", FILE_APPEND); + } + } + return $row; + } else { + return false; + } +} + +function prune_dir_by_days($path, $days) +{ + if ($filenames = array_diff(scandir($path), array( + '..', + '.' + ))) { + foreach ($filenames as $file) { + $filelastmodified = filemtime($path . $file); + if ((time() - $filelastmodified) > $days * 86400) { + if (is_file($path . $file)) { + unlink($path . $file); + } + } + } + } else { + return false; + } + return true; +} + +function check_registered_email_addresses($email) +{ + global $config_dir; + $users = scandir($config_dir . "/userconfig"); + foreach ($users as $user) { + if (strcmp(get_user_config($user, 'email'), $email) == 0) { + return $user; + } + } + return false; +} + +function send_admin_message($admin, $from, $subject, $message) +{ + global $config_dir, $spooldir; + if (($to = get_config_value('aliases.conf', strtolower($admin))) == false) { + $to = strtolower($admin); + } + $to = trim($to); + $from = $to; + $database = $spooldir . '/mail.db3'; + $dbh = mail_db_open($database); + if (! $dbh) { + echo "Database error\n"; + return false; + } + $date = time(); + $msgid = '<' . md5(strtolower($to) . strtolower($from) . strtolower($subject) . strtolower($message)) . '>'; + $sql = 'INSERT OR IGNORE INTO messages(msgid, mail_from, rcpt_to, rcpt_target, date, subject, message, from_hide, to_hide, mail_viewed, rcpt_viewed) VALUES(?,?,?,?,?,?,?,?,?,?,?)'; + $stmt = $dbh->prepare($sql); + $target = "local"; + $mail_viewed = "true"; + $rcpt_viewed = null; + $q = $stmt->execute([ + $msgid, + $from, + $to, + $target, + $date, + $subject, + $message, + null, + null, + false, + false + ]); + + $dbh = null; + return true; +} + +function delete_message($messageid, $group = null, $overview_dbh = null) +{ + global $logfile, $logdir, $config_dir, $spooldir, $CONFIG, $webserver_group; + if (file_exists($config_dir . '/memcache.inc.php')) { + include $config_dir . '/memcache.inc.php'; + } + if ($group == null) { + $message = get_data_from_msgid($messageid); + $groups = $message['newsgroup']; + $grouplist = preg_split("/( |\,)/", $groups); + } else { + $grouplist[0] = $group; + } + + /* Find section */ + $menulist = file($config_dir . "menu.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + foreach ($grouplist as $group) { + foreach ($menulist as $menu) { + if ($menu[0] == '#') { + continue; + } + $menuitem = explode(':', $menu); + $glfp = fopen($config_dir . $menuitem[0] . "/groups.txt", 'r'); + while ($gl = fgets($glfp)) { + $group_name = preg_split("/( |\t)/", $gl, 2); + if (strtolower(trim($group)) == strtolower(trim($group_name[0]))) { + $config_name = $menuitem[0]; + // echo "\nFOUND: " . $group . " IN: " . $config_name; + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " FOUND: " . $group . " IN: " . $config_name, FILE_APPEND); + break 2; + } + } + } + if ($CONFIG['article_database'] == '1') { + $database = $spooldir . '/' . $group . '-articles.db3'; + if (is_file($database)) { + $articles_dbh = article_db_open($database); + $articles_stmt = $articles_dbh->prepare('DELETE FROM articles WHERE msgid=:messageid'); + $articles_stmt->execute([ + 'messageid' => $messageid + ]); + $articles_dbh = null; + } + } + // Handle overview and history + if ($overview_dbh == null) { + $database = $spooldir . '/articles-overview.db3'; + $overview_dbh = overview_db_open($database); + if (! $overview_dbh) { + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " FAILED opening " . $database, FILE_APPEND); + return; + } + $close_ovdb = true; + } + $overview_stmt_del = $overview_dbh->prepare('DELETE FROM overview WHERE newsgroup=:newsgroup AND msgid=:msgid'); + $overview_query = $overview_dbh->prepare('SELECT * FROM overview WHERE newsgroup=:newsgroup AND msgid=:msgid'); + $overview_query->execute([ + ':newsgroup' => $group, + ':msgid' => $messageid + ]); + $grouppath = preg_replace('/\./', '/', $group); + $status = "deleted"; + $statusdate = time(); + $statusreason = "nocem"; + $statusnotes = null; + while ($row = $overview_query->fetch()) { + if (isset($row['number'])) { + // echo "\nFOUND: " . $messageid . " IN: " . $group; + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " DELETING: " . $messageid . " IN: " . $group, FILE_APPEND); + } + if (is_file($spooldir . '/articles/' . $grouppath . '/' . $row['number'])) { + unlink($spooldir . '/articles/' . $grouppath . '/' . $row['number']); + } + delete_message_from_overboard($config_name, $group, $messageid); + add_to_history($group, $row['number'], $row['msgid'], $status, $statusdate, $statusreason, $statusnotes); + thread_cache_removearticle($group, $row['number']); + $overview_stmt_del->execute([ + ':newsgroup' => $group, + ':msgid' => $messageid + ]); + // Delete article from memcache + if ($memcacheD) { + $article_key = $memcache_key_prefix . '_' . 'article.db3-' . $group . ':' . $row['number']; + $result = $memcacheD->delete($article_key); + if ($enable_memcache_logging) { + if ($result) { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Deleted $article_key", FILE_APPEND); + } else { + file_put_contents($logdir . '/memcache.log', "\n" . format_log_date() . " Failed to delete (or not found) $article_key", FILE_APPEND); + } + } + } + } + } + if ($close_ovdb) { + $overview_dbh = null; + } + return; +} + +// This function returns FALSE if article is OK +// Else returns a string with reason for failure +function check_article_integrity($rawmessage) +{ + global $CONFIG, $logfile; + $returnval = false; + $count_rawmessage = count($rawmessage); + $message = new messageType(); + $rawheader = array(); + $i = 0; + while ($rawmessage[$i] != "") { + $rawheader[] = $rawmessage[$i]; + $i ++; + } + // Parse the Header: + $message->header = parse_header($rawheader); + // Check if date is in future + if ($message->header->date > time()) { + $returnval = " Skipping message (date in future): " . $message->header->id; + return $returnval; + } + // Now we know if the message is a mime-multipart message: + $content_type = explode("/", $message->header->content_type[0]); + if ($content_type[0] == "multipart") { + $message->header->content_type = array(); + // We have multible bodies, so we split the message into its parts + $boundary = "--" . $message->header->content_type_boundary; + // lets find the first part + while ($rawmessage[$i] != $boundary) { + $i ++; + if ($i > $count_rawmessage) { + $returnval = " Skipping malformed message: " . $message->header->id; + return $returnval; + } + } + } + return $returnval; +} + +function wrap_post($body) +{ + $line_length = 72; + $lines = preg_split("/\n/", $body); + $wrapped = ''; + foreach ($lines as $line) { + if (trim($line) == '') { + $wrapped .= "\n"; + continue; + } + if ($line[0] == '>') { + $depth = 0; + while ($line[$depth] == '>') { + $depth ++; + if ($depth > 30) { + break; + } + } + if (strlen($line) > $line_length) { + // HERE is where we wrap quoted lines (not so easy) + $start = substr($line, 0, $depth + 1); + $end = substr($line, $depth + 1); + $line_wrapped = $start . mb_wordwrap($end, $line_length); + $line_wrapped = preg_split("/\n/", $line_wrapped); + foreach ($line_wrapped as $lw) { + if ($lw[0] != '>') { + $i = 0; + while ($i < $depth) { + $wrapped .= '>'; + $i ++; + } + $wrapped .= ' '; + } + $wrapped .= $lw . "\n"; + } + } else { + $wrapped .= $line . "\n"; + } + } else { + if (strlen($line) > $line_length) { + // HERE is where we wrap NON quoted lines (easy) + $wrapped .= mb_wordwrap($line, $line_length) . "\n"; + } else { + $wrapped .= $line . "\n"; + } + } + } + return $wrapped; +} + +function delete_message_from_overboard($config_name, $group, $messageid) +{ + GLOBAL $spooldir; + $cachefile = $spooldir . "/" . $config_name . "-overboard.dat"; + if (is_file($cachefile)) { + $cached_overboard = unserialize(file_get_contents($cachefile)); + if ($target = $cached_overboard['msgids'][$messageid]) { + unset($cached_overboard['threads'][$target['date']]); + unset($cached_overboard['msgids'][$messageid]); + unset($cached_overboard['threadlink'][$messageid]); + file_put_contents($cachefile, serialize($cached_overboard)); + } + } + $cachefile = $spooldir . "/" . $group . "-overboard.dat"; + if (is_file($cachefile)) { + $cached_overboard = unserialize(file_get_contents($cachefile)); + if ($target = $cached_overboard['msgids'][$messageid]) { + unset($cached_overboard['threads'][$target['date']]); + unset($cached_overboard['msgids'][$messageid]); + unset($cached_overboard['threadlink'][$messageid]); + file_put_contents($cachefile, serialize($cached_overboard)); + } + } +} From 1d347d0344a03520f7a9983c3d4982edc232218b Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Tue, 21 May 2024 05:41:33 -0700 Subject: [PATCH 06/11] Add comment in overrides.inc.php for blocking by user agent. --- Rocksolid_Light/rslight/overrides.inc.php.dist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Rocksolid_Light/rslight/overrides.inc.php.dist b/Rocksolid_Light/rslight/overrides.inc.php.dist index a13ff9a..8bd26f8 100644 --- a/Rocksolid_Light/rslight/overrides.inc.php.dist +++ b/Rocksolid_Light/rslight/overrides.inc.php.dist @@ -72,6 +72,10 @@ return [ // Rate per second that a 'bot' may load pages 'throttle_hits_bot_loadrate' => '.1', + // Block connections when one of these strings is + // contained in user_agent string + // 'block_by_user_agent' => array("semrushbot", "bytespider"), + // Just leave this here to avoid comma errors 'comma' => true ]; From 1641b223f3d90cdee5b0de4e6ec2e521900d2349 Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Tue, 21 May 2024 05:50:33 -0700 Subject: [PATCH 07/11] Increment version number. --- Rocksolid_Light/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rocksolid_Light/version.txt b/Rocksolid_Light/version.txt index 8d0065f..ca6f47f 100644 --- a/Rocksolid_Light/version.txt +++ b/Rocksolid_Light/version.txt @@ -1 +1 @@ -0.9.85 +0.9.86 From 0dac91732071aacc4ce8f6e83adabfb921286fab Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Wed, 22 May 2024 04:20:16 -0700 Subject: [PATCH 08/11] Some cleanup for spoolnews, and add some more logging for debug purposes. --- Rocksolid_Light/rocksolid/lib/thread.inc.php | 6 ++++-- Rocksolid_Light/rslight/scripts/spoolnews.php | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Rocksolid_Light/rocksolid/lib/thread.inc.php b/Rocksolid_Light/rocksolid/lib/thread.inc.php index 0a2bbb2..d74dd5c 100644 --- a/Rocksolid_Light/rocksolid/lib/thread.inc.php +++ b/Rocksolid_Light/rocksolid/lib/thread.inc.php @@ -368,8 +368,10 @@ function thread_load_newsserver(&$ns, $groupname, $poll) $overviewformat = thread_overview_read($ns); $spoolfilename = $spooldir . '/' . $groupname . '-data.db3'; fputs($ns, "GROUP $groupname\r\n"); // select a group - $groupinfo = explode(" ", line_read($ns)); - if (substr($groupinfo[0], 0, 1) != 2) { + $response = line_read($ns); + $groupinfo = explode(" ", $response); + if (strcmp(substr($groupinfo[0], 0, 3), "211") != 0) { + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Response to group command for " . $groupname . ": " . $response, FILE_APPEND); echo "

" . $text_error["error:"] . "

"; echo "

" . $text_thread["no_such_group"] . "

"; flush(); diff --git a/Rocksolid_Light/rslight/scripts/spoolnews.php b/Rocksolid_Light/rslight/scripts/spoolnews.php index a40a692..8fccc26 100644 --- a/Rocksolid_Light/rslight/scripts/spoolnews.php +++ b/Rocksolid_Light/rslight/scripts/spoolnews.php @@ -122,7 +122,7 @@ if ($CONFIG['remote_server'] == '') { } } if ($CONFIG['remote_server'] != '') { - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " remote_server: " . $CONFIG['remote_server'], FILE_APPEND); + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Connecting: " . $CONFIG['remote_server'] . ":" . $CONFIG['remote_port'], FILE_APPEND); $ns = nntp2_open($CONFIG['remote_server'], $CONFIG['remote_port']); if (! $ns) { file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Failed to connect to " . $CONFIG['remote_server'] . ":" . $CONFIG['remote_port'], FILE_APPEND); @@ -194,6 +194,7 @@ function get_articles($ns, $group) fputs($ns, "group " . $group . "\r\n"); $response = line_read($ns); if (strcmp(substr($response, 0, 3), "211") != 0) { + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Response to group command for " . $group . ": " . $response, FILE_APPEND); echo "\n" . $response; return (1); } @@ -234,11 +235,15 @@ function get_articles($ns, $group) } else { $getlast = $detail[3]; } - - fputs($ns, "xover " . $article . "-" . $getlast . "\r\n"); + if ($article > $getlast || $article == $getlast) { + // This is probably not necessary + fputs($ns, "xover " . $getlast . "\r\n"); + } else { + fputs($ns, "xover " . $article . "-" . $getlast . "\r\n"); + } $response = line_read($ns); // and once more if ((substr($response, 0, 3) != "224")) { - file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Cannot get overview from " . $CONFIG['remote_server'] . " for " . $group, FILE_APPEND); + file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Cannot get overview from " . $CONFIG['remote_server'] . " for " . $group . " (requested: xover " . $article . "-" . $getlast . " received " . $response . ")", FILE_APPEND); return false; } while (trim($response = line_read($ns)) !== '.') { @@ -292,6 +297,7 @@ function get_articles($ns, $group) $is_header = 1; $body = ""; $content_transfer_encoding = null; + $response = str_replace("\n", "", str_replace("\r", "", $response)); while (strcmp($response, ".") != 0) { $is_xref = false; $bytes = $bytes + mb_strlen($response, '8bit'); @@ -344,7 +350,7 @@ function get_articles($ns, $group) $enco = explode(': ', $response, 2); $content_transfer_encoding = $enco[1]; } - + if (stripos($response, "Newsgroups: ") === 0) { $response = str_ireplace($group, $group, $response); // Identify each group name for xref @@ -412,7 +418,7 @@ function get_articles($ns, $group) $integrity = check_article_integrity(file($articleHandle)); if (($banned !== false) || ($integrity !== false)) { unlink($articleHandle); - if($integrity) { + if ($integrity) { file_put_contents($logfile, "\n" . format_log_date() . $integrity, FILE_APPEND); } elseif ($banned) { file_put_contents($spamlog, "\n" . format_log_date() . " " . $banned . " :\tSPAM\t" . $mid[1] . "\t" . $groupnames[1] . "\t" . $from[1], FILE_APPEND); From 775a39ab7bd11a30bf2474f0278a42452d91ef4a Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Wed, 22 May 2024 04:33:47 -0700 Subject: [PATCH 09/11] Allow incoming article to be up to 2 minutes in the future, no more. --- Rocksolid_Light/rocksolid/newsportal.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rocksolid_Light/rocksolid/newsportal.php b/Rocksolid_Light/rocksolid/newsportal.php index 8d2b511..12d8af0 100644 --- a/Rocksolid_Light/rocksolid/newsportal.php +++ b/Rocksolid_Light/rocksolid/newsportal.php @@ -2410,8 +2410,8 @@ function insert_article_from_array($this_article, $check_duplicates = true) return "441 Insert failed (duplicate)\r\n"; } } - - if ($this_article['epochdate'] > time()) { + // Allow a message to be approximately 2 minutes in the future, but not more. + if ($this_article['epochdate'] > (time() + 120)) { echo "\n(newsportal)Article date in future. Skipping: " . $group . ":" . $this_article['mid']; file_put_contents($logfile, "\n" . format_log_date() . " " . $config_name . " Article date in future. Skipping: " . $group . ":" . $this_article['mid'], FILE_APPEND); return "441 Insert failed (article date in future)\r\n"; From 6108dcefbacb02f903222b767c378f17c3f35790 Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Wed, 22 May 2024 05:09:05 -0700 Subject: [PATCH 10/11] Line wrap article before preparing for quoting. --- Rocksolid_Light/rocksolid/post.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Rocksolid_Light/rocksolid/post.php b/Rocksolid_Light/rocksolid/post.php index 4016c3a..662dd18 100644 --- a/Rocksolid_Light/rocksolid/post.php +++ b/Rocksolid_Light/rocksolid/post.php @@ -464,6 +464,7 @@ if ($show == 1) { echo 'class="postbody" id="postbody" '; echo 'name="' . md5($fieldencrypt . "body") . '" wrap="soft">'; + $bodyzeile = wrap_post($bodyzeile); if ((isset($bodyzeile)) && ($post_autoquote)) echo htmlspecialchars($bodyzeile); if (is_string($body)) @@ -472,8 +473,9 @@ if ($show == 1) { if (! $post_autoquote) { echo ''; ?> From 0f23b44ef026fcc5e835230f184eb7b7fa643c2b Mon Sep 17 00:00:00 2001 From: Retro_Guy Date: Wed, 22 May 2024 05:33:03 -0700 Subject: [PATCH 11/11] Handle X-Face: header properly when a user creates an x-face of just spaces (Don't add the header). --- Rocksolid_Light/rocksolid/lib/post.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rocksolid_Light/rocksolid/lib/post.inc.php b/Rocksolid_Light/rocksolid/lib/post.inc.php index f13cf92..4d746b0 100644 --- a/Rocksolid_Light/rocksolid/lib/post.inc.php +++ b/Rocksolid_Light/rocksolid/lib/post.inc.php @@ -572,7 +572,7 @@ function message_post($subject, $from, $newsgroups, $ref, $body, $encryptthis = $body .= "\r\n--------------" . $boundary . "\r\n"; } fputs($ns, 'Message-ID: ' . $msgid . "\r\n"); - if ($userconfig['xface'] !== '' && $myconfig) { + if (trim($userconfig['xface']) !== '' && $myconfig) { fputs($ns, 'X-Face: ' . $userconfig['xface'] . "\r\n"); } if ($do_attach) {