diff --git a/boardlink/main.php b/boardlink/main.php new file mode 100644 index 00000000..cd493e3b --- /dev/null +++ b/boardlink/main.php @@ -0,0 +1,251 @@ +boardname = $boardname; + $this->self = $self; + $this->connected = $connected; + + $this->queue = array(); + } + + function send_data($uri, $password, $data) { + ignore_user_abort(true); // this is a critical code, we don't want users to desync boards + // by quitting at a random moment + + $d = http_build_query(array('password' => $password, + 'from' => $this->self, + 'version' => BOARDLINK_VERSION, + 'data' => serialize($data))); + $ctx = stream_context_create(array('http' => array( + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\n". + "Content-length: ".strlen($d)."\r\n", + 'content' => $d))); + $this->queue[] = array($ctx, $uri, $data['action']); + } + + function commit_send_as_last() { + register_shutdown_function(array($this, "commit_send")); + } + + function commit_send() { + global $request_finished; + + if (function_exists("fastcgi_finish_request") && !isset($request_finished)) { + fastcgi_finish_request(); // hooray! php-fpm! + $request_finished = true; + } + + foreach ($this->queue as $value) { + list($ctx, $uri, $action) = $value; + $fp = file_get_contents($uri.'callback.php', false, $ctx); + + _syslog(LOG_INFO, "BoardLink: sent query of type $action from {$this->self} to $uri. Query yielded $fp"); + } + + $this->queue = array(); + } + + function configure_board() { + global $config; + + $config['blotter'] = "This board is synchronized with "; + $a = array_values($this->connected); + array_walk($a, function(&$v, $k){ + $v = "$v"; + }); + $count = count($a); + + if ($count == 0) { + $synced = ''; + } elseif ($count == 1) { + $synced = $a[0]; + } else { + $synced = implode(', ', array_slice($a,0,$count-1)) . ' and ' . end($a); + } + $config['blotter'] .= $synced; + + if ($config['vichan_federation']) { + if ($config['locale'] != "en") { + $config['locale'] = "en"; + $config['file_script'] = "main-en.js"; + } + $config['country_flags'] = true; + } + + event_handler('delete', function($post) { + global $delete_from; + + $data = array(); + $data['action'] = 'delete'; + $data['post'] = $post; + foreach ($this->connected as $password => $uri) { + if (!$delete_from || $delete_from != $uri) { + $this->send_data($uri, $password, $data); + } + } + register_shutdown_function(array($this, "commit_send_as_last")); + }); + + event_handler('post-after', function($post) { + $data = array(); + $data['action'] = 'create'; + if (!isset ($post['ip'])) $post['ip'] = $_SERVER['REMOTE_ADDR']; + $data['post'] = $post; + foreach ($this->connected as $password => $uri) { + if (!isset($data['post']['origin']) || $data['post']['origin'] != $uri) { + $this->send_data($uri, $password, $data); + } + } + register_shutdown_function(array($this, "commit_send_as_last")); + }); + } + + function handle_error($err, $from, $sort) { + _syslog(LOG_INFO, "BoardLink: received query of type $sort from $from to {$this->self}. Query finished with $err"); + die('{status:"'.$err.'",version:"'.BOARDLINK_VERSION.'"}'); + } + + function configure_callback() { + global $board, $config, $delete_from, $build_pages, $pdo; + + if (!isset ($_POST['password'])) { + $this->handle_error("ERR_NOREQUEST", "nil", "nil"); + } + + if (!isset ($this->connected[$_POST['password']])) { + $this->handle_error("ERR_PASSWD", $_POST['from'], "nil"); + } + $uri = $this->connected[$_POST['password']]; + if ($uri != $_POST['from']) { + $this->handle_error("ERR_FROM", $_POST['from'], "nil"); + } + $data = unserialize($_POST['data']); + + openBoard($this->boardname); + + switch ($data['action']) { + case 'create': + //Check if thread exists + if (!$data['post']['op']) { + $query = prepare(sprintf("SELECT `sticky`,`locked`,`sage` FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri'])); + $query->bindValue(':id', $data['post']['thread'], PDO::PARAM_INT); + $query->execute() or error(db_error()); + + if (!$thread = $query->fetch(PDO::FETCH_ASSOC)) { + $this->handle_error("ERR_DESYNC", $uri, $data['action']); + } + + $numposts = numPosts($data['post']['thread']); + } + + $a = array("src" => "file", "thumb" => "thumb"); + foreach ($a as $dir => $field) { + if (isset($data['post'][$field]) && $data['post'][$field] && + $data['post'][$field] != 'spoiler' && $data['post'][$field] != 'deleted') { + // Security filename checks + if (preg_match('@\.php|\.phtml|\.ht|\.\.|\x00|/@i', $data['post'][$field])) { + $this->handle_error("ERR_SECURITY", $uri, $data['action']); + } + $i = file_get_contents($uri.$dir.'/'.$data['post'][$field]); + file_put_contents($this->boardname.'/'.$dir.'/'.$data['post'][$field], $i); + } + } + $tmpid = post($data['post']); + + // Post doesn't cover custom post IDs + $query = prepare(sprintf("UPDATE ``posts_%s`` SET `id`=:id WHERE `id`=:tmpid", $board['uri'])); + $query->bindValue("id", $id = $data['post']['id']); + $query->bindValue("tmpid", $tmpid); + if (!$query->execute()) { + $query = prepare(sprintf("DELETE FROM ``posts_%s`` WHERE `id`=:tmpid", $board['uri'])); + $query->bindValue("tmpid", $tmpid); + $query->execute(); + + // Reset the auto increment + query(sprintf("ALTER TABLE ``posts_%s`` AUTO_INCREMENT = 1", $board['uri'])); + + $this->handle_error("ERR_DUPLICATE_ID", $uri, $data['action']); + } + + // Reset the auto increment + query(sprintf("ALTER TABLE ``posts_%s`` AUTO_INCREMENT = 1", $board['uri'])); + + $post = &$data['post']; + $post['origin'] = $uri; + + // The rest is just a copied code from post.php + + if (isset($post['tracked_cites']) && !empty($post['tracked_cites'])) { + $insert_rows = array(); + foreach ($post['tracked_cites'] as $cite) { + $insert_rows[] = '(' . + $pdo->quote($board['uri']) . ', ' . (int)$id . ', ' . + $pdo->quote($cite[0]) . ', ' . (int)$cite[1] . ')'; + } + query('INSERT INTO ``cites`` VALUES ' . implode(', ', $insert_rows)) or error(db_error()); + } + + if (!$post['op'] && strtolower($post['email']) != 'sage' && !$thread['sage'] && ($config['reply_limit'] == 0 + || $numposts['replies']+1 < $config['reply_limit'])) { + bumpThread($post['thread']); + } + + buildThread($post['op'] ? $id : $post['thread']); + + if ($config['try_smarter'] && $post['op']) + $build_pages = range(1, $config['max_pages']); + + if ($post['op']) + clean(); + + event('post-after', $post); + + buildIndex(); + + if ($post['op']) + rebuildThemes('post-thread', $board['uri']); + else + rebuildThemes('post', $board['uri']); + + break; + + case 'delete': + $delete_from = $uri; + deletePost($data['post']['id'], false); + $delete_from = false; + + buildIndex(); + rebuildThemes('post-delete', $board['uri']); + + break; + + default: + $this->handle_error("ERR_UNSUPPORTED", $uri, $data['action']); + } + + $this->handle_error("OK", $uri, $data['action']); + } +} +?>