Added C4Masterserver 1.1.5

floating-point
Benedict Etzel 2010-09-26 21:58:21 +02:00
parent 31ef03d555
commit 39c06d80b0
10 changed files with 831 additions and 0 deletions

View File

@ -0,0 +1,33 @@
__Quick start__
To directly use the masterserver you will need a webserver with PHP >= 5 and access to a MySQL-database. Start by opening the folder web and navigating through server and include, open the config.ini. Enter you're preferences there, everything is documented.
IMPORTANT: Before you continue make sure this folder will be inaccessible through the web later by making sure a working .htaccess is present on an Apache server or there is a corresponding chmod on the whole include folder after uploading later on.
Open a connection to your MySQL-server directly or via a tool like phpMyAdmin and query the command listet below to create the table structure. You can change the default prefix without any problems, just don't forget to change it in your config file.
Now upload the contents of the web/ folder. Now you should be able to open the root and see the server frontend. Make sure again, that the /server/include folder can NOT be accessed via web, since it contains your MySQL-data.
The installation should now be complete and ready to use. You can see the server link on the frontend, just put it in your Clonk network settings and you're done!
__Database__
CREATE TABLE IF NOT EXISTS `c4ms_flood` (
`ip` char(32) NOT NULL,
`count` int(11) NOT NULL,
`time` char(20) NOT NULL,
PRIMARY KEY (`ip`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `c4ms_games` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(255) NOT NULL,
`csid` varchar(255) NOT NULL,
`data` text NOT NULL,
`start` varchar(255) NOT NULL,
`time` varchar(255) NOT NULL,
`valid` tinyint(4) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
__License__
This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
__Append__
Please also note that 'Clonk' is a registered trademark of Matthes Bender (http://www.clonk.de)

View File

@ -0,0 +1,124 @@
<?php
/**
* C4Masterserver main frontend
*
* @package C4Masterserver
* @version 1.1.5-en
* @author Benedict Etzel <b.etzel@live.de>
* @license http://creativecommons.org/licenses/by/3.0/ CC-BY 3.0
*/
//error_reporting(E_NONE); //suppress errors
require_once('server/include/C4Masterserver.php');
require_once('server/include/C4Network.php');
require_once('server/include/FloodProtection.php');
require_once('server/include/ParseINI.php');
$config = file_get_contents('server/include/config.ini');
$link = mysql_connect(
ParseINI::ParseValue('mysql_host', $config),
ParseINI::ParseValue('mysql_user', $config),
ParseINI::ParseValue('mysql_password', $config)); //connect to MySQL
$db = mysql_selectdb(ParseINI::ParseValue('mysql_db', $config), $link); //select the database
if($link && $db) {
$server = new C4Masterserver($link, ParseINI::ParseValue('mysql_prefix', $config));
$server->SetTimeoutgames(intval(ParseINI::ParseValue('c4ms_timeoutgames', $config)));
$server->SetDeletegames(intval(ParseINI::ParseValue('c4ms_deletegames', $config)));
$server->SetMaxgames(intval(ParseINI::ParseValue('c4ms_maxgames', $config)));
$protect = new FloodProtection($link, ParseINI::ParseValue('mysql_prefix', $config));
$protect->SetMaxflood(intval(ParseINI::ParseValue('flood_maxrequests', $config)));
if($protect->CheckRequest($_SERVER['REMOTE_ADDR'])) { //flood protection
header('Content-Type: text/plain');
die('Flood protection.');
}
$games = '';
$list = $server->GetReferenceArray(true);
$players = '';
$count = 0;
foreach($list as $reference) {
if($reference['valid']) {
$games .= '<tr>';
$games .= '<td>'.htmlspecialchars(ParseINI::ParseValue('Title', $reference['data'])).'</td>';
$games .= '<td>'.htmlspecialchars(ParseINI::ParseValue('State', $reference['data'])).'</td>';
$games .= '<td>'.date("Y-m-d H:i", $reference['start']).'</td>';
$players = '';
$player_list = ParseINI::ParseValuesByCategory('Name', 'Player', $reference['data']);
foreach($player_list as $player) {
if(!empty($players)) $players .= ', ';
$players .= $player;
}
$games .= '<td>'.htmlspecialchars($players).'</td>';
}
if((ParseINI::ParseValue('State', $reference['data']) == 'Running') && $reference['time'] >= time() - 60*60*24) {
$count++;
}
}
$games = C4Network::CleanString($games);
$server->CleanUp();
}
$dirname = dirname($_SERVER['SCRIPT_NAME']);
$path = '';
if($dirname != '/') {
$path .= '/';
}
$dirname .= $path.'server/';
$server_link = strtolower($_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$dirname);
$engine = '';
$engine_string = ParseINI::ParseValue('c4ms_engine', $config);
if(!empty($engine_string)) {
$engine = '('.$engine_string.' only)';
}
?>
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>
<html>
<head>
<title>C4Masterserver</title>
<meta http-equiv='content-type' content='text/html; charset=utf-8'>
<meta http-equiv='content-style-type' content='text/css'>
<link rel='stylesheet' href='masterserver.css' type='text/css'>
</head>
<body>
<div id="masterserver">
<h1>Masterserver</h1>
<?php
if($link && $db) {
echo '<p>You can reach the masterserver by using the address <strong>'.$server_link.'</strong>';
if($engine) {
echo ' '.$engine;
}
echo '.</p>';
if(!empty($games)) {
echo '<p style="color: gray;">Following games are running now:</p>';
echo '<table>';
echo '<tr><th>Round</th><th>State</th><th>Begin</th><th>Players</th></tr>';
echo $games;
echo '</table>';
}
else {
echo '<p style="color: gray;">No games are currently running.</p>';
}
if($count > 0) {
if($count > 1) {
echo '<p>There have been '.$count.' running games in the last 24 hours.</p>';
}
else {
echo '<p>There has been one running game in the last 24 hours.</p>';
}
}
else {
echo '<p>There have been no games running in the last 24 hours.</p>';
}
}
else {
echo '<p style="color: red;">Error: Could not connect to database specified in config.</p>';
}
?>
<div id="masterserver_footer">
<p>Powered by C4Masterserver v<?php echo C4Masterserver::GetVersion(); ?> &bull; Coded by Benedict Etzel</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,54 @@
body {
font-family: 'Verdana', sans-serif;
}
#masterserver {
background: none repeat scroll 0 0 transparent;
margin: 0 auto;
padding-left: 76px;
padding-right: 76px;
padding-top: 7px;
}
#masterserver a, p, table {
font-size: 10pt;
color: black;
text-decoration: none;
}
#masterserver a {
border-bottom: 1px dotted black;
text-decoration: none;
}
#masterserver a:hover {
border-bottom: 1px solid black;
}
#masterserver h1 {
margin: 0 0 2px;
font-size: 1.3em;
font-weight: normal;
color: #222222;
padding: 0px;
}
#masterserver th {
text-align: center;
}
#masterserver td {
padding: 5px 30px;
}
#masterserver_footer {
margin-top: 10px;
border-top: 1px dotted gray;
}
#masterserver_footer p {
color: gray;
font-size: 0.7em;
margin-top: 2px;
float: right;
}

