rocksolid-light/Rocksolid_Light/rslight/scripts/rslight-lib.php

1465 lines
51 KiB
PHP

<?php
function interact($msgsock, $use_crypto = false)
{
global $CONFIG, $OVERRIDES, $logdir, $lockdir, $logfile, $installed_path, $config_path, $config_dir, $groupconfig, $workpath, $path, $spooldir, $nntp_group, $nntp_article, $auth_ok, $user, $pass;
$workpath = $spooldir . "/";
$path = $workpath . "articles/";
$groupconfig = $spooldir . "/spoolnews/groups.txt";
if (isset($OVERRIDES['nntp_full_auth_required']) && $OVERRIDES['nntp_full_auth_required'] == true) {
$nntp_full_auth_required = true;
} else {
$nntp_full_auth_required = false;
}
$logfile = $logdir . '/nntp.log';
$nntp_group = "";
$nntp_article = "";
/* CRYPTO */
stream_set_blocking($msgsock, true);
if ($use_crypto) {
$cryptoSetup = stream_socket_enable_crypto($msgsock, TRUE, STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER);
}
stream_set_timeout($msgsock, 300);
$client = stream_socket_get_name($msgsock, 1);
$client_ip = explode(':', $client);
if (strpos($CONFIG['open_clients'], $client_ip[0]) !== false) {
$auth_ok = 1;
}
/* Send instructions. */
$msg = "200 Rocksolid Light NNTP Server ready (no posting)\r\n";
fwrite($msgsock, $msg, strlen($msg));
do {
$msg = "";
set_time_limit(30);
$buf = fgets($msgsock, 2048);
if ($buf === false) {
// file_put_contents($logfile, "\n".format_log_date()." socket read failed: reason: " . socket_strerror(socket_last_error($msgsock)), FILE_APPEND);
break;
}
set_time_limit(0);
$buf = trim($buf);
if (strlen($buf) < 1) {
continue;
}
if (stripos($buf, 'AUTHINFO PASS') !== false) {
file_put_contents($logfile, "\n" . format_log_date() . " " . $client . " AUTHINFO PASS (hidden)", FILE_APPEND);
} else {
file_put_contents($logfile, "\n" . format_log_date() . " " . $client . " " . $buf, FILE_APPEND);
}
$command = explode(' ', $buf);
$command[0] = strtolower($command[0]);
// Check if auth required for everything or only posting
if ($nntp_full_auth_required == true && $auth_ok == 0) {
if ($command[0] == 'authinfo' || $command[0] == 'quit' || $command[0] == 'mode') {
// Ok to continue
} else {
// Auth is required. Try again
$msg = "480 Authentication Required\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
}
if (isset($command[1])) {
}
if ($command[0] == 'date') {
$msg = '111 ' . date('YmdHis') . "\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'list') {
if (isset($command[1])) {
if (isset($command[2])) {
$msg = get_list(strtolower($command[1]), $command[2], $msgsock);
} else {
$msg = get_list(strtolower($command[1]), false, $msgsock);
}
} else {
$msg = get_list("active", false, $msgsock);
}
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'post') {
if ($auth_ok == 0) {
$msg = "480 Posting not permitted (Auth required)\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
$msg = "340 Send article to be posted\r\n";
$tempfilename = tempnam(sys_get_temp_dir(), '');
$tempfilehandle = fopen($tempfilename, 'wb');
fwrite($msgsock, $msg, strlen($msg));
$buf = fgets($msgsock, 2048);
while (rtrim($buf) !== '.') {
fwrite($tempfilehandle, $buf);
$buf = fgets($msgsock, 2048);
}
fclose($tempfilehandle);
$msg = prepare_post($tempfilename);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'capabilities') {
$msg = "101 Capability list:\r\n";
$msg .= "VERSION 2\r\n";
$msg .= "AUTHINFO USER\r\n";
$msg .= "HDR\r\n";
$msg .= "LIST ACTIVE HEADERS NEWSGROUPS OVERVIEW.FMT\r\n";
if ($auth_ok == '1') {
$msg .= "POST\r\n";
}
$msg .= "OVER\r\n";
$msg .= "READER\r\n";
$msg .= ".\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'newgroups') {
$msg = get_newgroups($command);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'next') {
$msg = get_next($nntp_group);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'last') {
$msg = get_last($nntp_group);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'authinfo') {
if (! isset($command[2])) {
$command[2] = fgets($msgsock, 2048);
}
if (strtolower($command[1]) == 'user') {
$user = $command[2];
if (isset($command[3])) {
$user = $user . " " . $command[3];
}
$msg = "381 Enter password\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if (strtolower($command[1]) == 'pass') {
if ($user == "") {
$msg = "482 Authentication commands issued out of sequence\r\n";
} else {
$pass = $command[2];
if (check_bbs_auth($user, $pass, stream_socket_get_name($msgsock, true))) {
$auth_ok = 1;
$msg = "281 Authentication succeeded\r\n";
} else {
$auth_ok = 0;
$msg = "481 Authentication failed\r\n";
}
}
fwrite($msgsock, $msg, strlen($msg));
continue;
}
$msg = "501 Syntax error\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'mode') { // Here to handle MODE READER. This is our only mode right now.
$msg = "200 Rocksolid Light NNRP Server ready (no posting)\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'stat') {
$msg = get_stat($command[1]);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'article') {
$msg = get_article($command[1], $nntp_group);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'head') {
$msg = get_header($command[1], $nntp_group);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'body') {
$msg = get_body($command[1], $nntp_group);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'listgroup') {
if (isset($command[1])) {
$nntp_group = $command[1];
}
$msg = get_listgroup($nntp_group, $msgsock);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'group') {
$change_group = $command[1];
$msg = get_group($change_group);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'xgtitle') {
if (isset($command[1])) {
$msg = get_title($command[1]);
} else {
$msg = get_title("active");
}
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if (($command[0] == 'xover') || ($command[0] == 'over')) {
$msg = get_xover($command[1], $msgsock);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if (($command[0] == 'xhdr') || ($command[0] == 'hdr')) {
$msg = get_xhdr($command[1], $command[2]);
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'help') {
$msg = "100 Sorry, can't help\r\n";
fwrite($msgsock, $msg, strlen($msg));
continue;
}
if ($command[0] == 'quit') {
$msg = "205 closing connection - goodbye!\r\n";
fwrite($msgsock, $msg, strlen($msg));
// socket_close($msgsock);
exit(0);
}
file_put_contents($logfile, "\n" . format_log_date() . " Syntax error: " . $buf, FILE_APPEND);
$talkback = "500 Syntax error or unknown command\r\n";
fwrite($msgsock, $talkback, strlen($talkback));
} while (true);
exit(0);
}
/**
* Become a daemon by forking and closing the parent
*/
function become_daemon()
{
$pid = pcntl_fork();
if ($pid == -1) {
/* fork failed */
echo "fork failure!\n";
exit();
} elseif ($pid) {
/* close the parent */
exit();
} else {
/* child becomes our daemon */
posix_setsid();
chdir('/');
umask(0);
return posix_getpid();
}
}
function prepare_post($filename)
{
global $logdir, $spooldir, $config_dir, $rslight_gpg;
$logfile = $logdir . '/nntp.log';
$message = file($filename, FILE_IGNORE_NEW_LINES);
$response = "441 Posting failed\r\n";
$lines = 0;
$is_header = 1;
$head_from = false;
$head_subject = false;
$head_newsgroups = false;
$nocem_check = "@@NCM";
$bbsmail_check = "@@RSL";
foreach ($message as $line) {
if (trim($line) == "" && $lines > 0) {
$is_header = 0;
$lines++;
}
if ($lines > 0 && $is_header == 0) {
break;
}
if (stripos($line, "From: ") === 0) {
$lines++;
$head_from = true;
continue;
}
if (stripos($line, "Newsgroups: ") === 0) {
$ngroups = explode(': ', $line);
$lines++;
$head_newsgroups = true;
continue;
}
if (stripos($line, "Subject: ") === 0) {
$sub = explode(': ', $line);
$subject = $sub[1];
$lines++;
$head_subject = true;
continue;
}
}
$ok = 0;
if (! $head_from || ! $head_subject || ! $head_newsgroups) {
$response = "441 Posting failed (Missing header line)\r\n";
return $response;
}
$allgroups = preg_split("/\ |\,/", $ngroups[1]);
foreach ($allgroups as $agroup) {
$agroup = trim($agroup);
if (testGroup($agroup)) {
// if ((testGroup($agroup)) || $agroup == '') {
$response = process_post($message, $agroup);
if (substr($response, 0, 3) == "240") {
$ok = 1;
}
}
}
if ($ok == 1) {
if ((strpos($rslight_gpg['nntp_group'], $group) !== false) && ($rslight_gpg['enable'] == '1')) {
if (strpos($subject, $bbsmail_check) !== false) {
$bbsmail_file = preg_replace('/@@RSL /', '', $subject);
$bbsmail_filename = $spooldir . "/bbsmail/in/bbsmail-" . $bbsmail_file;
copy($filename, $bbsmail_filename);
}
}
if (strpos($subject, $nocem_check) !== false) {
$nocem_file = tempnam($spooldir . "/nocem", "nocem-" . $group . "-");
copy($filename, $nocem_file);
}
$response = "240 Article received OK (posted)\r\n";
} else {
// $response = "441 Posting failed (group not found)\r\n";
}
return $response;
}
function process_post($message, $group)
{
global $logfile, $spooldir, $config_dir, $logfile, $CONFIG, $nntp_group;
$posted_db = $spooldir . '/posted_articles.dat';
if (file_exists($posted_db)) {
$posted_articles = unserialize(file_get_contents($posted_db));
} else {
$posted_articles = array();
}
$no_mid = 1;
$no_date = 1;
$no_org = 1;
$is_header = 1;
$body = "";
$ref = 0;
$sub = 0;
$ng = 0;
$response = "";
$bytes = 0;
$lines = 0;
/* Process post */
foreach ($message as $line) {
$bytes = $bytes + mb_strlen($line, '8bit');
if (trim($line) == "" && $lines > 0) {
$is_header = 0;
$lines++;
} else {
$lines++;
}
if ($is_header == 0) {
$body .= $line . "\n";
} else {
if (strpos($line, ': ') !== false) {
$ref = 0;
$sub = 0;
$ng = 0;
} else {
if (preg_match('/^\s/', $line)) {
if ($ng == 1) {
$newsgroups = $newsgroups . ',' . trim($line);
$newsgroups = preg_replace('/\,\,/', ',', $newsgroups);
continue;
}
if ($ref == 1) {
$references = $references . $line;
continue;
}
if ($sub == 1) {
$subject = $subject . $line;
continue;
}
}
}
if (stripos($line, "Path: ") === 0) {
$response = "441 Posting failed (Header preloading denied)\r\n";
return $response;
}
if (stripos($line, "Injection-Info: ") === 0) {
$response = "441 Posting failed (Header preloading denied)\r\n";
return $response;
}
if (stripos($line, "Date: ") === 0) {
$finddate = explode(': ', $line);
$article_date = strtotime($finddate[1]);
$no_date = 0;
}
if (stripos($line, "Organization: ") !== false) {
$no_org = 0;
}
if (stripos($line, "Subject: ") !== false) {
$this_subject = explode('Subject: ', $line, 2);
$subject = $this_subject[1];
$sub = 1;
}
if (stripos($line, "From: ") === 0) {
$from = explode(': ', $line);
}
if (stripos($line, "Xref: ") === 0) {
$xref = $line;
}
if (stripos($line, "Newsgroups: ") === 0) {
$ngroups = explode(': ', $line);
$newsgroups = $ngroups[1];
$ng = 1;
}
if (stripos($line, "References: ") === 0) {
$references_line = explode(': ', $line);
$references = $references_line[1];
$ref = 1;
}
if (stripos($line, "Message-ID: ") !== false) {
$mid = explode(': ', $line);
$no_mid = 0;
}
}
}
if ($no_mid == 1) {
$identity = $subject . "," . $from[1] . "," . $ngroups[1] . "," . $references . "," . $body;
$msgid = '<' . md5($identity) . '$1@' . trim($CONFIG['email_tail'], '@') . '>';
} else {
$msgid = $mid[1];
}
/* Find section for posting */
$section = get_section_by_group($group);
// Get server details for this section
if (file_exists($config_dir . $section . '.inc.php')) {
$config_file = $config_dir . $section . '.inc.php';
} else {
$config_file = $config_dir . 'rslight.inc.php';
}
$THIS_CONFIG = include($config_file);
$this_server = $THIS_CONFIG['remote_server'] . $THIS_CONFIG['remote_port'];
if (isset($posted_articles[$msgid][$this_server])) {
$previously_posted = true;
} else {
$previously_posted = false;
$posted_articles[$msgid][$this_server] = true;
}
/*
* SPAM CHECK
*/
if (isset($CONFIG['spamassassin']) && ($CONFIG['spamassassin'] == true)) {
$spam_result_array = check_spam($subject, $from[1], $newsgroups, $references, $body, $msgid, true);
$res = $spam_result_array['res'];
$spamresult = $spam_result_array['spamresult'];
$spamcheckerversion = $spam_result_array['spamcheckerversion'];
$spamlevel = $spam_result_array['spamlevel'];
}
if ($res === 1) {
$orig_newsgroups = $newsgroups;
$newsgroups = $CONFIG['spamgroup'];
$group = $newsgroups;
$response = "441 Posting failed (Exceeds Spam Score)\r\n";
return $response;
}
@mkdir($spooldir . "/" . $section . "/outgoing", 0755, 'recursive');
$postfilename = $spooldir . '/' . $section . '/outgoing/' . $msgid . '.msg';
if (file_exists($postfilename)) {
$postfilename_previous = true;
} else {
$postfilename_previous = false;
}
$postfilehandle = fopen($postfilename, 'w');
if ($no_date == 1) {
$article_date = time();
$date_rep = date('D, j M Y H:i:s O', $article_date);
fputs($postfilehandle, "Date: " . $date_rep . "\r\n");
} else {
$date_rep = $finddate[1];
}
if ($no_mid == 1) {
fputs($postfilehandle, "Message-ID: " . $msgid . "\r\n");
}
if ($no_org == 1) {
fputs($postfilehandle, "Organization: " . $CONFIG['organization'] . "\r\n");
}
if ($res === 1) {
if ($orig_newsgroups !== $CONFIG['spamgroup']) {
fputs($postfilehandle, "X-Rslight-Original-Group: " . $orig_newsgroups . "\r\n");
}
}
$is_header = 1;
$lines = 0;
$ng = 0;
foreach ($message as $line) {
if (trim($line) == "" && $lines > 0) {
$is_header = 0;
$lines++;
} else {
$lines++;
}
if (stripos($line, "Newsgroups: ") === 0 && $is_header == 1) {
fputs($postfilehandle, "Newsgroups: " . $newsgroups . "\r\n");
$ng = 1;
} else {
if (strpos($line, ': ') !== false) {
$ng = 0;
} else {
if (preg_match('/^\s/', $line)) {
if ($ng == 1) {
continue;
}
}
}
fputs($postfilehandle, $line . "\r\n");
}
}
fclose($postfilehandle);
chmod($postfilename, 0600);
unlink($filename);
if ($section == "") {
$response = "441 Posting failed (section not found)\r\n";
file_put_contents($logfile, "\n" . format_log_date() . " " . trim($response) . " for " . $group, FILE_APPEND);
} else {
$response = insert_article($section, $group, $postfilename, $subject, $from[1], $article_date, $date_rep, $msgid, $references, $bytes, $lines, $xref, $body);
// Only add to another section if a different remote server is used
// else only add to local database for next group
if ($previously_posted) {
file_put_contents($logfile, "\n" . format_log_date() . " NOT adding article to: " . $section . "/outgoing - Already Posted", FILE_APPEND);
if (!$postfilename_previous) {
unlink($postfilename);
} else {
file_put_contents($logfile, "\n" . format_log_date() . " NOT DELETING: " . $section . "/outgoing - Already Exists", FILE_APPEND);
}
} else {
file_put_contents($posted_db, serialize($posted_articles));
file_put_contents($logfile, "\n" . format_log_date() . " Adding article to: " . $section . "/outgoing", FILE_APPEND);
}
}
return $response;
}
function get_next($nntp_group)
{
global $spooldir, $nntp_article;
if ($nntp_group == "") {
$response = "412 Not in a newsgroup\r\n";
return $response;
}
$ok_article = get_article_list($nntp_group);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
if (($nntp_article + 1) > $last) {
$response = "421 No next article to retrieve\r\n";
} else {
$nntp_article++;
$database = $spooldir . '/articles-overview.db3';
$table = 'overview';
$overview_dbh = overview_db_open($database, $table);
$stmt = $overview_dbh->prepare("SELECT * FROM $table WHERE newsgroup=:newsgroup AND number=:number");
$stmt->bindParam(':newsgroup', $nntp_group);
$stmt->bindParam(':number', $nntp_article);
$stmt->execute();
while ($found = $stmt->fetch()) {
$msgid = $found['msgid'];
break;
}
$overview_dbh = null;
$response = "223 " . $nntp_article . " " . $msgid . " Article retrieved; request text separately\r\n";
}
return $response;
}
function get_last($nntp_group)
{
global $spooldir, $nntp_article;
if ($nntp_group == "") {
$response = "412 Not in a newsgroup\r\n";
return $response;
}
$ok_article = get_article_list($nntp_group);
rsort($ok_article);
$first = $ok_article[key(array_slice($ok_article, -1, 1, true))];
if (($nntp_article - 1) < $first || ! isset($nntp_article)) {
$response = "422 No previous article to retrieve\r\n";
} else {
$nntp_article--;
$database = $spooldir . '/articles-overview.db3';
$table = 'overview';
$overview_dbh = overview_db_open($database, $table);
$stmt = $overview_dbh->prepare("SELECT * FROM $table WHERE newsgroup=:newsgroup AND number=:number");
$stmt->bindParam(':newsgroup', $nntp_group);
$stmt->bindParam(':number', $nntp_article);
$stmt->execute();
while ($found = $stmt->fetch()) {
$msgid = $found['msgid'];
break;
}
$overview_dbh = null;
$response = "223 " . $nntp_article . " " . $msgid . " Article retrieved; request text separately\r\n";
}
return $response;
}
function get_xhdr($header, $articles)
{
global $config_dir, $spooldir, $nntp_group, $nntp_article, $workpath, $path;
$tmpgroup = $nntp_group;
$mid = false;
// Use article pointer
if (! isset($articles) && is_numeric($nntp_article)) {
$articles = $nntp_article;
}
// By Message-ID
if (strpos($articles, "@") !== false) {
$found = find_article_by_msgid($articles);
$nntp_group = $found['newsgroup'];
$first = $found['number'];
$last = $first;
$this_id = $found['msgid'];
$articles = $found['number'];
if (! isset($articles)) {
$output = "430 No article with that message-id\r\n";
return $output;
}
$output = "221 Header information for " . $header . " follows (from articles)\r\n";
}
if (! isset($tmpgroup)) {
$msg = "412 no newsgroup selected\r\n";
return $msg;
}
$thisgroup = $path . "/" . preg_replace('/\./', '/', $tmpgroup);
$article_num = explode('-', $articles);
$first = $article_num[0];
if (isset($article_num[1]) && is_numeric($article_num[1])) {
$last = $article_num[1];
} else {
if (strpos($articles, "-")) {
$ok_article = get_article_list($nntp_group);
// fclose($group_overviewfp);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
if (! is_numeric($last))
$last = 0;
} else {
$last = $first;
}
}
$msg = "221 Header information for " . $header . " follows (from articles)\r\n";
for ($i = $first; $i <= $last; $i++) {
$article_full_path = $thisgroup . '/' . strval($i);
$data = extract_header_line($article_full_path, $header, $tmpgroup, $i);
if ($data !== false) {
if ($mid !== false) {
$msg .= $mid . " " . $data;
} else {
$msg .= strval($i) . " " . $data;
}
}
}
$msg .= ".\r\n";
return $msg;
}
function extract_header_line($article_full_path, $header, $thisgroup, $article)
{
global $CONFIG;
if ($CONFIG['article_database'] == '1') {
$thisarticle = np_get_db_article($article, $thisgroup);
} else {
$thisarticle = file($article_full_path, FILE_IGNORE_NEW_LINES);
}
foreach ($thisarticle as $thisline) {
if ($thisline == "") {
$msg2 .= ".\r\n";
break;
}
if (stripos($thisline, $header) === 0) {
$content = preg_split("/$header: /i", $thisline);
return ($content[1] . "\r\n");
}
}
return (false);
}
function get_title($mode)
{
global $nntp_group, $workpath, $spooldir, $path;
if ($mode == "active") {
$msg = "481 descriptions unavailable\r\n";
return $msg;
}
if (! file_exists($spooldir . "/" . $mode . "-title")) {
$msg = "481 descriptions unavailable\r\n";
return $msg;
}
$title = file_get_contents($spooldir . "/" . $mode . "-title");
$msg = "282 list of group and description follows\r\n";
$msg .= trim($title);
$msg .= "\r\n.\r\n";
return $msg;
}
function get_xover($articles, $msgsock)
{
global $nntp_group, $nntp_article, $workpath, $path, $spooldir;
// Use article pointer
if (! isset($articles) && is_numeric($nntp_article)) {
$articles = $nntp_article;
}
// By Message-ID
if (strpos($articles, "@") !== false) {
$found = find_article_by_msgid($articles);
$nntp_group = $found['newsgroup'];
$first = $found['number'];
$last = $first;
$this_id = $found['msgid'];
$articles = $found['number'];
if (! isset($articles)) {
$output = "430 No article with that message-id\r\n";
return $output;
}
$output = "224 Overview information follows for " . $this_id . "\r\n";
}
if ($nntp_group == '') {
$msg = "412 no newsgroup selected\r\n";
return $msg;
}
if (! isset($articles)) {
$msg = "420 no article(s) selected\r\n";
return $msg;
}
if (! isset($this_id)) {
$article_num = explode('-', $articles);
$first = $article_num[0];
if (isset($article_num[1]) && is_numeric($article_num[1])) {
$last = $article_num[1];
$output = "224 Overview information follows for articles " . $first . " through " . $last . "\r\n";
} else {
if (strpos($articles, "-")) {
$ok_article = get_article_list($nntp_group);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
if (! is_numeric($last)) {
$last = 0;
}
$output = "224 Overview information follows for articles " . $first . " through " . $last . "\r\n";
} else {
$last = $first;
$output = "224 Overview information follows for " . $first . "\r\n";
}
}
}
fwrite($msgsock, $output, strlen($output));
$database = $spooldir . '/articles-overview.db3';
$table = 'overview';
$overview_dbh = overview_db_open($database, $table);
$stmt = $overview_dbh->prepare("SELECT * FROM $table WHERE newsgroup=:thisgroup AND number=:number"); // Why doesn't BETWEEN work properly here?
for ($i = $first; $i <= $last; $i++) {
$stmt->execute([
'thisgroup' => $nntp_group,
':number' => $i
]);
while ($row = $stmt->fetch()) {
$msg .= $row['number'] . "\t" . $row['subject'] . "\t" . $row['name'] . "\t" . $row['datestring'] . "\t" . $row['msgid'] . "\t" . $row['refs'] . "\t" . $row['bytes'] . "\t" . $row['lines'] . "\t" . $row['xref'] . "\r\n";
}
}
$overview_dbh = null;
$msg .= ".\r\n";
return $msg;
}
function get_stat($article)
{
global $nntp_group, $nntp_article, $spooldir;
// Use article pointer
if (! isset($article) && is_numeric($nntp_article)) {
$article = $nntp_article;
}
// By Message-ID
if (preg_match("/\<.*\@.*\>/", $article)) {
$found = find_article_by_msgid($article);
$nntp_group = $found['newsgroup'];
$article = $found['number'];
if (!$found) {
$msg = "430 No such article\r\n";
return $msg;
}
} else {
if (! is_numeric($article)) {
$msg = "501 Syntax error in Message-ID\r\n";
return $msg;
}
}
if ($nntp_group == '') {
$msg = "412 Not in a newsgroup\r\n";
return $msg;
}
if (! is_numeric($article)) {
$msg = "423 No article number selected\r\n";
return $msg;
}
$database = $spooldir . '/articles-overview.db3';
if (! is_file($database)) {
return false;
}
$overview_dbh = overview_db_open($database);
$query = $overview_dbh->prepare('SELECT * FROM overview WHERE number=:number AND newsgroup=:newsgroup');
$query->execute([
'number' => $article,
'newsgroup' => $nntp_group
]);
$found = 0;
while ($row = $query->fetch()) {
$found = 1;
break;
}
$overview_dbh = null;
if ($found == 1) {
$msg = "223 " . $article . " " . $row['msgid'] . " status\r\n";
} else {
$msg = "423 No such article number " . $article . "\r\n";
}
return $msg;
}
function get_article($article, $nntp_group)
{
global $CONFIG, $config_dir, $path, $groupconfig, $config_name, $spooldir, $nntp_article;
$msg2 = "";
// Use article pointer
if (! isset($article) && is_numeric($nntp_article)) {
$article = $nntp_article;
}
// By Message-ID
if (! is_numeric($article)) {
$found = find_article_by_msgid($article);
$nntp_group = $found['newsgroup'];
$article = $found['number'];
$this_id = $found['msgid'];
} else {
// By article number
if ($nntp_group === "") {
$msg .= "412 no newsgroup has been selected\r\n";
return $msg;
}
if (! is_numeric($article)) {
$msg .= "420 no article has been selected\r\n";
return $msg;
}
}
if ($CONFIG['article_database'] == '1') {
$thisarticle = np_get_db_article($article, $nntp_group);
if ($thisarticle === FALSE) {
$msg .= "430 no such article found\r\n";
return $msg;
}
$thisarticle[] = ".";
} else {
$thisgroup = $path . "/" . preg_replace('/\./', '/', $nntp_group);
if (! file_exists($thisgroup . "/" . $article)) {
$msg .= "430 no such article found\r\n";
return $msg;
}
$thisarticle = file($thisgroup . "/" . $article, FILE_IGNORE_NEW_LINES);
}
foreach ($thisarticle as $thisline) {
if ((strpos($thisline, "Message-ID: ") === 0) && ! isset($mid[1])) {
$mid = explode(': ', $thisline);
}
$msg2 .= $thisline . "\r\n";
}
$msg = "220 " . $article . " " . $mid[1] . " article retrieved - head and body follow\r\n";
$nntp_article = $article;
return $msg . $msg2;
}
function get_header($article, $nntp_group)
{
global $CONFIG, $nntp_article, $config_dir, $path, $groupconfig, $config_name, $spooldir;
$msg2 = "";
// Use article pointer
if (! isset($article) && is_numeric($nntp_article)) {
$article = $nntp_article;
}
// By Message-ID
if (! is_numeric($article)) {
$found = find_article_by_msgid($article);
$nntp_group = $found['newsgroup'];
$article = $found['number'];
$this_id = $found['msgid'];
} else {
// By article number
if ($nntp_group === "") {
$msg .= "412 no newsgroup has been selected\r\n";
return $msg;
}
if (! is_numeric($article)) {
$msg .= "420 no article has been selected\r\n";
return $msg;
}
}
if ($CONFIG['article_database'] == '1') {
$thisarticle = np_get_db_article($article, $nntp_group);
if ($thisarticle === FALSE) {
$msg .= "430 no such article found\r\n";
return $msg;
}
} else {
$thisgroup = $path . "/" . preg_replace('/\./', '/', $nntp_group);
if (! file_exists($thisgroup . "/" . $article)) {
$msg .= "430 no such article found\r\n";
return $msg;
}
$thisarticle = file($thisgroup . "/" . $article, FILE_IGNORE_NEW_LINES);
}
foreach ($thisarticle as $thisline) {
if ($thisline == "") {
$msg2 .= ".\r\n";
break;
}
if ((strpos($thisline, "Message-ID: ") === 0) && ! isset($mid[1])) {
$mid = explode(': ', $thisline);
}
$msg2 .= $thisline . "\r\n";
}
$msg = "221 " . $article . " " . $mid[1] . " article retrieved - header follows\r\n";
return $msg . $msg2;
}
function get_body($article, $nntp_group)
{
global $CONFIG, $nntp_article, $config_dir, $path, $groupconfig, $config_name, $spooldir;
$msg2 = "";
// Use article pointer
if (! isset($article) && is_numeric($nntp_article)) {
$article = $nntp_article;
}
// By Message-ID
if (! is_numeric($article)) {
$found = find_article_by_msgid($article);
$nntp_group = $found['newsgroup'];
$article = $found['number'];
$this_id = $found['msgid'];
} else {
// By article number
if ($nntp_group === "") {
$msg .= "412 no newsgroup has been selected\r\n";
return $msg;
}
if (! is_numeric($article)) {
$msg .= "420 no article has been selected\r\n";
return $msg;
}
}
if ($CONFIG['article_database'] == '1') {
$thisarticle = np_get_db_article($article, $nntp_group);
if ($thisarticle === FALSE) {
$msg .= "430 no such article found\r\n";
return $msg;
}
$thisarticle[] = ".";
} else {
$thisgroup = $path . "/" . preg_replace('/\./', '/', $nntp_group);
if (! file_exists($thisgroup . "/" . $article)) {
$msg .= "430 no such article found\r\n";
return $msg;
}
$thisarticle = file($thisgroup . "/" . $article, FILE_IGNORE_NEW_LINES);
}
foreach ($thisarticle as $thisline) {
if (($thisline == "") && ($body == 0)) {
$body = 1;
continue;
}
if ((strpos($thisline, "Message-ID: ") === 0) && ! isset($mid[1])) {
$mid = explode(': ', $thisline);
}
if ($body == 1) {
$msg2 .= $thisline . "\r\n";
}
}
$msg = "222 " . $article . " " . $mid[1] . " article retrieved - body follows\r\n";
return $msg . $msg2;
}
function get_listgroup($nntp_group, $msgsock)
{
global $spooldir, $path, $nntp_group, $groupconfig;
$grouplist = file($groupconfig, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($nntp_group == '') {
$msg = "412 no newsgroup selected\r\n";
return $msg;
}
$ok_group = false;
$count = 0;
foreach ($grouplist as $findgroup) {
$name = preg_split("/( |\t)/", $findgroup, 2);
if (! strcmp($name[0], $nntp_group)) {
$ok_group = true;
break;
}
}
$ok_article = get_article_list($nntp_group);
// fclose($group_overviewfp);
$count = count($ok_article);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
$first = $ok_article[0];
if (! is_numeric($last))
$last = 0;
if (! is_numeric($first))
$first = 0;
$output = "211 " . $count . " " . $first . " " . $last . " " . $nntp_group . "\r\n";
fwrite($msgsock, $output, strlen($output));
foreach ($ok_article as $art) {
$output = $art . "\r\n";
fwrite($msgsock, $output, strlen($output));
}
$msg = ".\r\n";
return $msg;
}
function get_group($change_group)
{
global $spooldir, $path, $nntp_group, $nntp_article, $groupconfig, $debug_log;
$grouplist = file($groupconfig, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$ok_group = false;
$count = 0;
foreach ($grouplist as $findgroup) {
$name = preg_split("/( |\t)/", $findgroup, 2);
if (! strcmp($name[0], $change_group)) {
$ok_group = true;
break;
}
}
if ($ok_group == false) {
$response = "411 No such group " . $change_group . "\r\n";
return $response;
}
$nntp_group = $change_group;
$ok_article = get_article_list($nntp_group);
$count = count($ok_article);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
$first = $ok_article[0];
if (! is_numeric($last))
$last = 0;
if (! is_numeric($first))
$first = 0;
$nntp_article = $first;
$msg = "211 " . $count . " " . $first . " " . $last . " " . $nntp_group . "\r\n";
// Write article number statistics to file (matching newsportal info.txt format)
$articlestats = explode(" ", $msg);
$savestats = $articlestats[2] . " " . $articlestats[3] . " " . $articlestats[1];
file_put_contents($spooldir . '/' . $nntp_group . '-rslight_info.txt', $savestats);
repair_broken_group($nntp_group);
return $msg;
}
function get_newgroups($mode)
{
global $path, $groupconfig;
$grouplist = file($groupconfig, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$mode = "active";
if ($mode == "active") {
$msg = '231 list of newsgroups follows' . "\r\n";
foreach ($grouplist as $findgroup) {
$name = preg_split("/( |\t)/", $findgroup, 2);
if ($name[0][0] === ':')
continue;
$ok_article = get_article_list($nntp_group);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
$first = $ok_article[0];
if (! is_numeric($last))
$last = 0;
if (! is_numeric($first))
$first = 0;
$msg .= $name[0] . " " . $last . " " . $first . " n\r\n";
}
}
if ($mode == "newsgroups") {
$msg = '215 list of newsgroups and descriptions follows' . "\r\n";
foreach ($grouplist as $findgroup) {
if ($findgroup[0] === ':')
continue;
$msg .= $findgroup . "\r\n";
}
}
if ($mode == "overview.fmt") {
$msg = "215 Order of fields in overview database.\r\n";
$msg .= "Subject:\r\n";
$msg .= "From:\r\n";
$msg .= "Date:\r\n";
$msg .= "Message-ID:\r\n";
$msg .= "References:\r\n";
$msg .= "Bytes:\r\n";
$msg .= "Lines:\r\n";
$msg .= "Xref:full\r\n";
}
if (isset($msg)) {
return $msg . ".\r\n";
} else {
$msg = "501 Syntax error or unknown command\r\n";
return $msg . ".\r\n";
}
}
function get_list($mode, $ngroup, $msgsock)
{
global $path, $spooldir, $groupconfig;
if ($ngroup) {
$grouplist[0] = $ngroup;
} else {
$grouplist = file($groupconfig, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
}
if ($mode == "headers") {
$msg = "215 metadata items supported:\r\n";
$msg .= ":\r\n";
$msg .= ":lines\r\n";
$msg .= ":bytes\r\n";
}
if ($mode == "active") {
$msg = '215 Newsgroups in form "group high low status"' . "\r\n";
fwrite($msgsock, $msg, strlen($msg));
foreach ($grouplist as $findgroup) {
$name = preg_split("/( |\t)/", $findgroup, 2);
if ($name[0][0] === ':')
continue;
$ok_article = get_article_list($findgroup);
sort($ok_article);
$last = $ok_article[key(array_slice($ok_article, -1, 1, true))];
$first = $ok_article[0];
if (! is_numeric($last)) {
$last = 0;
}
if (! is_numeric($first)) {
$first = 0;
}
$output = $name[0] . " " . $last . " " . $first . " y\r\n";
fwrite($msgsock, $output, strlen($output));
}
return ".\r\n";
}
if ($mode == "newsgroups") {
$msg = '215 list of newsgroups and descriptions follows' . "\r\n";
foreach ($grouplist as $findgroup) {
if ($findgroup[0] === ':')
continue;
$name = preg_split("/( |\t)/", $findgroup, 2);
if (trim($name[1]) !== "") {
$msg .= $findgroup . "\r\n";
} elseif (file_exists($spooldir . "/" . $name[0] . "-title")) {
$msg .= file_get_contents($spooldir . "/" . $name[0] . "-title") . "\r\n";
} else {
$msg .= $findgroup . "\r\n";
}
}
}
if ($mode == "overview.fmt") {
$msg = "215 Order of fields in overview database.\r\n";
$msg .= "Subject:\r\n";
$msg .= "From:\r\n";
$msg .= "Date:\r\n";
$msg .= "Message-ID:\r\n";
$msg .= "References:\r\n";
$msg .= "Bytes:\r\n";
$msg .= "Lines:\r\n";
$msg .= "Xref:full\r\n";
}
if (isset($msg)) {
return $msg . ".\r\n";
} else {
$msg = "501 Syntax error or unknown command\r\n";
return $msg . ".\r\n";
}
}
function insert_article($section, $nntp_group, $filename, $subject_i, $from_i, $article_date, $date_i, $mid_i, $references_i, $bytes_i, $lines_i, $xref_i, $body)
{
global $enable_rslight, $spooldir, $CONFIG, $OVERRIDES, $logdir, $lockdir, $logfile;
if (is_moderated($nntp_group)) {
file_put_contents($logfile, "\n" . format_log_date() . " " . $section . " Moderated group... Queuing local post: " . $nntp_group, FILE_APPEND);
$return_val = "240 Article received OK (queued for moderation)\r\n";
return ($return_val);
}
if (isset($OVERRIDES['insert_disable']) && $OVERRIDES['insert_disable'] != '') {
$insert_disable = explode(',', $OVERRIDES['insert_disable']);
foreach ($insert_disable as $disable) {
if ($section == trim($disable)) {
file_put_contents($logfile, "\n" . format_log_date() . " " . $section . " Insert Disabled... Queuing local post: " . $nntp_group, FILE_APPEND);
$return_val = "240 Article received OK (queued)\r\n";
return ($return_val);
}
}
}
$return_val = "441 Posting failed\r\n";
if ($CONFIG['remote_server'] !== '') {
$sn_lockfile = $lockdir . '/' . $section . '-spoolnews.lock';
$sn_pid = file_get_contents($sn_lockfile);
if (posix_getsid($sn_pid) === false || ! is_file($sn_lockfile)) {
file_put_contents($sn_lockfile, getmypid()); // create lockfile
} else {
file_put_contents($logfile, "\n" . format_log_date() . " " . $section . " Queuing local post: " . $nntp_group, FILE_APPEND);
$return_val = "240 Article received OK (queued)\r\n";
return ($return_val);
}
}
$local_groupfile = $spooldir . "/" . $section . "/local_groups.txt";
$article_date = strtotime($date_i);
// Get list of article numbers to find what number is next
$local = get_next_article_number($nntp_group);
if ($article_date > time())
$article_date = time();
$in_file = fopen($filename, 'r');
$tmp_file = tempnam(sys_get_temp_dir(), 'rslmsg-');
// Prepare some tradspool paths
if ($CONFIG['article_database'] !== '1') {
$path = $spooldir . "/articles/";
$grouppath = $path . preg_replace('/\./', '/', $nntp_group);
$tradspool_out_file = $grouppath . "/" . $local;
// $tradspool_out_file = fopen($grouppath . "/" . $local, 'w+');
if (! is_dir($grouppath)) {
mkdir($grouppath, 0755, true);
}
}
$header = 1;
$tmp_file_handle = fopen($tmp_file, 'w');
while ($buf = fgets($in_file)) {
if ((trim($buf) == "") && ($header == 1)) {
$current_article['xref'] = "Xref: " . $CONFIG['pathhost'];
foreach ($allgroups as $agroup) {
$agroup = trim($agroup);
if ((! testGroup($agroup)) || $agroup == '') {
continue;
}
if ($group == $agroup) {
$artnum = $local;
} else {
$artnum = get_next_article_number($agroup);
}
if ($artnum > 0) {
$current_article['xref'] .= ' ' . $agroup . ':' . $artnum;
}
}
$header = 0;
fputs($tmp_file_handle, $current_article['xref'] . PHP_EOL);
$buf .= '';
}
if ($header == 1) {
if (stripos($buf, "Content-Type: ") === 0) {
preg_match('/.*charset=.*/', $buf, $te);
$content_type = explode("Content-Type: text/plain; charset=", $te[0]);
}
if (stripos($buf, "Newsgroups: ") === 0) {
$response = str_ireplace($group, $group, $buf);
// Identify each group name for xref
$groupnames = explode("Newsgroups: ", $buf);
$allgroups = preg_split("/\ |\,/", $groupnames[1]);
$ref = 0;
}
}
fputs($tmp_file_handle, rtrim($buf, "\n\r") . PHP_EOL);
}
fputs($tmp_file_handle, "\n.\n");
fclose($tmp_file_handle);
fclose($in_file);
touch($tmp_file, $article_date);
file_put_contents($logfile, "\n" . format_log_date() . " " . $section . " Inserting local post: " . $nntp_group . ":" . $local, FILE_APPEND);
$current_article = array();
$current_article['article'] = file_get_contents($tmp_file);
if ($CONFIG['article_database'] == '1') {
if (isset($content_type[1])) {
$this_snippet = get_search_snippet($body, $content_type[1]);
} else {
$this_snippet = get_search_snippet($body);
}
unlink($tmp_file);
} else {
rename($tmp_file, $tradspool_out_file);
}
$current_article['mid'] = $mid_i;
$current_article['epochdate'] = $article_date;
$current_article['stringdate'] = $date_i;
$current_article['from'] = $from_i;
$current_article['subject'] = $subject_i;
$current_article['references'] = $references_i;
$current_article['bytes'] = $bytes_i;
$current_article['lines'] = $lines_i;
$current_article['snippet'] = $this_snippet;
foreach ($allgroups as $agroup) {
$agroup = trim($agroup);
if ((! testGroup($agroup)) || $agroup == '') {
continue;
}
$current_article['group'] = $agroup;
if ($nntp_group == $agroup) {
$current_article['local'] = $local;
insert_article_from_array($current_article);
} else {
$current_article['local'] = get_next_article_number($agroup);
insert_article_from_array($current_article);
}
}
$references = "";
// End Overview
$grouplist = file($local_groupfile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$saveconfig = fopen($local_groupfile, 'w+');
$local++;
foreach ($grouplist as $savegroup) {
$name = explode(':', $savegroup);
if (strcmp($name[0], $nntp_group) == 0) {
fwrite($saveconfig, $nntp_group . ":" . $local . "\n");
} else {
fwrite($saveconfig, $savegroup . "\n");
}
}
fclose($saveconfig);
unlink($sn_lockfile);
$return_val = "240 Article received OK (posted)\r\n";
file_put_contents($logfile, "\n" . format_log_date() . " " . $nntp_group . ":" . --$local . " " . trim($return_val), FILE_APPEND);
return ($return_val);
}
function find_article_by_msgid($msgid)
{
global $spooldir;
$database = $spooldir . '/articles-overview.db3';
$table = 'overview';
$overview_dbh = overview_db_open($database, $table);
$stmt = $overview_dbh->prepare("SELECT * FROM $table WHERE msgid like :terms");
$stmt->bindParam(':terms', $msgid);
$stmt->execute();
while ($found = $stmt->fetch()) {
$return['newsgroup'] = $found['newsgroup'];
$return['number'] = $found['number'];
$return['msgid'] = $found['msgid'];
break;
}
$overview_dbh = null;
return $return;
}
function create_node_ssl_cert($pemfile)
{
global $CONFIG, $ssldir, $webtmp, $logdir, $config_dir, $spooldir;
include $config_dir . '/letsencrypt.inc.php';
$logfile = $logdir . '/nntp.log';
$uinfo = posix_getpwnam($CONFIG['webserver_user']);
$pubkeyfile = $ssldir . '/pubkey.pem';
$pubkeytxtfile = $webtmp . '/pubkey.txt';
$ssltime = filectime($letsencrypt['path'] . 'fullchain.pem');
if (isset($letsencrypt['path'])) {
file_put_contents($logfile, "\n" . format_log_date() . " Checking " . $letsencrypt['path'] . "fullchain.pem time", FILE_APPEND);
if ($ssltime > filectime($pemfile)) {
file_put_contents($logfile, "\n" . format_log_date() . " " . $letsencrypt['path'] . "fullchain.pem newer. Reloading cert.", FILE_APPEND);
touch($spooldir . '/ssl.reload');
}
}
if (! file_exists($spooldir . '/ssl.reload')) {
if ((is_file($pemfile)) && (is_file($pubkeyfile)) && (is_file($pubkeytxtfile))) {
if (md5_file($pubkeyfile) == md5_file($pubkeytxtfile)) {
return;
}
}
}
@unlink($spooldir . '/ssl.reload');
unlink($pemfile);
unlink($pubkeyfile);
unlink($pubkeytxtfile);
/* Use letsencrypt */
if ((isset($letsencrypt['server.pem'])) && (isset($letsencrypt['pubkey.pem']))) {
echo "Using existing LetsEncrypt certificate.\n";
file_put_contents($logfile, "\n" . format_log_date() . " Using existing LetsEncrypt certificate.", FILE_APPEND);
file_put_contents($pemfile, $letsencrypt['server.pem'] . $letsencrypt['privkey']);
file_put_contents($pubkeyfile, $letsencrypt['pubkey.pem']);
file_put_contents($pubkeytxtfile, $letsencrypt['pubkey.pem']);
touch($pemfile, $ssltime);
touch($pubkeyfile, $ssltime);
touch($pubkeytxtfile, $ssltime);
} else {
/* Create self signed cert */
file_put_contents($logfile, "\n" . format_log_date() . " Creating self-signed certificate.", FILE_APPEND);
$certificateData = array(
"countryName" => "US",
"stateOrProvinceName" => "New York",
"localityName" => "New York City",
"organizationName" => "Rocksolid",
"organizationalUnitName" => "Rocksolid Light",
"commonName" => $CONFIG['organization'],
"emailAddress" => "rocksolid@example.com"
);
// Generate certificate
$privateKey = openssl_pkey_new();
$certificate = openssl_csr_new($certificateData, $privateKey);
$certificate = openssl_csr_sign($certificate, null, $privateKey, 365);
// Generate PEM file
$pem_passphrase = null; // empty for no passphrase
$pem = array();
openssl_x509_export($certificate, $pem[0]);
openssl_pkey_export($privateKey, $pem[1], $pem_passphrase);
$pem = implode($pem);
$pubkey = openssl_pkey_get_details($privateKey);
// Save PEM file
file_put_contents($pemfile, $pem);
file_put_contents($pubkeyfile, $pubkey['key']);
file_put_contents($pubkeytxtfile, $pubkey['key']);
}
chown($pemfile, $uinfo["uid"]);
chown($pubkeyfile, $uinfo["uid"]);
chown($pubkeytxtfile, $uinfo["uid"]);
chmod($pemfile, 0660);
chmod($pubkeyfile, 0660);
chmod($pubkeytxtfile, 0660);
}