changeset 0:d7ab68b71c74

Initial commit
author Ivo Smits <Ivo@UCIS.nl>
date Mon, 11 Apr 2011 22:44:47 +0200
parents
children 61fac319ca3e
files config.php.example fetchnews.php pdo.php server.php
diffstat 4 files changed, 382 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/config.php.example	Mon Apr 11 22:44:47 2011 +0200
@@ -0,0 +1,2 @@
+<?php
+$db = PDOWrapper::connectMysql('localhost', 'dbname', 'username', 'password');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fetchnews.php	Mon Apr 11 22:44:47 2011 +0200
@@ -0,0 +1,88 @@
+<?php
+require_once './pdo.php';
+require_once './config.php';
+
+foreach ($db->evalAllAssoc('SELECT * FROM `peers`') as $peer) {
+	$socket = stream_socket_client($peer['address']);
+	if ($socket === FALSE) die("Could not connect to peer $peer[address]\n");
+	$line = nntp_readline($socket);
+	$code = strtok($line, " \t");
+	if ($code != 200) die("Error code $code from $peer[address]\n");
+	foreach ($db->evalAllAssoc('SELECT * FROM `peergroups` WHERE `peer` = ?', $peer['id']) as $peergroup) {
+		$group = $db->evalRowAssoc('SELECT * FROM `groups` WHERE `id` = ?', $peergroup['group']);
+		nntp_writeline($socket, 'GROUP '.$group['name']);
+		$line = nntp_readline($socket);
+		$code = strtok($line, " \t");
+		if ($code != 211) die("Error code $code from $peer[address]\n");
+		strtok(" \t"); //number of articles
+		$low = strtok(" \t");
+		$high = strtok(" \t");
+		strtok(" \t"); //group name
+		if ($low != $peergroup['low'] || $high != $peergroup['high'] || $peergroup['low'] === NULL || $peergroup['high'] === NULL) {
+			for ($i = $low; $i <= $high; $i++) {
+				if ($i >= $peergroup['low'] && $i <= $peergroup['high'] && $peergroup['low'] !== NULL && $peergroup['high'] !== NULL) continue;
+				nntp_writeline($socket, 'STAT '.$i);
+				$line = nntp_readline($socket);
+				$code = strtok($line, " \t");
+				if ($code == 423) {
+					print("Gap in article numbering at $i\n");
+					continue;
+				}
+				if ($code != 223) die("Error code $code from $peer[address]\n");
+				strtok(" \t"); //article number
+				$messageid = strtok(" \t");
+				if ($messageid[0] != '<' || $messageid[strlen($messageid)-1] != '>') die("Malformed message ID $messageid\n");
+				$messageid = substr($messageid, 1, -1);
+				$message = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `messageid` = ?', $messageid);
+				if ($message) {
+					$groupmessage = $db->evalRowAssoc('SELECT * FROM `groupmessages` WHERE `group` = ? AND `message` = ?', array($group['id'], $message['id']));
+					if (!$groupmessage) {
+						$db->insert('INSERT INTO `groupmessages` (`group`, `message`) VALUES (?, ?)', array($group['id'], $message['id']));
+					}
+				} else {
+					nntp_writeline($socket, 'HEAD '.$i);
+					$line = nntp_readline($socket);
+					$code = strtok($line, " \t");
+					if ($code != 221) die("Error code $code from $peer[address]\n");
+					strtok(" \t"); //article number
+					$lines = nntp_readlines($socket);
+					$header = implode("\r\n", $lines);
+					
+					nntp_writeline($socket, 'BODY '.$i);
+					$line = nntp_readline($socket);
+					$code = strtok($line, " \t");
+					if ($code != 222) die("Error code $code from $peer[address]\n");
+					strtok(" \t"); //article number
+					$lines = nntp_readlines($socket);
+					$body = implode("\r\n", $lines);
+					
+					$id = $db->insert('INSERT INTO `messages` (`messageid`, `header`, `body`) VALUES (?, ?, ?)', array($messageid, $header, $body));
+					$db->insert('INSERT INTO `groupmessages` (`group`, `message`) VALUES (?, ?)', array($group['id'], $id));
+				}
+			}
+			$db->update('UPDATE `peergroups` SET `low` = ?, `high` = ? WHERE `peer` = ? AND `group` = ?', array($low, $high, $peergroup['peer'], $peergroup['group']));
+		}
+	}
+	nntp_writeline($socket, 'QUIT');
+	fclose($socket);
+}
+
+function nntp_readline($socket) {
+	$line = rtrim(fgets($socket, 512), "\r\n");
+	print('R: '.$line."\n");
+	return $line;
+}
+function nntp_writeline($socket, $line) {
+	print('W: '.$line."\n");
+	fwrite($socket, $line."\r\n");
+}
+function nntp_readlines($socket) {
+	$line = nntp_readline($socket);
+	$lines = array();
+	while ($line != '.' && $line !== FALSE && $line !== FALSE) {
+		$lines[] = $line;
+		$line = nntp_readline($socket);
+	}
+	if ($line != '.') die("Unexpected end of message header\n");
+	return $lines;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pdo.php	Mon Apr 11 22:44:47 2011 +0200
@@ -0,0 +1,88 @@
+<?php
+class PDOWrapper {
+	private $numQueries = 0;
+	private $pdo = NULL;
+	
+	public static function connectMysql($host, $database, $user = NULL, $pass = NULL, $initialcommand = NULL) {
+		$options = array();
+		if ($initialcommand !== NULL) $options[PDO::MYSQL_ATTR_INIT_COMMAND] = $initialcommand;
+		return new self('mysql:dbname='.$database.';host='.$host, $user, $pass, $options);
+		//"SET NAMES utf8,time_zone = '+0:00'"
+	}
+	
+	public function __construct($dsn, $user = NULL, $pass = NULL, $options = NULL) {
+		try {
+			$this->pdo = new PDO($dsn, $user, $pass, $options);
+		} catch (PDOException $e) {
+			throw new Exception('Could not connect to database: '.$e->getMessage());
+		}
+		$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+	}
+	public function __call($name, $arguments) {
+		if ($name == 'eval') {
+			$q = array_shift($arguments);
+			return $this->evalField($q, $arguments);
+		}
+	}
+	
+	private function queryi($query, $args = NULL) {
+		$this->numQueries++;
+		if ($args === NULL) {
+			//$stmt = $db_pdo->query($query);
+			$stmt = $this->pdo->prepare($query);
+			$stmt->execute();
+		} else {
+			if (!is_array($args)) $args = array($args);
+			$stmt = $this->pdo->prepare($query);
+			$stmt->execute($args);
+		}
+		return $stmt;
+	}
+	function insert($query, $args = NULL) {
+		$this->queryi($query, $args)->closeCursor();
+		return $this->pdo->lastInsertId();
+	}
+	function update($query, $args = NULL) {
+		global $db_pdo;
+		$stmt = $this->queryi($query, $args);
+		$cnt = $stmt->rowCount();
+		$stmt->closeCursor();
+		return $cnt;
+	}
+	function evalField($query, $args = NULL) {
+		$stmt = $this->queryi($query, $args);
+		$ret = $stmt->fetchColumn();
+		$stmt->closeCursor();
+		return $ret;
+	}
+	function evalRow($query, $args = NULL) {
+		$stmt = $this->queryi($query, $args);
+		$ret = $stmt->fetch(PDO::FETCH_NUM);
+		$stmt->closeCursor();
+		return $ret;
+	}
+	function evalRowAssoc($query, $args = NULL) {
+		$stmt = $this->queryi($query, $args);
+		$ret = $stmt->fetch(PDO::FETCH_ASSOC);
+		$stmt->closeCursor();
+		return $ret;
+	}
+	function evalAllAssoc($query, $args = NULL) {
+		$stmt = $this->queryi($query, $args);
+		$ret = $stmt->fetchAll(PDO::FETCH_ASSOC);
+		$stmt->closeCursor();
+		return $ret;
+	}
+	function evalAllKVP($query, $args = NULL) {
+		$stmt = $this->queryi($query, $args);
+		$ret = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
+		$stmt->closeCursor();
+		return $ret;
+	}
+	function evalColumn($query, $args = NULL) {
+		$stmt = $this->queryi($query, $args);
+		$ret = $stmt->fetchAll(PDO::FETCH_COLUMN);
+		$stmt->closeCursor();
+		return $ret;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server.php	Mon Apr 11 22:44:47 2011 +0200
@@ -0,0 +1,204 @@
+#!/usr/bin/php
+<?php
+chdir('/home/ivo/projects/pnewss');
+require_once './pdo.php';
+require_once './config.php';
+
+$logfile = fopen('./server.log', 'w');
+$currentgroup = NULL;
+$currentarticle = NULL;
+
+function exception_handler($exception) {
+	nntp_writeline(STDOUT, '');
+	nntp_writeline(STDOUT, '590 Exception: '.$exception->getMessage());
+	die();
+}
+function error_handler($errno, $errstr, $errfile, $errline) {
+	nntp_writeline(STDOUT, '');
+	nntp_writeline(STDOUT, '590 Error in file '.$errfile.' line '.$errline.' error '.$errno.' '.$errstr);
+	die();
+}
+set_exception_handler('exception_handler');
+set_error_handler("error_handler");
+
+nntp_writeline(STDOUT, '200 pNewss ready');
+while (TRUE) {
+	$line = nntp_readline(STDIN);
+	if ($line === FALSE || $line === NULL) break;	
+	$cmd = strtok($line, " \t");
+	switch (strtoupper($cmd)) {
+		case 'LIST':
+			nntp_writeline(STDOUT, '215 List of groups follows');
+			foreach ($db->evalAllAssoc('SELECT * FROM `groups`') as $group) {
+				$groupmessages = $db->evalRow('SELECT MIN(`number`), MAX(`number`) FROM `groupmessages` WHERE `group` = ?', $group['id']);
+				nntp_writeline(STDOUT, $group['name'].' '.intval($groupmessages[1]).' '.intval($groupmessages[0]).' n');
+			}
+			nntp_writeline(STDOUT, '.');
+			break;
+		case 'GROUP':
+			$groupname = strtok(" \t");
+			$group = $db->evalRowAssoc('SELECT * FROM `groups` WHERE `name` = ?', $groupname);
+			if ($group === FALSE) {
+				nntp_writeline(STDOUT, '411 No such group '.$groupname);
+			} else {
+				$currentgroup = $group;
+				$groupmessages = $db->evalRow('SELECT MIN(`number`), MAX(`number`), COUNT(`number`) FROM `groupmessages` WHERE `group` = ?', $group['id']);
+				nntp_writeline(STDOUT, '211 '.intval($groupmessages[2]).' '.intval($groupmessages[0]).' '.intval($groupmessages[1]).' '.$group['name']);
+				if ($groupmessages[0] === NULL) {
+					$currentarticle = NULL;
+				} else {
+					$currentarticle = $db->evalRowAssoc('SELECT * FROM `groupmessages` WHERE `group` = ? AND `number` = ?', array($group['id'], $groupmessages[0]));
+					if ($currentarticle === FALSE) $currentarticle = NULL;
+				}
+			}
+			break;
+		case 'STAT':
+			$article = nntp_get_article(strtok(" \t"));
+			if ($article === NULL) break;
+			nntp_writeline(STDOUT, '223 '.$article['messagenumber'].' <'.$article['messageid'].'> stat');
+			break;
+		case 'HEAD':
+			$article = nntp_get_article(strtok(" \t"));
+			if ($article === NULL) break;
+			nntp_writeline(STDOUT, '221 '.$article['messagenumber'].' <'.$article['messageid'].'> head');
+			foreach (explode("\r\n", $article['header']) as $line) nntp_writeline(STDOUT, $line);
+			nntp_writeline(STDOUT, '.');
+			break;
+		case 'BODY':
+			$article = nntp_get_article(strtok(" \t"));
+			if ($article === NULL) break;
+			nntp_writeline(STDOUT, '222 '.$article['messagenumber'].' <'.$article['messageid'].'> body');
+			foreach (explode("\r\n", $article['body']) as $line) nntp_writeline(STDOUT, $line);
+			nntp_writeline(STDOUT, '.');
+			break;
+		case 'ARTICLE':
+			$article = nntp_get_article(strtok(" \t"));
+			if ($article === NULL) break;
+			nntp_writeline(STDOUT, '220 '.$article['messagenumber'].' <'.$article['messageid'].'> article');
+			foreach (explode("\r\n", $article['header']) as $line) nntp_writeline(STDOUT, $line);
+			nntp_writeline(STDOUT, '');
+			foreach (explode("\r\n", $article['body']) as $line) nntp_writeline(STDOUT, $line);
+			nntp_writeline(STDOUT, '.');
+			break;
+		case 'LAST':
+			if ($currentarticle === NULL) {
+				nntp_writeline(STDOUT, '420 no current article has been selected');
+				break;
+			}
+			$article = $db->evalRowAssoc('SELECT * FROM `groupmessages` WHERE `group` = ? AND `number` < ? ORDER BY `number` DESC LIMIT 1', array($currentarticle['group'], $currentarticle['number']));
+			if ($article === FALSE) {
+				nntp_writeline(STDOUT, '422 no previous article in this group');
+			} else {
+				$articlea = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `id` = ?', $article['message']);
+				if ($articlea === FALSE) {
+					nntp_writeline(STDOUT, '430 no such article found');
+					return NULL;
+				}
+				$currentarticle = $article;
+				nntp_writeline(STDOUT, '223 '.$article['number'].' <'.$articlea['messageid'].'> ok');
+			}
+			break;
+		case 'NEXT':
+			if ($currentarticle === NULL) {
+				nntp_writeline(STDOUT, '420 no current article has been selected');
+				break;
+			}
+			$article = $db->evalRowAssoc('SELECT * FROM `groupmessages` WHERE `group` = ? AND `number` > ? ORDER BY `number` ASC LIMIT 1', array($currentarticle['group'], $currentarticle['number']));
+			if ($article === FALSE) {
+				nntp_writeline(STDOUT, '422 no previous article in this group');
+			} else {
+				$articlea = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `id` = ?', $article['message']);
+				if ($articlea === FALSE) {
+					nntp_writeline(STDOUT, '430 no such article found');
+					return NULL;
+				}
+				$currentarticle = $article;
+				nntp_writeline(STDOUT, '223 '.$article['number'].' <'.$articlea['messageid'].'> ok');
+			}
+			break;
+		case 'QUIT':
+			nntp_writeline(STDOUT, '205 .');
+			return;
+		case 'XOVER':
+		case 'MODE':
+		case 'CAPABILITIES':
+			nntp_writeline(STDOUT, '500 Command not implemented');
+			break;
+		default:
+			nntp_writeline(STDOUT, '500 Command not understood');
+			break;
+	}
+}
+
+function nntp_get_article($article) {
+	global $currentgroup, $currentarticle, $db;
+	if ($article === FALSE) {
+		if ($currentarticle === NULL) {
+			nntp_writeline(STDOUT, '420 no current article has been selected');
+			return NULL;
+		}
+		$messagenumber = $currentarticle['number'];
+		$article = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `id` = ?', $currentarticle['message']);
+		if ($article === FALSE) {
+			nntp_writeline(STDOUT, '430 no such article found');
+			return NULL;
+		}
+	} elseif (strlen($article) > 2 && $article[0] == '<' && $article[strlen($article)-1] == '>') {
+		$messagenumber = 0;
+		$article = substr($article, 1, -1);
+		$article = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `messageid` = ?', $article);
+		if ($article === FALSE) {
+			nntp_writeline(STDOUT, '430 no such article found');
+			return NULL;
+		}
+	} elseif (is_numeric($article)) {
+		if ($currentgroup === NULL) {
+			nntp_writeline(STDOUT, '412 no newsgroup has been selected');
+			return NULL;
+		}
+		$article = $db->evalRowAssoc('SELECT * FROM `groupmessages` WHERE `group` = ? AND `number` = ?', array($currentgroup['id'], $article));
+		if ($article === FALSE) {
+			nntp_writeline(STDOUT, '423 no such article number in this group');
+			return NULL;
+		}
+		$currentarticle = $article;
+		$messagenumber = $article['number'];
+		$article = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `id` = ?', $article['message']);
+		if ($article === FALSE) {
+			nntp_writeline(STDOUT, '430 no such article found');
+			return NULL;
+		}
+	} else {
+		nntp_writeline(STDOUT, '500 Error in arguments');
+	}
+	$article['messagenumber'] = $messagenumber;
+	return $article;
+}
+
+function writelog($line) {
+	global $logfile;
+	fwrite($logfile, $line."\n");
+}
+function nntp_readline($socket) {
+	global $logfile;
+	$line = fgets($socket, 512);
+	if ($line === FALSE || $line === NULL) return $line;
+	$line = rtrim($line, "\r\n");
+	fwrite($logfile, 'R: '.$line."\n");
+	return $line;
+}
+function nntp_writeline($socket, $line) {
+	global $logfile;
+	fwrite($logfile, 'W: '.$line."\n");
+	fwrite($socket, $line."\r\n");
+}
+function nntp_readlines($socket) {
+	$line = nntp_readline($socket);
+	$lines = array();
+	while ($line != '.' && $line !== FALSE && $line !== FALSE) {
+		$lines[] = $line;
+		$line = nntp_readline($socket);
+	}
+	if ($line != '.') die("Unexpected end of message header\n");
+	return $lines;
+}