View File

@ -0,0 +1,2 @@
Order deny,allow
Deny from all

View File

@ -0,0 +1,187 @@
<?php
/**
* Provides functionality to add, update and remove
* game references based on a MySQL table.
*
* @package C4Masterserver
* @version 1.1.5-en
* @author Benedict Etzel <b.etzel@live.de>
* @license http://creativecommons.org/licenses/by/3.0/ CC-BY 3.0
*/
class C4Masterserver {
/**
* Stores the masterserver version.
*
* @var string
*/
static public $version = '1.1.5';
/**
* Stores the MySQL connection resource.
*
* @var resource
*/
private $link;
/**
* Stores the MySQL table prefix.
*
* @var string
*/
private $prefix;
/**
* Stores the seconds after which games timeout.
*
* @var int
*/
private $timeoutgames;
/**
* Stores the seconds after which games are removed.
*
* @var int
*/
private $deletegames;
/**
* Stores the maximum amount of games per ip.
*
* @var int
*/
private $maxgames;
/**
* The C4Masterserver constructor.
*
* @param resource $link
* @return C4Masterserver
*/
public function __construct($link, $prefix) {
$this->link = $link;
$this->prefix = $prefix;
$this->timeoutgames = 600;
$this->deletegames = 60*60*24;
$this->maxgames = 5;
}
/**
* Returns the C4Masterserver version.
*
* @param void
* @return string
*/
public static function GetVersion() {
return(C4Masterserver::$version);
}
/**
* Sets the seconds after which games timeout.
*
* @param int $timeoutgames
* @return void
*/
public function SetTimeoutgames($timeoutgames) {
$this->timeoutgames = $timeoutgames;
}
/**
* Sets the seconds after which games are removed.
*
* @param int $deletegames
* @return void
*/
public function SetDeletegames($deletegames) {
$this->deletegames = $deletegames;
}
/**
* Sets the maximum amount of games per ip.
*
* @param int $maxgames
* @return void
*/
public function SetMaxgames($maxgames) {
$this->maxgames = $maxgames;
}
/**
* Returns all valid references. If $show_all is true, it returns all references.
*
* @param bool $show_all
* @return array
*/
public function GetReferenceArray($show_all = false) {
if(!$this->link) return false;
$append = $show_all ? '' : 'WHERE `valid` = \'1\'';
$result = mysql_query('SELECT * FROM `'.$this->prefix.'games`'.$append);
$list = array();
while($row = mysql_fetch_assoc($result)) {
$list[$row['id']] = $row;
}
return $list;
}
/**
* Adds a new reference and returns the new CSID.
*
* @param string $reference
* @return string
*/
public function AddReference($reference) {
if(!$this->link) return false;
$reference = str_replace('0.0.0.0', $_SERVER['REMOTE_ADDR'], $reference);
$query = mysql_query('SELECT * FROM `'.$this->prefix.'games` WHERE `ip` = \''.$_SERVER['REMOTE_ADDR'].'\' AND `valid` = \'1\'', $this->link);
if(mysql_num_rows($query) > $this->maxgames) {
return false;
}
$csid = sha1(uniqid(mt_rand(), true));
mysql_query('INSERT INTO `'.$this->prefix.'games` (`id`, `ip`,`csid`, `data`, `start`, `time`, `valid`) VALUES (\'\', \''.$_SERVER['REMOTE_ADDR'].'\', \''.$csid.'\', \''.$reference.'\', \''.time().'\', \''.time().'\', \'1\')', $this->link);
return $csid;
}
/**
* Updates a reference.
*
* @param string $csid
* @param string $newreference
* @return bool
*/
public function UpdateReference($csid, $newreference) {
if(!$this->link) return false;
$newreference = str_replace('0.0.0.0', $_SERVER['REMOTE_ADDR'], $newreference);
mysql_query('UPDATE `'.$this->prefix.'games` SET `data`=\''.$newreference.'\',`time` = \''.time().'\', `valid` = \'1\' WHERE `csid` = \''.$csid.'\'', $this->link);
return true;
}
/**
* Removes a reference by setting it invalid.
*
* @param string $csid
* @return bool
*/
public function RemoveReference($csid) {
if(!$this->link) return false;
mysql_query('UPDATE `'.$this->prefix.'games` SET `time` = \''.time().'\', `valid` = \'0\' WHERE `csid` = \''.$csid.'\'', $this->link);
return true;
}
/**
* Sets old references invalid and removes very old ones if $remove is true.
*
* @param void
* @return void
*/
public function CleanUp() {
if(!$this->link) return false;
if($this->timeoutgames != 0) {
mysql_query('UPDATE `'.$this->prefix.'games` SET `valid` = \'0\' WHERE `time` < \''.(time() - $this->timeoutgames).'\'', $this->link);
}
if($this->deletegames != 0) {
mysql_query('DELETE FROM `'.$this->prefix.'games` WHERE `valid` = \'0\' AND `time` < \''.(time() - $this->deletegames).'\'', $this->link);
}
}
}
?>

