view server.php @ 2:40e545510a57

Added support for POSTing to the server, added readme and todo, added support for upstream synchronization using POST
author Ivo Smits <Ivo@UCIS.nl>
date Tue, 12 Apr 2011 00:29:41 +0200
parents d7ab68b71c74
children 0dcdb73cbcbf
line wrap: on
line source

#!/usr/bin/php
<?php
chdir(__DIR__);
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 'POST':
			nntp_writeline(STDOUT, '340 Ok, recommended message-ID <'.md5(time().rand()).'@pNews.Core.UCIS.nl>');
			$lines = nntp_readlines(STDIN);
			$header = array();
			$headers = array();
			while (count($lines)) {
				$line = array_shift($lines);
				if (!strlen($line)) break;
				$parts = explode(': ', $line, 2);
				$headers[strtoupper($parts[0])] = $parts[1];
				switch (strtoupper($parts[0])) {
					case 'PATH':
						$line = $parts[0].': pNews.Core.UCIS.nl!'.$parts[1];
						break;
					case 'FROM': case 'NEWSGROUPS': case 'SUBJECT': case 'DATE': case 'ORGANIZATION':
					case 'LINES': case 'MESSAGE-ID': case 'MIME-VERSION': case 'CONTENT-TYPE': case 'CONTENT-TRANSFER-ENCODING':
					case 'USER-AGENT': case 'REFERENCES': case 'REPLY-TO': case 'SENDER': case 'FOLLOWUP-TO':
					case 'EXPIRES': case 'CONTROL': case 'DISTRIBUTION': case 'KEYWORDS': case 'SUMMARY':
					case 'IN-REPLY-TO':
						break;
					case 'NNTP-POSTING-HOST': case 'X-TRACE': case 'XREF': case 'X-COMPLAINTS-TO':
					case 'NNTP-POSTING-DATE':
						$line = NULL;
						break;
					default:
						writelog("Received unknown header $parts[0]");
				}
				if ($line !== NULL) $header[] = $line;
			}
			if (!isset($headers['NEWSGROUPS'])) {
				nntp_writeline(STDOUT, '441 Missing required Newsgroups: header');
				break;
			}
			if (!isset($headers['MESSAGE-ID'])) {
				$headers['MESSAGE-ID'] = '<'.md5(time().rand()).'@pNews.Core.UCIS.nl>';
				$header[] = 'Message-ID: '.$headers['MESSAGE-ID'];
			}
			if (!isset($headers['PATH'])) {
				$headers['PATH'] = 'pNews.Core.UCIS.nl';
				$header[] = 'Path: '.$headers['PATH'];
			}
			$msgid = $headers['MESSAGE-ID'];
			if (strlen($msgid) <= 2 || $msgid[0] != '<' || $msgid[strlen($msgid)-1] != '>') {
				nntp_writeline(STDOUT, '441 435 Bad Message-ID');
				break;
			}
			$msgid = substr($msgid, 1, -1);
			$article = $db->evalRowAssoc('SELECT * FROM `messages` WHERE `messageid` = ?', $msgid);
			if ($article !== FALSE) {
				nntp_writeline(STDOUT, '441 435 Duplicate');
				return NULL;
			}
			$id = $db->insert('INSERT INTO `messages` (`messageid`, `header`, `body`) VALUES (?, ?, ?)', array($msgid, implode("\r\n", $header), implode("\r\n", $lines)));
			foreach (explode(',', $headers['NEWSGROUPS']) as $groupname) {
				$group = $db->evalRowAssoc('SELECT * FROM `groups` WHERE `name` = ?', $groupname);
				if ($group === FALSE) continue;
				$db->insert('INSERT INTO `groupmessages` (`group`, `message`) VALUES (?, ?)', array($group['id'], $id));
			}
			nntp_writeline(STDOUT, '240 Article received <'.$msgid.'>');
			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;
}