summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorczaks <marcin@6irc.net>2016-08-15 00:56:06 +0200
committerczaks <marcin@6irc.net>2016-08-15 00:56:06 +0200
commita779b96370d3353c8409d64b70c724fa783ad219 (patch)
tree0ef37aebac141cb0857caeaddde2a393159ce843
parent1c24c69999db67ebbc13fef7c53e37d08768008f (diff)
second iteration of nntpchan implementation
-rw-r--r--inc/config.php24
-rw-r--r--inc/nntpchan/nntpchan.php149
-rw-r--r--inc/nntpchan/tests.php30
-rw-r--r--post.php29
4 files changed, 231 insertions, 1 deletions
diff --git a/inc/config.php b/inc/config.php
index a8afdf7a..bfb774a8 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -1695,6 +1695,11 @@
* ==================
*/
+/*
+ * Please keep in mind that NNTPChan support in vichan isn't finished yet / is in an experimental
+ * state. Please join #nntpchan on Rizon in order to peer with someone.
+ */
+
$config['nntpchan'] = array();
// Enable NNTPChan integration
@@ -1703,9 +1708,26 @@
// NNTP server
$config['nntpchan']['server'] = "localhost:1119";
- // Global dispatch array. Add your boards to it to enable them.
+ // Global dispatch array. Add your boards to it to enable them. Please make
+ // sure that this setting is set in a global context.
$config['nntpchan']['dispatch'] = array(); // 'overchan.test' => 'test'
+ // Trusted peer - an IP address of your NNTPChan instance. This peer will have
+ // increased capabilities, eg.: will evade spamfilter.
+ $config['nntpchan']['trusted_peer'] = '127.0.0.1';
+
+ // Salt for message ID generation. Keep it long and secure.
+ $config['nntpchan']['salt'] = 'change_me+please';
+
+ // A local message ID domain. Make sure to change it.
+ $config['nntpchan']['domain'] = 'example.vichan.net';
+
+ // An NNTPChan group name.
+ // Please set this setting in your board/config.php, not globally.
+ $config['nntpchan']['group'] = false; // eg. 'overchan.test'
+
+
+
/*
* ====================
* Other/uncategorized
diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php
new file mode 100644
index 00000000..25215e94
--- /dev/null
+++ b/inc/nntpchan/nntpchan.php
@@ -0,0 +1,149 @@
+<?php
+/*
+ * Copyright (c) 2016 vichan-devel
+ */
+
+defined('TINYBOARD') or exit;
+
+function gen_msgid($board, $id) {
+ global $config;
+
+ $b = preg_replace("/[^0-9a-zA-Z$]/", 'x', $board);
+ $salt = sha1($board . "|" . $id . "|" . $config['nntpchan']['salt']);
+ $salt = substr($salt, 0, 7);
+ $salt = base_convert($salt, 16, 36);
+
+ return "<$b.$id.$salt@".$config['nntpchan']['domain'].">";
+}
+
+
+function gen_nntp($headers, $files) {
+ if (count($files) == 0) {
+ }
+ else if (count($files) == 1 && $files[0]['type'] == 'text/plain') {
+ $content = $files[0]['text'] . "\r\n";
+ $headers['Content-Type'] = "text/plain; charset=UTF-8";
+ }
+ else {
+ $boundary = sha1($headers['Message-Id']);
+ $content = "";
+ $headers['Content-Type'] = "multipart/mixed; boundary=$boundary";
+ foreach ($files as $file) {
+ $content .= "--$boundary\r\n";
+ if (isset($file['name'])) {
+ $file['name'] = preg_replace('/[\r\n\0"]/', '', $file['name']);
+ $content .= "Content-Disposition: form-data; filename=\"$file[name]\"; name=\"attachment\"\r\n";
+ }
+ $type = explode('/', $file['type'])[0];
+ if ($type == 'text') {
+ $file['type'] .= '; charset=UTF-8';
+ }
+ $content .= "Content-Type: $file[type]\r\n";
+ if ($type != 'text' && $type != 'message') {
+ $file['text'] = base64_encode($file['text']);
+ $content .= "Content-Transfer-Encoding: base64\r\n";
+ }
+ $content .= "\r\n";
+ $content .= $file['text'];
+ $content .= "\r\n";
+ }
+ $content .= "--$boundary--\r\n";
+
+ $headers['Mime-Version'] = '1.0';
+ }
+ //$headers['Content-Length'] = strlen($content);
+ $headers['Date'] = date('r', $headers['Date']);
+ $out = "";
+ foreach ($headers as $id => $val) {
+ $val = str_replace("\n", "\n\t", $val);
+ $out .= "$id: $val\r\n";
+ }
+ $out .= "\r\n";
+ $out .= $content;
+ return $out;
+}
+
+function nntp_publish($msg, $id) {
+ $s = fsockopen("tcp://localhost:1119");
+ fgets($s);
+ fputs($s, "MODE STREAM\r\n");
+ fgets($s);
+ fputs($s, "TAKETHIS $id\r\n");
+ fputs($s, $msg);
+ fputs($s, "\r\n.\r\n");
+ fgets($s);
+ fclose($s);
+}
+
+function post2nntp($post, $msgid) {
+ global $config;
+
+ $headers = array();
+ $files = array();
+
+ $headers['Message-Id'] = $msgid;
+ $headers['Newsgroups'] = $config['nntpchan']['group'];
+ $headers['Date'] = time();
+ $headers['Subject'] = $post['subject'] ? $post['subject'] : "None";
+ $headers['From'] = $post['name'] . " <poster@" . $config['nntpchan']['domain'] . ">";
+
+ if ($post['email'] == 'sage') {
+ $headers['X-Sage'] = true;
+ }
+
+ if (!$post['op']) {
+ // Get muh parent
+ $query = prepare("SELECT `message_id` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id");
+ $query->bindValue(':board', $post['board']);
+ $query->bindValue(':id', $post['thread']);
+ $query->execute() or error(db_error($query));
+
+ if ($result = $query->fetch(PDO::FETCH_ASSOC)) {
+ $headers['References'] = $result['message_id'];
+ }
+ else {
+ return false; // We don't have OP. Discarding.
+ }
+ }
+
+ // Let's parse the body a bit.
+ $body = trim($post['body_nomarkup']);
+ $body = preg_replace('/\r?\n/', "\r\n", $body);
+ $body = preg_replace_callback('@>>(>/([a-zA-Z0-9_+-]+)/)?([0-9]+)@', function($o) use ($post) {
+ if ($o[1]) {
+ $board = $o[2];
+ }
+ else {
+ $board = $post['board'];
+ }
+ $id = $o[3];
+
+ $query = prepare("SELECT `message_id_digest` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id");
+ $query->bindValue(':board', $board);
+ $query->bindValue(':id', $id);
+ $query->execute() or error(db_error($query));
+
+ if ($result = $query->fetch(PDO::FETCH_ASSOC)) {
+ return ">>".substr($result['message_id_digest'], 0, 16);
+ }
+ else {
+ return $o[0]; // Should send URL imo
+ }
+ }, $body);
+ $body = preg_replace('/>>>>([0-9a-fA-F])+/', '>>\1', $body);
+
+
+ $files[] = array('type' => 'text/plain', 'text' => $body);
+
+ foreach ($post['files'] as $id => $file) {
+ $fc = array();
+
+ $fc['type'] = $file['type'];
+ $fc['text'] = file_get_contents($file['file_path']);
+ $fc['name'] = $file['name'];
+
+ $files[] = $fc;
+ }
+
+ return array($headers, $files);
+}
diff --git a/inc/nntpchan/tests.php b/inc/nntpchan/tests.php
new file mode 100644
index 00000000..a63789d7
--- /dev/null
+++ b/inc/nntpchan/tests.php
@@ -0,0 +1,30 @@
+<?php
+define('TINYBOARD', 'fuck yeah');
+require_once('nntpchan.php');
+
+
+die();
+
+$time = time();
+echo "\n@@@@ Thread:\n";
+echo $m0 = gennntp(["From" => "czaks <marcin@6irc.net>", "Message-Id" => "<1234.0000.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None"],
+[['type' => 'text/plain', 'text' => "THIS IS A NEW TEST THREAD"]]);
+echo "\n@@@@ Single msg:\n";
+echo $m1 = gennntp(["From" => "czaks <marcin@6irc.net>", "Message-Id" => "<1234.1234.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"],
+[['type' => 'text/plain', 'text' => "hello world, with no image :("]]);
+echo "\n@@@@ Single msg and pseudoimage:\n";
+echo $m2 = gennntp(["From" => "czaks <marcin@6irc.net>", "Message-Id" => "<1234.2137.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"],
+[['type' => 'text/plain', 'text' => "hello world, now with an image!"],
+ ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"]]);
+echo "\n@@@@ Single msg and two pseudoimages:\n";
+echo $m3 = gennntp(["From" => "czaks <marcin@6irc.net>", "Message-Id" => "<1234.1488.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"],
+[['type' => 'text/plain', 'text' => "hello world, now WITH TWO IMAGES!!!"],
+ ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"],
+ ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif2.gif"]]);
+shoveitup($m0, "<1234.0000.".$time."@example.vichan.net>");
+sleep(1);
+shoveitup($m1, "<1234.1234.".$time."@example.vichan.net>");
+sleep(1);
+shoveitup($m2, "<1234.2137.".$time."@example.vichan.net>");
+shoveitup($m3, "<1234.1488.".$time."@example.vichan.net>");
+
diff --git a/post.php b/post.php
index 4590a534..1e4edcee 100644
--- a/post.php
+++ b/post.php
@@ -13,7 +13,12 @@ if ((!isset($_POST['mod']) || !$_POST['mod']) && $config['board_locked']) {
$dropped_post = false;
+// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post.
if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) {
+ if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) {
+ error("NNTPChan: Forbidden. $_SERVER[REMOTE_ADDR] is not a trusted peer");
+ }
+
$_POST = array();
$_POST['json_response'] = true;
@@ -1057,7 +1062,31 @@ if (isset($_POST['delete'])) {
$query->bindValue(':message_id_digest', sha1($dropped_post['msgid']));
$query->bindValue(':headers', $dropped_post['headers']);
$query->execute() or error(db_error($query));
+ } // ^^^^^ For inbound posts ^^^^^
+ elseif ($config['nntpchan']['enabled'] && $config['nntpchan']['group']) {
+ // vvvvv For outbound posts vvvvv
+
+ require_once('inc/nntpchan/nntpchan.php');
+ $msgid = gen_msgid($post['board'], $post['id']);
+
+ list($headers, $files) = post2nntp($post, $msgid);
+
+ $message = gen_nntp($headers, $files);
+
+ $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES ".
+ "(:board , :id , :message_id , :message_id_digest , true , :headers)");
+
+ $query->bindValue(':board', $post['board']);
+ $query->bindValue(':id', $post['id']);
+ $query->bindValue(':message_id', $msgid);
+ $query->bindValue(':message_id_digest', sha1($msgid));
+ $query->bindValue(':headers', json_encode($headers));
+ $query->execute() or error(db_error($query));
+
+ // Let's broadcast it!
+ nntp_publish($message, $msgid);
}
+
insertFloodPost($post);
// Handle cyclical threads