View File

@ -0,0 +1,79 @@
<?php
/**
* Provides functionality to communicate with a
* Clonk 4 engine with ini-style strings.
*
* @package C4Masterserver
* @version 1.1.5-en
* @author Benedict Etzel <b.etzel@live.de>
* @license http://creativecommons.org/licenses/by/3.0/ CC-BY 3.0
*/
abstract class C4Network {
/**
* Creates a Clonk 4 conform answer string.
*
* @param array $data
* @return string
*/
public static function CreateAnswer($data) {
$message = '[Response]'."\n";
foreach ($data as $key => $value) {
$message .= $key.'='.$value."\n";
}
return $message;
}
/**
* Returns a Clonk 4 conform error string.
*
* @param string message
* @return string
*/
public static function CreateError($message) {
return C4Network::CreateAnswer(array("Status" => "Failure", "Message" => $message));
}
/**
* Sends a Clonk 4 conform answer string.
*
* @param string $message
* @return void
*/
public static function SendAnswer($message) {
header('Content-Length: '.strlen($message));
echo $message;
}
/**
* Cleans a Clonk 4 conform text sting human readable.
*
* @param string $message
* @return string
*/
public static function CleanString($message) {
$coded = $decoded = array();
preg_match_all('|\\\[0-9]{3}|', $message, $coded);
foreach($coded[0] as $numstr) {
$num = ereg_replace("[^0-9]", "", $numstr);
$decoded[$num] = C4Network::DecodeEntitiyString($num);
}
foreach($decoded as $num => $entity) {
$message = str_replace('\\'.$num, $entity, $message);
}
return $message;
}
/**
* Decodes Clonk 4 conform entitiy string to its character.
*
* @param string $string
* @return string
*/
public static function DecodeEntitiyString($string) {
$num = ereg_replace("[^0-9]", "", $string);
$num = octdec($num);
return iconv('Windows-1252', 'UTF-8', chr($num));
}
}
?>

View File

@ -0,0 +1,140 @@
<?php
/**
* Provides functionality to check and block
* flooding attempts by ip.
*
* @package C4Masterserver
* @version 1.1.5-en
* @author Benedict Etzel <b.etzel@live.de>
* @license http://creativecommons.org/licenses/by/3.0/ CC-BY 3.0
*/
class FloodProtection {
/**
* Stores the MySQL connection resource.
*
* @var resource
*/
private $link;
/**
* Stores the MySQL table prefix.
*
* @var string
*/
private $prefix;
/**
* Stores the maximum alloud requests per second per ip.
*
* @var int
*/
private $maxflood;
/**
* The FloodProtection constructor.
*
* @param resource $link
* @return FloodProtection
*/
public function __construct($link, $prefix) {
$this->link = $link;
$this->prefix = $prefix;
$this->maxflood = 5;
}
/**
* Sets the maximum alloud requests per second per ip.
*
* @param int $maxflood
* @return void
*/
public function SetMaxflood($maxflood) {
$this->maxflood = $maxflood;
}
/**
* Checks a request and returns true if the user is flooding.
*
* @param string $ip
* @return bool
*/
public function CheckRequest($ip) {
if(!$this->link) return false;
if($this->UserKnown($ip)) {
$this->UpdateUser($ip);
$this->CleanUp();
return $this->UserFlooding($ip);
}
$this->AddUser($ip);
$this->CleanUp();
return false;
}
/**
* Returns, if a user is already in the table.
*
* @param string $ip
* @return bool
*/
private function UserKnown($ip) {
if(!$this->link) return false;
$query = mysql_query('SELECT `time` FROM `'.$this->prefix.'flood` WHERE `ip` = \' '.$ip. '\' LIMIT 1', $this->link);
if(mysql_num_rows($query) > 0) {
return true;
}
return false;
}
/**
* Adds a new user to the table.
*
* @param string $ip
* @return bool
*/
private function AddUser($ip) {
if(!$this->link) return false;
$query = mysql_query('INSERT INTO `'.$this->prefix.'flood` (`ip`, `count`, `time`) VALUES (\' '.$ip. '\', \'0\',\''. time() .'\') ', $this->link);
if(!$query) {
return false;
}
return true;
}
/**
* Checks if the given user is flooding the server.
*
* @param string $ip
* @return bool
*/
private function UpdateUser($ip) {
if(!$this->link) return false;
mysql_query('UPDATE `'.$this->prefix.'flood` SET `count` = \'0\' WHERE `ip` = \' '.$ip.'\' AND `time` != \''.time().'\'', $this->link);
mysql_query('UPDATE `'.$this->prefix.'flood` SET `count` = `count`+\'1\', `time` = \''.time().'\' WHERE `ip` = \' '. mysql_real_escape_string($ip, $this->link).'\'', $this->link);
}
/**
* Checks if the given user is flooding the server.
*
* @param string $ip
* @return bool
*/
private function UserFlooding($ip) {
if(!$this->link) return false;
$query = mysql_query('SELECT `time` FROM `'.$this->prefix.'flood` WHERE `ip` = \' '.$ip.'\' AND `count` >= \''.$this->maxflood.'\' LIMIT 1', $this->link);
if(mysql_num_rows($query) > 0) {
return true;
}
return false;
}
/**
* Removes old entrys.
*
* @return void
*/
private function CleanUp() {
mysql_query('DELETE FROM `'.$this->prefix.'flood` WHERE `time` <= \'' . (time()- 600) . '\'', $this->link);
}
}
?>

View File

@ -0,0 +1,75 @@
<?php
/**
* Provides functionality to parse key-value-pairs
* from an ini-like string.
*
* @package C4Masterserver
* @version 1.1.5-en
* @author Benedict Etzel <b.etzel@live.de>
* @license http://creativecommons.org/licenses/by/3.0/ CC-BY 3.0
*/
abstract class ParseINI {
/**
* Returns the value belonging to the key from an ini-like string.
*
* @param string $key
* @param string $string
* @return string
*/
public static function ParseValue($key, $string) {
if(!$key || !$string) {
return false;
}
if(strpos($string, $key) === false) {
return false;
}
$value = '';
$key_start = strpos($string, $key."=") + strlen($key) + 1;
$key_end = strpos($string, "\n", $key_start);
if($key_end === false) {
$value = substr($string, $key_start);
}
else {
$value = substr($string, $key_start, $key_end - $key_start);
}
$value = trim($value);
return trim($value, '"');
}
/**
* Returns all values in a certain category and supports multiple appereances.
*
* @param string $key
* @param string $category
* @param string $string
* @return array
*/
public static function ParseValuesByCategory($key, $category, $string) {
if(!$key || !$string || !$category) {
return false;
}
if(strpos($string, $key) === false || strpos($string, '['.$category.']') === false) {
return false;
}
$values = array();
$lines = explode("\n", $string);
$current_category = '';
foreach($lines as $line) {
$line = trim($line);
if(strpos($line, '[') !== false && strpos($line, ']') !== false && strpos($line, '=') === false) {
$start = strpos($line, '[') + 1;
$end = strpos($line, ']') - 1;
$current_category = substr($line, $start, $end);
}
if($current_category != $category) continue; //wrong category
if(strpos($line, ';') === 0) continue; //comment
if(strpos($line, $key) === false) continue; //not needed
$value = ParseINI::ParseValue($key, $line);
$value = trim($value);
$values[] = trim($value, '"');
}
return $values;
}
}
?>

View File

@ -0,0 +1,30 @@
[C4Masterserver]
;your MySQL host, usually localhost
mysql_host=localhost
;your MySQL user
mysql_user=root
;your MySQL password
mysql_password=
;your MySQL database
mysql_db=c4ms
;your MySQL prefix, default c4ms_
mysql_prefix=c4ms_
;accepted engine strings, leave empty to allow all (Example: OpenClonk)
c4ms_engine=
;seconds after timing out old games, default 60*10 = 600
c4ms_timeoutgames=600
;seconds after deleting old games, default 60*60*24 = 86400
c4ms_deletegames=86400
;maximum alloud games per ip, default 5
c4ms_maxgames=5
;maximum alloud requests per second, default 5
flood_maxrequests=5

View File

@ -0,0 +1,107 @@
<?php
/**
* C4Masterserver engine backend
*
* @package C4Masterserver
* @version 1.1.5-en
* @author Benedict Etzel <b.etzel@live.de>
* @license http://creativecommons.org/licenses/by/3.0/ CC-BY 3.0
*/
//error_reporting(E_NONE); //suppress errors
require_once('include/C4Masterserver.php');
require_once('include/C4Network.php');
require_once('include/FloodProtection.php');
require_once('include/ParseINI.php');
$config = file_get_contents('include/config.ini');
$link = mysql_connect(
ParseINI::ParseValue('mysql_host', $config),
ParseINI::ParseValue('mysql_user', $config),
ParseINI::ParseValue('mysql_password', $config)); //connect to MySQL
$db = mysql_selectdb(ParseINI::ParseValue('mysql_db', $config), $link); //select the database
header('Content-Type: text/plain'); //output as plain text
if($link && $db) {
$server = new C4Masterserver($link, ParseINI::ParseValue('mysql_prefix', $config));
$server->SetTimeoutgames(intval(ParseINI::ParseValue('c4ms_timeoutgames', $config)));
$server->SetDeletegames(intval(ParseINI::ParseValue('c4ms_deletegames', $config)));
$server->SetMaxgames(intval(ParseINI::ParseValue('c4ms_maxgames', $config)));
$protect = new FloodProtection($link, ParseINI::ParseValue('mysql_prefix', $config));
$protect->SetMaxflood(intval(ParseINI::ParseValue('flood_maxrequests', $config)));
if($protect->CheckRequest($_SERVER['REMOTE_ADDR'])) { //flood protection
C4Network::SendAnswer(C4Network::CreateError('Flood protection.'));
die();
}
$server->CleanUp(true); //Cleanup old stuff
if(isset($GLOBALS['HTTP_RAW_POST_DATA'])) { //data sent from engine?
$input = $GLOBALS['HTTP_RAW_POST_DATA'];
$action = ParseINI::ParseValue('Action', $input);
$csid = ParseINI::ParseValue('CSID', $input);
$csid = mysql_real_escape_string($csid, $link);
$reference = mysql_real_escape_string(strstr($input, '[Reference]'), $link);
$engine_string = ParseINI::ParseValue('c4ms_engine', $config);
if(empty($engine_string) || ParseINI::ParseValue('Game', $input) == $engine_string) {
switch($action) {
case 'Start': //start a new round
if(ParseINI::ParseValue('LeagueAddress', $reference)) {
C4Network::SendAnswer(C4Network::CreateError('League not supported!'));
}
else {
$csid = $server->AddReference($reference);
if($csid) {
C4Network::SendAnswer(C4Network::CreateAnswer(array('Status' => 'Success', 'CSID' => $csid)));
}
else {
C4Network::SendAnswer(C4Network::CreateError('Round signup failed. (To many tries?)'));
}
}
break;
case 'Update': //update an existing round
if($server->UpdateReference($csid, $reference)) {
C4Network::SendAnswer(C4Network::CreateAnswer(array('Status' => 'Success')));
}
else {
C4Network::SendAnswer(C4Network::CreateError('Round update failed.'));
}
break;
case 'End': //remove a round
if($server->RemoveReference($csid)) {
C4Network::SendAnswer(C4Network::CreateAnswer(array('Status' => 'Success')));
}
else {
C4Network::SendAnswer(C4Network::CreateError('Round end failed.'));
}
break;
default:
if (!empty($action)) {
C4Network::SendAnswer(C4Network::CreateError('Unknown action.'));
}
else {
C4Network::SendAnswer(C4Network::CreateError('No action defined.'));
}
break;
}
}
else {
C4Network::SendAnswer(C4Network::CreateError('Wrong engine.'));
}
}
else { //list availabe games
$list = $server->GetReferenceArray(false);
$message = '';
foreach($list as $reference) {
if(!empty($message)) $message .= "\n\n";
$message .= $reference['data'];
$message .= 'GameId='.$reference['id']."\n";
}
C4Network::SendAnswer($message);
}
mysql_close($link);
}
else {
C4Network::SendAnswer(C4Network::CreateError('Database error.'));
}
?>