aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer
diff options
context:
space:
mode:
authorDarkelarious <darkelarious@333networks.com>2016-11-19 20:56:04 +0100
committerDarkelarious <darkelarious@333networks.com>2016-11-19 20:56:04 +0100
commitc3f8d65a4fb1f5674557ee67cf7f74369df86ad1 (patch)
tree92aab2a394bda28da0ed7c7c75e633fdf386fc71 /lib/MasterServer
parent1de3da4b8027508a91144639455c934fd6ccb9b7 (diff)
downloadMasterServer-Perl-c3f8d65a4fb1f5674557ee67cf7f74369df86ad1.tar.gz
MasterServer-Perl-c3f8d65a4fb1f5674557ee67cf7f74369df86ad1.zip
Massive improvements on efficiency, robustness, security, reliability and more
Diffstat (limited to 'lib/MasterServer')
-rwxr-xr-xlib/MasterServer/Core/Core.pm99
-rwxr-xr-xlib/MasterServer/Core/Logging.pm5
-rwxr-xr-xlib/MasterServer/Core/Schedulers.pm125
-rwxr-xr-xlib/MasterServer/Core/Secure.pm128
-rwxr-xr-xlib/MasterServer/Core/Stats.pm39
-rwxr-xr-xlib/MasterServer/Core/Util.pm89
-rwxr-xr-xlib/MasterServer/Core/Version.pm11
-rwxr-xr-xlib/MasterServer/Database/Pg/dbBeacon.pm203
-rwxr-xr-xlib/MasterServer/Database/Pg/dbCiphers.pm98
-rwxr-xr-xlib/MasterServer/Database/Pg/dbClientList.pm45
-rwxr-xr-xlib/MasterServer/Database/Pg/dbCore.pm44
-rwxr-xr-xlib/MasterServer/Database/Pg/dbServerlist.pm152
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbBeacon.pm203
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbCiphers.pm98
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbClientList.pm45
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbCore.pm66
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbServerlist.pm152
-rwxr-xr-xlib/MasterServer/Database/dbAddServers.pm162
-rwxr-xr-xlib/MasterServer/Database/dbCiphers.pm57
-rwxr-xr-xlib/MasterServer/Database/dbCore.pm83
-rwxr-xr-xlib/MasterServer/Database/dbGetServers.pm139
-rwxr-xr-xlib/MasterServer/Database/dbMaintenance.pm40
-rwxr-xr-xlib/MasterServer/Database/dbStats.pm117
-rwxr-xr-xlib/MasterServer/Database/dbUTServerInfo.pm238
-rwxr-xr-xlib/MasterServer/Database/mysql/dbBeacon.pm203
-rwxr-xr-xlib/MasterServer/Database/mysql/dbCiphers.pm98
-rwxr-xr-xlib/MasterServer/Database/mysql/dbClientList.pm45
-rwxr-xr-xlib/MasterServer/Database/mysql/dbCore.pm44
-rwxr-xr-xlib/MasterServer/Database/mysql/dbServerlist.pm153
-rwxr-xr-xlib/MasterServer/TCP/BrowserHost.pm5
-rwxr-xr-xlib/MasterServer/TCP/Handler.pm82
-rwxr-xr-xlib/MasterServer/TCP/ListCompiler.pm34
-rwxr-xr-xlib/MasterServer/TCP/Syncer.pm100
-rwxr-xr-xlib/MasterServer/TCP/UCCAppletQuery.pm (renamed from lib/MasterServer/UDP/UCCAppletQuery.pm)52
-rwxr-xr-xlib/MasterServer/UDP/BeaconCatcher.pm15
-rwxr-xr-xlib/MasterServer/UDP/BeaconChecker.pm147
-rwxr-xr-xlib/MasterServer/UDP/DatagramProcessor.pm245
-rwxr-xr-xlib/MasterServer/UDP/UDPTicker.pm298
-rwxr-xr-xlib/MasterServer/UDP/UpLink.pm169
-rwxr-xr-xlib/MasterServer/Util/KFStatsWatcher.pm59
40 files changed, 2106 insertions, 2081 deletions
diff --git a/lib/MasterServer/Core/Core.pm b/lib/MasterServer/Core/Core.pm
index a20172c..3f99a3a 100755
--- a/lib/MasterServer/Core/Core.pm
+++ b/lib/MasterServer/Core/Core.pm
@@ -7,7 +7,7 @@ use AnyEvent;
use Exporter 'import';
use DBI;
-our @EXPORT = qw | halt main |;
+our @EXPORT = qw | halt select_database_type main |;
################################################################################
## Handle shutting down the program in case a fatal error occurs.
@@ -34,6 +34,19 @@ sub halt {
}
################################################################################
+## Set up the database connection
+################################################################################
+sub select_database_type {
+ my $self = shift;
+
+ # Connect to database
+ $self->{dbh} = $self->database_login();
+
+ # and test whether we succeeded.
+ $self->halt() unless (defined $self->{dbh});
+}
+
+################################################################################
## Initialize all processes and start various functions
################################################################################
sub main {
@@ -42,67 +55,69 @@ sub main {
# condition var prevents or allows the program from ending
$self->{must_halt} = AnyEvent->condvar;
- # determine version info
+ # force version info
$self->version();
+ # print startup
+ print "Running...\n";
+
# keep several objects alive outside their original scope
$self->{scope} = ();
# startup procedure information
+ $self->log("info", "");
+ $self->log("info", "");
$self->log("info", "333networks Master Server Application.");
- $self->log("info", "Build: $self->{build_type}");
- $self->log("info", "Version: $self->{build_version}");
- $self->log("info", "Written by $self->{build_author}");
- $self->log("info", "Logs are written to $self->{log_dir}");
+ $self->log("info", "Hostname: $self->{masterserver_hostname}");
+ $self->log("info", "Build: $self->{build_type}");
+ $self->log("info", "Version: $self->{build_version}");
+ $self->log("info", "Author: $self->{build_author}");
+ $self->log("info", "Logs: $self->{log_dir}");
# determine the type of database and load the appropriate module
- {
- # read from login
- my @db_type = split(':', $self->{dblogin}->[0]);
-
- # format supported?
- if ( "Pg SQLite mysql" =~ m/$db_type[1]/i) {
-
- # inform us what DB we try to load
- $self->log("load","Loading $db_type[1] database module.");
-
- # load dbd and tables/queries for this db type
- MasterServer::load_recursive("MasterServer::Database::$db_type[1]");
-
- # Connect to database
- $self->{dbh} = $self->database_login();
-
- # and test whether we succeeded.
- $self->halt() unless (defined $self->{dbh});
- }
- else {
- # raise error and halt
- $self->log("fatal", "The masterserver could not determine the chosen database type.");
- $self->halt();
- }
- }
+ $self->select_database_type();
+
+ #
+ # Prepare necessary tasks for running the masterserver
+ #
# (re)load the list with ciphers from the config file, into the database
$self->load_ciphers();
+ # set first run flag to avoid ignoring servers after downtime
+ $self->{firstrun} = undef;
+ $self->{firstruntime} = time;
+
+ ###
+ #
+ # activate all schedulers and functions
+ #
+ ###
+
+ #
+ # Timers
+ #
+ # tasks that are executed once or twice per hour
+ $self->{scope}->{long_periodic_tasks} = $self->long_periodic_tasks();
+ #
+ # tasks that are executed every few minutes
+ $self->{scope}->{short_periodic_tasks} = $self->short_periodic_tasks();
+ #
+ # tasks that are executed every few milliseconds
+ $self->{scope}->{udp_ticker} = $self->udp_ticker();
+
+ #
+ # Network listeners
+ #
# start the listening service (listen for UDP beacons)
$self->{scope}->{beacon_catcher} = $self->beacon_catcher();
-
- # start the beacon checker service (query entries from the pending list)
- $self->{scope}->{beacon_checker} = $self->beacon_checker() if ($self->{beacon_checker_enabled});
-
+ #
# provide server lists to clients with the browser host server
$self->{scope}->{browser_host} = $self->browser_host();
- # query other masterserver applets to get more server addresses
- $self->{scope}->{ucc_applet_query} = $self->ucc_applet_query_scheduler() if ($self->{master_applet_enabled});
-
- # synchronize with 333networks-based masterservers
- $self->{scope}->{syncer_scheduler} = $self->syncer_scheduler() if ($self->{sync_enabled});
-
# all modules loaded. Running...
$self->log("info", "All modules loaded. Masterserver is now running.");
-
+
# prevent main program from ending prematurely
$self->{must_halt}->recv;
}
diff --git a/lib/MasterServer/Core/Logging.pm b/lib/MasterServer/Core/Logging.pm
index 800fa43..5aaf683 100755
--- a/lib/MasterServer/Core/Logging.pm
+++ b/lib/MasterServer/Core/Logging.pm
@@ -15,6 +15,9 @@ our @EXPORT = qw| log |;
sub log {
my ($self, $type, $msg) = @_;
+ # flush
+ $| = 1;
+
# parse time of log entry and prep for rotating log
my $time = strftime('%Y-%m-%d %H:%M:%S',localtime);
my $yearly = strftime('-%Y',localtime);
@@ -26,7 +29,7 @@ sub log {
return if (defined $type && $self->{suppress} =~ m/$type/i);
# determine filename
- my $f = "MasterServer-Perl";
+ my $f = "MasterServer";
# rotate log filename according to config
$f .= $daily if ($self->{log_rotate} =~ /^daily$/i );
diff --git a/lib/MasterServer/Core/Schedulers.pm b/lib/MasterServer/Core/Schedulers.pm
new file mode 100755
index 0000000..97e45a5
--- /dev/null
+++ b/lib/MasterServer/Core/Schedulers.pm
@@ -0,0 +1,125 @@
+
+package MasterServer::Core::Schedulers;
+
+use strict;
+use warnings;
+use AnyEvent;
+use POSIX qw/strftime/;
+use Exporter 'import';
+use DBI;
+
+our @EXPORT = qw |
+ long_periodic_tasks
+ short_periodic_tasks
+|;
+
+################################################################################
+## tasks that are executed only once or twice per hour
+################################################################################
+sub long_periodic_tasks {
+ my $self = shift;
+ my $num = 0;
+
+ return AnyEvent->timer (
+ after => 300, # 5 minutes grace time
+ interval => 1800, # execute every half hour
+ cb => sub {
+
+ ## update Killing Floor stats
+ $self->read_kfstats();
+
+ # time spacer
+ my $t = 0;
+
+ # clean out handles from the previous round (executed or not)
+ $self->{scope}->{sync} = ();
+
+ ## Query Epic Games'-based UCC applets periodically to get an additional
+ ## list of online UT, Unreal (or other) game servers.
+ if ($self->{master_applet_enabled}) {
+ for my $ms (@{$self->{master_applet}}) {
+
+ # add 3 second delay to spread network/server load
+ $self->{scope}->{sync}->{$t} = AnyEvent->timer(
+ after => 3*$t++,
+ cb => sub{$self->query_applet($ms)}
+ );
+ }
+ }
+
+ # do NOT reset $t, keep padding time -- you should not have more than 600
+ # entries in applets/syncer in total.
+
+ ## Request the masterlist for selected or all games from other
+ ## 333networks-based masterservers that uplinked to us and otherwise made
+ ## our list (config, manual entry, etc)
+ if ($self->{sync_enabled}) {
+ foreach my $ms (values %{$self->masterserver_list()}) {
+
+ # add 3 second delay to spread network/server load
+ $self->{scope}->{sync}->{$t} = AnyEvent->timer(
+ after => 3*$t++,
+ cb => sub{$self->sync_with_master($ms) if ($ms->{tcp} > 0)}
+ );
+ }
+ }
+
+ #
+ # Also very long-running tasks, like once per day:
+ #
+ if ($num++ >= 47) {
+ # reset counter
+ $num = 0;
+
+ #
+ # do database dump
+ #
+ my $time = strftime('%Y-%m-%d-%H-%M',localtime);
+
+ # read db type from db login
+ my @db_type = split(':', $self->{dblogin}->[0]);
+ $db_type[2] =~ s/dbname=//;
+
+ if ($db_type[1] eq "Pg") {
+ # use pg_dump to dump Postgresql databases
+ system("pg_dump $db_type[2] -U $self->{dblogin}->[1] > $self->{root}/data/dumps/$db_type[1]-$time.db");
+ $self->log("dump", "Dumping database to /data/dumps/$db_type[1]-$time.db");
+ }
+ }
+
+ },
+ );
+}
+
+################################################################################
+## tasks that are executed every few minutes
+################################################################################
+sub short_periodic_tasks {
+ my $self = shift;
+
+ return AnyEvent->timer (
+ after => 10,
+ interval => 120,
+ cb => sub {
+
+ ## update stats on direct beacons and total number of servers
+ $self->update_stats();
+
+ ## determine whether servers are still uplinking to us. If not, toggle.
+ $self->write_direct_beacons() if (defined $self->{firstrun});
+
+ ## delete old servers from the "pending" list (except for the first run)
+ $self->delete_old_pending() if (defined $self->{firstrun});
+
+ ## uplink to other 333networks masterservers with heartbeats,
+ ## that way we can index other masterservers too
+ $self->send_heartbeats();
+
+ #
+ # more short tasks?
+ #
+ },
+ );
+}
+
+1;
diff --git a/lib/MasterServer/Core/Secure.pm b/lib/MasterServer/Core/Secure.pm
index 07e48b8..51d1832 100755
--- a/lib/MasterServer/Core/Secure.pm
+++ b/lib/MasterServer/Core/Secure.pm
@@ -8,11 +8,8 @@ use Exporter 'import';
our @EXPORT = qw| load_ciphers
secure_string
- validated_beacon
- validated_request
- validate_string
- charshift
- get_validate_string |;
+ validate_string
+ compare_challenge |;
################################################################################
## Supported Games list ciphers
@@ -21,7 +18,6 @@ our @EXPORT = qw| load_ciphers
## data/supportedgames.pl file.
##
## Only config files after 5 October 2015 work with this script.
-## IMPORTANT! Afterwards, the $self->{game} hash will be cleared!
################################################################################
sub load_ciphers {
my $self = shift;
@@ -47,14 +43,13 @@ sub load_ciphers {
$self->{dbh}->rollback;
$self->halt();
}
-
}
# commit
$self->{dbh}->commit;
$self->log("info", "Cipher database successfully updated!");
- # unload the game variables from memory
+ # unload the game variables from masterserver memory
$self->{game} = undef;
}
@@ -78,82 +73,54 @@ sub secure_string {
# authenticate the \validate\ response for the \secure\ challenge.
# returns 1 on valid response, 0 on invalid
################################################################################
-sub validated_beacon {
- my ($self, $gamename, $secure, $enctype, $validate) = @_;
+sub compare_challenge {
+ my ($self, %o) = @_;
# debugging enabled? Then don't care about validation
return 1 if ($self->{debug_validate});
- # enctype given?
- $enctype = 0 unless $enctype;
+ # secure string too long? (because vulnerable in UE)
+ return 0 if (length $o{secure} > 16);
- if ($self->{ignore_beacon_key} =~ m/$gamename/i){
- $self->log("secure", "ignored beacon validation for $gamename");
- return 1;
- }
+ # additional conditions to skip checking provided?
+ $o{ignore} = "" unless $o{ignore};
- # compare received response with expected response
- return ($self->validate_string($gamename, $secure, $enctype) eq $validate) || 0;
-}
+ # ignore this game if asked to do so
+ if ($o{ignore} =~ m/$o{gamename}/i){
+ $self->log("secure", "ignored beacon validation for $o{gamename}");
+ return 1;
+ }
-################################################################################
-# authenticate the \validate\ response for the \secure\ challenge.
-# returns 1 on valid response, 0 on invalid
-################################################################################
-sub validated_request {
- my ($self, $gamename, $secure, $enctype, $validate) = @_;
-
- # debugging enabled? Then don't care about validation
- return 1 if ($self->{debug_validate});
+ # enctype given?
+ $o{enctype} = 0 unless $o{enctype};
- # enctype given?
- $enctype = 0 unless $enctype;
+ # get cipher corresponding with the gamename
+ my $cip = $self->get_game_props($o{gamename})->{cipher};
- # ignore games and beacons that are listed
- if ($self->{ignore_browser_key} =~ m/$gamename/i){
- $self->log("secure", "ignored browser validation for $gamename");
- return 1;
- }
-
- # compare received response with expected response
- return ($self->validate_string($gamename, $secure, $enctype) eq $validate) || 0;
+ # calculate validate string
+ my $val = get_validate_string($cip, $o{secure}, $o{enctype});
+
+ # return whether or not they match
+ return ($val eq $o{validate});
}
################################################################################
-# process the validate string as a response to the secure challenge
-# returns: validate string (usually 8 characters long)
+# obtain the secure/validate challenge string
################################################################################
sub validate_string {
- my ($self, $game, $sec, $enc) = @_;
+ my ($self, %o) = @_;
- # get cipher from gamename
- my $cip = $self->get_cipher($game);
+ # secure string too long? (because vulnerable in UE)
+ return 0 if (length $o{secure} > 16);
- # don't accept challenge longer than 16 characters (because vulnerable in UE)
- if (length $sec > 16) {
- return "invalid!"}
+ # get cipher from gamename
+ my $cip = $self->get_game_props(lc $o{gamename})->{cipher};
- # check for valid encryption choices
- my $enc_val = (defined $enc && 0 <= $enc && $enc <= 2) ? $enc : 0;
+ # enctype given?
+ $o{enctype} = 0 unless $o{enctype};
# calculate and return validate string
- return $self->get_validate_string($cip, $sec, $enc_val);
-}
-
-################################################################################
-# rotate characters as part of the secure/validate algorithm.
-# arg and return: int (representing a character)
-################################################################################
-sub charshift {
- my ($self, $reg) = @_;
- return($reg + 65) if ($reg < 26);
- return($reg + 71) if ($reg < 52);
- return($reg - 4) if ($reg < 62);
- return(43) if ($reg == 62);
- return(47) if ($reg == 63);
-
- # if all else fails
- return(0);
+ return get_validate_string($cip, $o{secure}, $o{enctype});
}
################################################################################
@@ -166,14 +133,15 @@ sub charshift {
# was released under the GNU General Public License, for more information, see
# the original software at http://aluigi.altervista.org/papers.htm#gsmsalg
#
-# conversion and modification of the algorithm by Darkelarious, June 2014.
+# conversion and modification of the algorithm by Darkelarious, June 2014 with
+# explicit, written permission of Luigi Auriemma.
#
# args: game cipher, 6-char challenge string, encryption type
# returns: validate string (usually 8 characters long)
# !! requires cipher hash to be configured in config! (imported or otherwise)
################################################################################
sub get_validate_string {
- my ($self, $cipher_string, $secure_string, $enctype) = @_;
+ my ($cipher_string, $secure_string, $enctype) = @_;
# use pre-built rotations for enctype
# -- see GSMSALG 0.3.3 reference for copyright and more information
@@ -245,7 +213,7 @@ sub get_validate_string {
$tmp[$i] = $sec[$i] ^ $enc[($l + $m) & 0xff];
}
-# part of the enctype 1-2 process
+ # part of the enctype 1-2 process
for($sec_len = $i; $sec_len % 3; $sec_len++) {
$tmp[$sec_len] = 0;
}
@@ -270,10 +238,10 @@ sub get_validate_string {
$m = $m % 256;
$n = $tmp[$i + 2];
$n = $n % 256;
- $val[$p++] = $self->charshift($l >> 2);
- $val[$p++] = $self->charshift((($l & 3 ) << 4) | ($m >> 4));
- $val[$p++] = $self->charshift((($m & 15) << 2) | ($n >> 6));
- $val[$p++] = $self->charshift($n & 63);
+ $val[$p++] = charshift($l >> 2);
+ $val[$p++] = charshift((($l & 3 ) << 4) | ($m >> 4));
+ $val[$p++] = charshift((($m & 15) << 2) | ($n >> 6));
+ $val[$p++] = charshift($n & 63);
}
# return to ascii characters
@@ -283,4 +251,20 @@ sub get_validate_string {
return $str;
}
+################################################################################
+# rotate characters as part of the secure/validate algorithm.
+# arg and return: int (representing a character)
+################################################################################
+sub charshift {
+ my $reg = shift;
+ return($reg + 65) if ($reg < 26);
+ return($reg + 71) if ($reg < 52);
+ return($reg - 4) if ($reg < 62);
+ return(43) if ($reg == 62);
+ return(47) if ($reg == 63);
+
+ # if all else fails
+ return(0);
+}
+
1;
diff --git a/lib/MasterServer/Core/Stats.pm b/lib/MasterServer/Core/Stats.pm
new file mode 100755
index 0000000..25044e8
--- /dev/null
+++ b/lib/MasterServer/Core/Stats.pm
@@ -0,0 +1,39 @@
+
+package MasterServer::Core::Stats;
+
+use strict;
+use warnings;
+use AnyEvent::IO;
+use Exporter 'import';
+
+our @EXPORT = qw| update_stats |;
+
+################################################################################
+# Update statistics on servers and update the games table with those values
+################################################################################
+sub update_stats {
+ my $self = shift;
+
+ # get all gamenames where there is one or more servers online and update the
+ # stats per gamename.
+ my $games = $self->get_gamelist_stats();
+
+ # iterate through available stats
+ for my $e (@{$games}) {
+
+ # extract gamename, number of direct uplinks and total servers
+ my %opt = ();
+ $opt{gamename} = $e->[0];
+ $opt{num_uplink} = $e->[1];
+ $opt{num_total} = $e->[2];
+
+ # write to DB
+ $self->write_stat(%opt);
+ }
+
+ #done
+ $self->log("stat", "Updated all game statistics.");
+
+}
+
+1;
diff --git a/lib/MasterServer/Core/Util.pm b/lib/MasterServer/Core/Util.pm
index af3a551..4f64fe1 100755
--- a/lib/MasterServer/Core/Util.pm
+++ b/lib/MasterServer/Core/Util.pm
@@ -4,10 +4,11 @@ package MasterServer::Core::Util;
use strict;
use warnings;
use IP::Country::Fast;
+use Socket;
use POSIX qw/strftime/;
use Exporter 'import';
-our @EXPORT = qw| ip2country countryflag valid_address |;
+our @EXPORT = qw| ip2country host2ip valid_address db_all sqlprint |;
################################################################################
## return the abbreviated country name based on IP
@@ -19,23 +20,24 @@ sub ip2country {
}
################################################################################
-## return the flag of a country
+## return IP of a hostname
################################################################################
-sub countryflag {
- my ($self, $country) = @_;
- # placeholder function to return the flag of a country
+sub host2ip {
+ my ($self, $name) = @_;
+ return inet_ntoa(inet_aton($name)) if $name;
}
+
################################################################################
## Verify whether a given domain name or IP address and port are valid.
-## returns 1/0 if valid/invalid ip + port
+## returns 1/0 if valid/invalid ip + port. IPv4 ONLY!
################################################################################
sub valid_address {
my ($self, $a, $p) = @_;
-
+
# check if ip and port are in valid range
- my $val_addr = ($a =~ '\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b');
- my $val_port = (0 < $p && $p <= 65535);
+ my $val_addr = ($a =~ '\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b') if $a;
+ my $val_port = (0 < $p && $p <= 65535) if $p;
# exclude addresses where we don't want people sniffing
for (qw|192.168.(.\d*).(.\d*) 127.0.(.\d*).(.\d*) 10.0.(.\d*).(.\d*)|){$val_addr = 0 if ($a =~ m/$_/)}
@@ -44,4 +46,73 @@ sub valid_address {
return ($val_addr && $val_port);
}
+################################################################################
+# Adaptation of TUWF's dbAll sql function
+################################################################################
+sub db_all {
+ my $self = shift;
+ my $sqlq = shift;
+ my $s = $self->{dbh};
+
+ $sqlq =~ s/\r?\n/ /g;
+ $sqlq =~ s/ +/ /g;
+ my(@q) = @_ ? sqlprint($sqlq, @_) : ($sqlq);
+
+ my($q, $r);
+ my $ret = eval {
+ $q = $s->prepare($q[0]);
+ $q->execute($#q ? @q[1..$#q] : ());
+ $r = $q->fetchall_arrayref({});
+ $q->finish();
+ 1;
+ };
+
+ $r = [] if (!$r || ref($r) ne 'ARRAY');
+ return $r;
+}
+
+################################################################################
+# sqlprint (TUWF):
+# ? normal placeholder
+# !l list of placeholders, expects arrayref
+# !H list of SET-items, expects hashref or arrayref: format => (bind_value || \@bind_values)
+# !W same as !H, but for WHERE clauses (AND'ed together)
+# !s the classic sprintf %s, use with care
+# This isn't sprintf, so all other things won't work,
+# Only the ? placeholder is supported, so no dollar sign numbers or named placeholders
+################################################################################
+sub sqlprint { # query, bind values. Returns new query + bind values
+
+ my @a;
+ my $q='';
+ for my $p (split /(\?|![lHWs])/, shift) {
+ next if !defined $p;
+ if($p eq '?') {
+ push @a, shift;
+ $q .= $p;
+ } elsif($p eq '!s') {
+ $q .= shift;
+ } elsif($p eq '!l') {
+ my $l = shift;
+ $q .= join ', ', map '?', 0..$#$l;
+ push @a, @$l;
+ } elsif($p eq '!H' || $p eq '!W') {
+ my $h=shift;
+ my @h=ref $h eq 'HASH' ? %$h : @$h;
+ my @r;
+ while(my($k,$v) = (shift(@h), shift(@h))) {
+ last if !defined $k;
+ my($n,@l) = sqlprint($k, ref $v eq 'ARRAY' ? @$v : $v);
+ push @r, $n;
+ push @a, @l;
+ }
+ $q .= ($p eq '!W' ? 'WHERE ' : 'SET ').join $p eq '!W' ? ' AND ' : ', ', @r
+ if @r;
+ } else {
+ $q .= $p;
+ }
+ }
+ return($q, @a);
+}
+
1;
diff --git a/lib/MasterServer/Core/Version.pm b/lib/MasterServer/Core/Version.pm
index ccf9491..bf12f21 100755
--- a/lib/MasterServer/Core/Version.pm
+++ b/lib/MasterServer/Core/Version.pm
@@ -23,17 +23,20 @@ sub version {
# these fields does not count as a significant alteration.
#
# -- addition to the LICENCE, you are only allowed to modify these lines
- # if you send Darkelarious a postcard or email with your compliments.
+ # if you send Darkelarious a postcard or (e)mail with your compliments.
#
# master type
- $self->{build_type} = "333networks Masterserver-Perl (Pg-SQLite-MySQL) 20151108209";
+ $self->{build_type} = "333networks Masterserver-Perl pre-release";
# version
- $self->{build_version} = "2.0.9";
+ $self->{build_version} = "2.2.4";
+
+ # short version for uplinks
+ $self->{short_version} = "MS-perl $self->{build_version}";
# date yyyy-mm-dd
- $self->{build_date} = "2015-11-08";
+ $self->{build_date} = "2016-11-17";
#author, email
$self->{build_author} = "Darkelarious, darkelarious\@333networks.com";
diff --git a/lib/MasterServer/Database/Pg/dbBeacon.pm b/lib/MasterServer/Database/Pg/dbBeacon.pm
deleted file mode 100755
index 735eaf9..0000000
--- a/lib/MasterServer/Database/Pg/dbBeacon.pm
+++ /dev/null
@@ -1,203 +0,0 @@
-
-package MasterServer::Database::Pg::dbBeacon;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| add_beacon
- add_pending
- remove_pending
- get_pending_beacon
- get_pending_info
- get_next_pending |;
-
-################################################################################
-## Update beacon in serverlist or pending list. Add if beacon does not exist in
-## either list. Return 0,1,2 if success in adding or -1 on error.
-################################################################################
-sub add_beacon {
- my ($self, $ip, $beaconport, $heartbeat, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "UPDATE serverlist
- SET beacon = NOW(),
- updated = NOW(),
- gamename = ?,
- b333ms = TRUE
- WHERE ip = ?
- AND port = ?",
- undef, lc $gamename, $ip, $heartbeat);
-
- # notify
- $self->log("update", "beacon heartbeat for $ip:$heartbeat") if ($u > 0);
-
- # if serverlist was updated return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET added = NOW(),
- beaconport = ?,
- gamename = ?,
- secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $beaconport, lc $gamename, $secure, $ip, $heartbeat);
-
- # notify
- $self->log("update", "beacon heartbeat $ip:$beaconport pending $gamename:$heartbeat") if ($u > 0);
-
- # beacon was already in pending list and was updated
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (
- ip,
- beaconport,
- heartbeat,
- gamename,
- secure)
- SELECT ?, ?, ?, ?, ?",
- undef, $ip, $beaconport, $heartbeat, lc $gamename, $secure);
-
- # notify
- $self->log("add", "beacon heartbeat $ip:$beaconport pending $gamename:$heartbeat") if ($u > 0);
-
- # it was added to pending
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding beacon $ip:$beaconport with $gamename:$heartbeat to the pending list");
- return -1;
-}
-
-################################################################################
-## Add an address to the database that was obtained via a method other than
-## an udp beacon. Return 0,1,2 if success in adding or -1 on error.
-################################################################################
-sub add_pending {
- my ($self, $ip, $port, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "UPDATE serverlist
- SET updated = NOW()
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "updated serverlist with $ip:$port") if ($u > 0);
-
- # if updated, return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET added = NOW(),
- secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $secure, $ip, $port);
-
- # notify
- $self->log("update", "updated pending with $ip:$port") if ($u > 0);
-
- # return 1 if updated
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (
- ip,
- heartbeat,
- gamename,
- secure)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $secure);
-
- # notify
- $self->log("add", "$ip:$port added pending $gamename") if ($u > 0);
-
- # return 2 if added new
- return 2 if ($u > 0);
-
- # else
- return -1;
-}
-
-################################################################################
-## Remove an entry from the pending list. Returns 0 if removed or -1 in case
-## of error(s).
-################################################################################
-sub remove_pending {
- my ($self, $id) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "DELETE FROM pending
- WHERE id = ?",
- undef, $id);
-
- # notify
- $self->log("delete", "removed pending id $id from pending") if ($u > 0);
-
- # it was removed from pending
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "error deleting server $id from pending");
- return -1;
-}
-
-################################################################################
-## Get pending server by ip, beacon port. Returns * or undef
-################################################################################
-sub get_pending_beacon {
- my ($self, $ip, $port) = @_;
-
- # if address is in list, update the timestamp
- return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND beaconport = ?",
- undef, $ip, $port)->[0];
-}
-
-################################################################################
-## Get pending server by ip, heartbeat port. Returns * or undef
-################################################################################
-sub get_pending_info {
- my ($self, $ip, $port) = @_;
-
- # if address is in list, update the timestamp
- return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $ip, $port)->[0];
-}
-
-################################################################################
-## Get server info from any entry with an id higher than the provided one. The
-## server is added to pending at least 15 seconds ago. Returns info or undef.
-################################################################################
-sub get_next_pending {
- my ($self, $id) = @_;
-
- # get 1 pending id that is older than 15s
- return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, heartbeat, secure FROM pending
- WHERE added < (NOW() - INTERVAL '15 SECONDS')
- AND id > ?
- ORDER BY id ASC LIMIT 1",
- undef, $id)->[0];
-}
-
-
-1;
diff --git a/lib/MasterServer/Database/Pg/dbCiphers.pm b/lib/MasterServer/Database/Pg/dbCiphers.pm
deleted file mode 100755
index e2e8191..0000000
--- a/lib/MasterServer/Database/Pg/dbCiphers.pm
+++ /dev/null
@@ -1,98 +0,0 @@
-
-package MasterServer::Database::Pg::dbCiphers;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| clear_ciphers
- insert_cipher
- get_cipher
- get_default_port |;
-
-################################################################################
-## Clear all existing ciphers from the database
-################################################################################
-sub clear_ciphers {
- my $self = shift;
-
- # delete ALL entries
- my $u = $self->{dbh}->do("DELETE FROM games");
-
- # notify
- $self->log("delete", "Removed all ciphers") if ($u > 0);
-
- # removed from games
- return 2 if ($u > 0);
-
- # or else report notice
- $self->log("notice", "No ciphers deleted!");
- return -1;
-
-}
-
-################################################################################
-## Insert the list of supported games and their ciphers / default ports /
-## descriptions included from the data/supportedgames.pl file.
-################################################################################
-sub insert_cipher {
- my ($self, %opt) = @_;
-
- # insert a single cipher/key combo
- my $u = $self->{dbh}->do(
- "INSERT INTO games (
- gamename,
- cipher,
- description,
- default_qport)
- VALUES(?, ?, ?, ?)", undef,
- $opt{gamename}, $opt{cipher}, $opt{description}, $opt{default_qport});
-
- # notify
- $self->log("add", "Added cipher for $opt{gamename}") if ($u and $u > 0);
- return 1 if ($u and $u > 0);
-
- # or else report error
- $self->log("error", "An error occurred adding a cipher for $opt{gamename}");
- return -1;
-
-}
-
-
-################################################################################
-## get the cipher that goes with gamename
-################################################################################
-sub get_cipher {
- my ($self, $gn) = @_;
-
- # no gamename specified? "undef" is not a known cipher, so send that instead.
- return 'undef' if !$gn;
-
- # get cipher from db if gamename exists
- my $cipher = $self->{dbh}->selectall_arrayref(
- 'SELECT cipher FROM games WHERE gamename = ?', undef,
- lc $gn)->[0]->[0];
-
- # return a non-zero-length string
- return ($cipher ? $cipher : 'undef');
-}
-
-################################################################################
-## get the default query port that goes with gamename
-################################################################################
-sub get_default_port {
- my ($self, $gn) = @_;
-
- # no gamename specified? default port is 0
- return 0 if !$gn;
-
- # get port from db if gamename exists
- my $p = $self->{dbh}->selectall_arrayref(
- 'SELECT default_qport FROM games WHERE gamename = ?', undef,
- lc $gn)->[0]->[0];
-
- # return port or zero
- return $p || 0;
-}
-
-1;
diff --git a/lib/MasterServer/Database/Pg/dbClientList.pm b/lib/MasterServer/Database/Pg/dbClientList.pm
deleted file mode 100755
index 718bf8a..0000000
--- a/lib/MasterServer/Database/Pg/dbClientList.pm
+++ /dev/null
@@ -1,45 +0,0 @@
-
-package MasterServer::Database::Pg::dbClientList;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| get_gamenames
- get_game_list |;
-
-
-################################################################################
-## get a list of distinct gamenames currently in the database. it does not
-## matter whether they are recent or old, as long as the game is currently in
-## the database.
-##
-## returns: hashref of gamenames
-################################################################################
-sub get_gamenames {
- my $self = shift;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT distinct gamename
- FROM serverlist");
-}
-
-################################################################################
-## get the list of games of a certain $gamename, excluding the ones excempted
-## via the blacklist
-## only returns server addresses that are no more than 1 hours old
-################################################################################
-sub get_game_list {
- my ($self, $gamename) = @_;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT ip, port
- FROM serverlist
- WHERE updated > (NOW() - INTERVAL '1 HOUR')
- AND gamename = ?
- AND NOT blacklisted",
- undef, lc $gamename);
-}
-
-
-1;
diff --git a/lib/MasterServer/Database/Pg/dbCore.pm b/lib/MasterServer/Database/Pg/dbCore.pm
deleted file mode 100755
index 5cd194b..0000000
--- a/lib/MasterServer/Database/Pg/dbCore.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-
-package MasterServer::Database::Pg::dbCore;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| database_login |;
-
-################################################################################
-## login to the database with credentials provided in the config file.
-## returns dbh object or quits application on error.
-################################################################################
-sub database_login {
- my $self = shift;
-
- # create the dbi object
- my $dbh = DBI->connect(@{$self->{dblogin}}, {PrintError => $self->{db_print}});
-
- # verify that the database connected
- if (defined $dbh) {
-
- # log the event
- $self->log("load","Connected to the Postgres database.");
-
- # turn on error printing
- $dbh->{printerror} = 1;
-
- # return the dbi object for further use
- return $dbh;
- }
- else {
- # fatal error
- $self->log("fatal", "$DBI::errstr!");
-
- # end program
- $self->halt();
- }
-
- # unreachable
- return undef;
-}
-
-1;
diff --git a/lib/MasterServer/Database/Pg/dbServerlist.pm b/lib/MasterServer/Database/Pg/dbServerlist.pm
deleted file mode 100755
index 5a58717..0000000
--- a/lib/MasterServer/Database/Pg/dbServerlist.pm
+++ /dev/null
@@ -1,152 +0,0 @@
-
-package MasterServer::Database::Pg::dbServerlist;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| add_to_serverlist
- update_serverlist
- syncer_add
- get_next_server |;
-
-################################################################################
-## beacon was verified or otherwise accepted and will now be added to the
-## serverlist.
-################################################################################
-sub add_to_serverlist {
- my ($self, $ip, $port, $gamename) = @_;
-
- # update or add server to serverlist
- my $u = $self->{dbh}->do("UPDATE serverlist
- SET updated = NOW()
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "$ip:$port timestamp updated") if ($u > 0);
-
- # if found, updated; done
- return 0 if ($u > 0);
-
- # if not found, add it.
- $u = $self->{dbh}->do("INSERT INTO serverlist (ip, port, gamename, country)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $self->ip2country($ip));
-
- # notify
- $self->log("add", "$ip:$port added to serverlist") if ($u > 0);
-
- # return added
- return 1 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding server $ip:$port ($gamename) to the serverlist");
- return -1;
-}
-
-################################################################################
-## same as add_to_serverlist above, but does not add the server to serverlist
-## if it does not exist in serverlist. it must be added by another function
-## first.
-################################################################################
-sub update_serverlist {
- my ($self, $ip, $port, $s) = @_;
-
- # update server info
- my $u = $self->{dbh}->do(
- 'UPDATE serverlist
- SET updated = NOW(),
- gamename = ?,
- gamever = ?,
- hostname = ?,
- hostport = ?
- WHERE ip = ?
- AND port = ?', undef,
- $s->{gamename}, $s->{gamever}, $s->{hostname}, $s->{hostport},
- $ip, $port);
-
- # notify
- $self->log("update", "server $ip:$port info updated") if ($u > 0);
-
- # return 0 if updated
- return 0 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred updating server $ip:$port in the serverlist");
- return -1;
-}
-
-
-################################################################################
-## add new addresses to the pending list, but do not update timestamps. masters
-## that sync with each other would otherwise update the timestamp for a server
-## which is no longer online.
-################################################################################
-sub syncer_add {
- my ($self, $ip, $port, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "SELECT * FROM serverlist
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("read","syncer found entry for $ip:$port") if ($u > 0);
-
- # if found, return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $secure, $ip, $port);
-
- # notify
- $self->log("update","$ip:$port was updated by syncer",
- $self->{log_settings}->{db_updated}) if ($u > 0);
-
- # return 1 if found
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (ip, heartbeat, gamename, secure)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $secure);
-
- # notify
- $self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0);
-
- # return 2 if added new
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding $ip:$port after sync");
- return -1;
-}
-
-################################################################################
-## get a server address of the next server in line to be queried for game info.
-## query must be older than 30 seconds (in case it just got added) and not
-## older than 3 hours.
-################################################################################
-sub get_next_server {
- my ($self, $id) = @_;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, port FROM serverlist
- WHERE added < (NOW() - INTERVAL '15 SECONDS')
- AND updated > (NOW() - INTERVAL '3 HOUR')
- AND id > ?
- AND NOT blacklisted
- ORDER BY id ASC LIMIT 1", undef, $id)->[0];
-}
-
-1;
diff --git a/lib/MasterServer/Database/SQLite/dbBeacon.pm b/lib/MasterServer/Database/SQLite/dbBeacon.pm
deleted file mode 100755
index 09eeec3..0000000
--- a/lib/MasterServer/Database/SQLite/dbBeacon.pm
+++ /dev/null
@@ -1,203 +0,0 @@
-
-package MasterServer::Database::SQLite::dbBeacon;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| add_beacon
- add_pending
- remove_pending
- get_pending_beacon
- get_pending_info
- get_next_pending |;
-
-################################################################################
-## Update beacon in serverlist or pending list. Add if beacon does not exist in
-## either list. Return 0,1,2 if success in adding or -1 on error.
-################################################################################
-sub add_beacon {
- my ($self, $ip, $beaconport, $heartbeat, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "UPDATE serverlist
- SET beacon = CURRENT_TIMESTAMP,
- updated = CURRENT_TIMESTAMP,
- gamename = ?,
- b333ms = 1
- WHERE ip = ?
- AND port = ?",
- undef, lc $gamename, $ip, $heartbeat);
-
- # notify
- $self->log("update", "beacon heartbeat for $ip:$heartbeat") if ($u > 0);
-
- # if serverlist was updated return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET added = CURRENT_TIMESTAMP,
- beaconport = ?,
- gamename = ?,
- secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $beaconport, lc $gamename, $secure, $ip, $heartbeat);
-
- # notify
- $self->log("update", "beacon heartbeat $ip:$beaconport pending $gamename:$heartbeat") if ($u > 0);
-
- # beacon was already in pending list and was updated
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (
- ip,
- beaconport,
- heartbeat,
- gamename,
- secure)
- SELECT ?, ?, ?, ?, ?",
- undef, $ip, $beaconport, $heartbeat, lc $gamename, $secure);
-
- # notify
- $self->log("add", "beacon heartbeat $ip:$beaconport pending $gamename:$heartbeat") if ($u > 0);
-
- # it was added to pending
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding beacon $ip:$beaconport with $gamename:$heartbeat to the pending list");
- return -1;
-}
-
-################################################################################
-## Add an address to the database that was obtained via a method other than
-## an udp beacon. Return 0,1,2 if success in adding or -1 on error.
-################################################################################
-sub add_pending {
- my ($self, $ip, $port, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "UPDATE serverlist
- SET updated = CURRENT_TIMESTAMP
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "updated serverlist with $ip:$port") if ($u > 0);
-
- # if updated, return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET added = CURRENT_TIMESTAMP,
- secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $secure, $ip, $port);
-
- # notify
- $self->log("update", "updated pending with $ip:$port") if ($u > 0);
-
- # return 1 if updated
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (
- ip,
- heartbeat,
- gamename,
- secure)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $secure);
-
- # notify
- $self->log("add", "$ip:$port added pending $gamename") if ($u > 0);
-
- # return 2 if added new
- return 2 if ($u > 0);
-
- # else
- return -1;
-}
-
-################################################################################
-## Remove an entry from the pending list. Returns 0 if removed or -1 in case
-## of error(s).
-################################################################################
-sub remove_pending {
- my ($self, $id) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "DELETE FROM pending
- WHERE id = ?",
- undef, $id);
-
- # notify
- $self->log("delete", "removed pending id $id from pending") if ($u > 0);
-
- # it was removed from pending
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "error deleting server $id from pending");
- return -1;
-}
-
-################################################################################
-## Get pending server by ip, beacon port. Returns * or undef
-################################################################################
-sub get_pending_beacon {
- my ($self, $ip, $port) = @_;
-
- # if address is in list, update the timestamp
- return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND beaconport = ?",
- undef, $ip, $port)->[0];
-}
-
-################################################################################
-## Get pending server by ip, heartbeat port. Returns * or undef
-################################################################################
-sub get_pending_info {
- my ($self, $ip, $port) = @_;
-
- # if address is in list, update the timestamp
- return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $ip, $port)->[0];
-}
-
-################################################################################
-## Get server info from any entry with an id higher than the provided one. The
-## server is added to pending at least 15 seconds ago. Returns info or undef.
-################################################################################
-sub get_next_pending {
- my ($self, $id) = @_;
-
- # get 1 pending id that is older than 15s
- return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, heartbeat, secure FROM pending
- WHERE added < datetime(CURRENT_TIMESTAMP, '-15 seconds')
- AND id > ?
- ORDER BY id ASC LIMIT 1",
- undef, $id)->[0];
-}
-
-
-1;
diff --git a/lib/MasterServer/Database/SQLite/dbCiphers.pm b/lib/MasterServer/Database/SQLite/dbCiphers.pm
deleted file mode 100755
index 0dc388d..0000000
--- a/lib/MasterServer/Database/SQLite/dbCiphers.pm
+++ /dev/null
@@ -1,98 +0,0 @@
-
-package MasterServer::Database::SQLite::dbCiphers;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| clear_ciphers
- insert_cipher
- get_cipher
- get_default_port |;
-
-################################################################################
-## Clear all existing ciphers from the database
-################################################################################
-sub clear_ciphers {
- my $self = shift;
-
- # delete ALL entries
- my $u = $self->{dbh}->do("DELETE FROM games");
-
- # notify
- $self->log("delete", "Removed all ciphers") if ($u > 0);
-
- # removed from games
- return 2 if ($u > 0);
-
- # or else report notice
- $self->log("notice", "No ciphers deleted!");
- return -1;
-
-}
-
-################################################################################
-## Insert the list of supported games and their ciphers / default ports /
-## descriptions included from the data/supportedgames.pl file.
-################################################################################
-sub insert_cipher {
- my ($self, %opt) = @_;
-
- # insert a single cipher/key combo
- my $u = $self->{dbh}->do(
- "INSERT INTO games (
- gamename,
- cipher,
- description,
- default_qport)
- VALUES(?, ?, ?, ?)", undef,
- $opt{gamename}, $opt{cipher}, $opt{description}, $opt{default_qport});
-
- # notify
- $self->log("add", "Added cipher for $opt{gamename}") if ($u and $u > 0);
- return 1 if ($u and $u > 0);
-
- # or else report error
- $self->log("error", "An error occurred adding a cipher for $opt{gamename}");
- return -1;
-
-}
-
-
-################################################################################
-## get the cipher that goes with gamename
-################################################################################
-sub get_cipher {
- my ($self, $gn) = @_;
-
- # no gamename specified? "undef" is not a known cipher, so send that instead.
- return 'undef' if !$gn;
-
- # get cipher from db if gamename exists
- my $cipher = $self->{dbh}->selectall_arrayref(
- 'SELECT cipher FROM games WHERE gamename = ?', undef,
- lc $gn)->[0]->[0];
-
- # return a non-zero-length string
- return ($cipher ? $cipher : 'undef');
-}
-
-################################################################################
-## get the default query port that goes with gamename
-################################################################################
-sub get_default_port {
- my ($self, $gn) = @_;
-
- # no gamename specified? default port is 0
- return 0 if !$gn;
-
- # get port from db if gamename exists
- my $p = $self->{dbh}->selectall_arrayref(
- 'SELECT default_qport FROM games WHERE gamename = ?', undef,
- lc $gn)->[0]->[0];
-
- # return port or zero
- return $p || 0;
-}
-
-1;
diff --git a/lib/MasterServer/Database/SQLite/dbClientList.pm b/lib/MasterServer/Database/SQLite/dbClientList.pm
deleted file mode 100755
index 58c1392..0000000
--- a/lib/MasterServer/Database/SQLite/dbClientList.pm
+++ /dev/null
@@ -1,45 +0,0 @@
-
-package MasterServer::Database::SQLite::dbClientList;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| get_gamenames
- get_game_list |;
-
-
-################################################################################
-## get a list of distinct gamenames currently in the database. it does not
-## matter whether they are recent or old, as long as the game is currently in
-## the database.
-##
-## returns: hashref of gamenames
-################################################################################
-sub get_gamenames {
- my $self = shift;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT distinct gamename
- FROM serverlist");
-}
-
-################################################################################
-## get the list of games of a certain $gamename, excluding the ones excempted
-## via the blacklist
-## only returns server addresses that are no more than 1 hours old
-################################################################################
-sub get_game_list {
- my ($self, $gamename) = @_;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT ip, port
- FROM serverlist
- WHERE updated > datetime(CURRENT_TIMESTAMP, '-3600 seconds')
- AND gamename = ?
- AND NOT blacklisted",
- undef, lc $gamename);
-}
-
-
-1;
diff --git a/lib/MasterServer/Database/SQLite/dbCore.pm b/lib/MasterServer/Database/SQLite/dbCore.pm
deleted file mode 100755
index 27c9b35..0000000
--- a/lib/MasterServer/Database/SQLite/dbCore.pm
+++ /dev/null
@@ -1,66 +0,0 @@
-
-package MasterServer::Database::SQLite::dbCore;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| database_login |;
-
-################################################################################
-## login to the database with credentials provided in the config file.
-## returns dbh object or quits application on error.
-################################################################################
-sub database_login {
- my $self = shift;
-
- # check if database file exists
- my $db_file = [split(':', $self->{dblogin}->[0])]->[2];
- $db_file =~ s/dbname=//i;
-
- unless (-e $db_file) {
- # fatal error
- $self->log("fatal", "Database file $db_file does not exist!");
-
- # end program
- $self->halt();
- }
-
- # create the dbi object
- my $dbh = DBI->connect(@{$self->{dblogin}}, {PrintError => $self->{db_print}});
-
- # verify that the database connected
- if (defined $dbh) {
- # log the event
- $self->log("load","Connected to the SQLite database.");
-
- # turn on error printing
- $dbh->{printerror} = 1;
-
- # synchronous read/writing to the sql file OFF. That means: when the script
- # shuts down unexpectedly, i.e. because of power failure or a crash, changes
- # to the database are NOT SAVED. However, if this setting is not turned OFF,
- # it takes too long to write to the database, which means that new beacons,
- # requests and servers cannot be processed. You don't have a choice, really..
- $dbh->do("PRAGMA synchronous = OFF");
-
- # allow the use of foreign keys (referencing)
- $dbh->do("PRAGMA foreign_keys = ON");
-
- # return the dbi object for further use
- return $dbh;
- }
- else {
- # fatal error
- $self->log("fatal", "$DBI::errstr!");
-
- # end program
- $self->halt();
- }
-
- # unreachable
- return undef;
-
-}
-
-1;
diff --git a/lib/MasterServer/Database/SQLite/dbServerlist.pm b/lib/MasterServer/Database/SQLite/dbServerlist.pm
deleted file mode 100755
index 436a788..0000000
--- a/lib/MasterServer/Database/SQLite/dbServerlist.pm
+++ /dev/null
@@ -1,152 +0,0 @@
-
-package MasterServer::Database::SQLite::dbServerlist;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| add_to_serverlist
- update_serverlist
- syncer_add
- get_next_server |;
-
-################################################################################
-## beacon was verified or otherwise accepted and will now be added to the
-## serverlist.
-################################################################################
-sub add_to_serverlist {
- my ($self, $ip, $port, $gamename) = @_;
-
- # update or add server to serverlist
- my $u = $self->{dbh}->do("UPDATE serverlist
- SET updated = CURRENT_TIMESTAMP
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "$ip:$port timestamp updated") if ($u > 0);
-
- # if found, updated; done
- return 0 if ($u > 0);
-
- # if not found, add it.
- $u = $self->{dbh}->do("INSERT INTO serverlist (ip, port, gamename, country)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $self->ip2country($ip));
-
- # notify
- $self->log("add", "$ip:$port added to serverlist") if ($u > 0);
-
- # return added
- return 1 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding server $ip:$port ($gamename) to the serverlist");
- return -1;
-}
-
-################################################################################
-## same as add_to_serverlist above, but does not add the server to serverlist
-## if it does not exist in serverlist. it must be added by another function
-## first.
-################################################################################
-sub update_serverlist {
- my ($self, $ip, $port, $s) = @_;
-
- # update server info
- my $u = $self->{dbh}->do(
- 'UPDATE serverlist
- SET updated = CURRENT_TIMESTAMP,
- gamename = ?,
- gamever = ?,
- hostname = ?,
- hostport = ?
- WHERE ip = ?
- AND port = ?', undef,
- $s->{gamename}, $s->{gamever}, $s->{hostname}, $s->{hostport},
- $ip, $port);
-
- # notify
- $self->log("update", "server $ip:$port info updated") if ($u > 0);
-
- # return 0 if updated
- return 0 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred updating server $ip:$port in the serverlist");
- return -1;
-}
-
-
-################################################################################
-## add new addresses to the pending list, but do not update timestamps. masters
-## that sync with each other would otherwise update the timestamp for a server
-## which is no longer online.
-################################################################################
-sub syncer_add {
- my ($self, $ip, $port, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "SELECT * FROM serverlist
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("read","syncer found entry for $ip:$port") if ($u > 0);
-
- # if found, return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $secure, $ip, $port);
-
- # notify
- $self->log("update","$ip:$port was updated by syncer",
- $self->{log_settings}->{db_updated}) if ($u > 0);
-
- # return 1 if found
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (ip, heartbeat, gamename, secure)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $secure);
-
- # notify
- $self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0);
-
- # return 2 if added new
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding $ip:$port after sync");
- return -1;
-}
-
-################################################################################
-## get a server address of the next server in line to be queried for game info.
-## query must be older than 30 seconds (in case it just got added) and not
-## older than 3 hours.
-################################################################################
-sub get_next_server {
- my ($self, $id) = @_;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, port FROM serverlist
- WHERE added < datetime(CURRENT_TIMESTAMP, '-15 seconds')
- AND updated > datetime(CURRENT_TIMESTAMP, '-10800 seconds')
- AND id > ?
- AND NOT blacklisted
- ORDER BY id ASC LIMIT 1", undef, $id)->[0];
-}
-
-1;
diff --git a/lib/MasterServer/Database/dbAddServers.pm b/lib/MasterServer/Database/dbAddServers.pm
new file mode 100755
index 0000000..4898316
--- /dev/null
+++ b/lib/MasterServer/Database/dbAddServers.pm
@@ -0,0 +1,162 @@
+
+package MasterServer::Database::dbAddServers;
+
+use strict;
+use warnings;
+use MasterServer::Core::Util 'sqlprint';
+use Exporter 'import';
+
+our @EXPORT = qw| add_server_new
+ add_server_list
+ update_server_list
+ syncer_add |;
+
+################################################################################
+## Update an existing address or add a new address to the pending list.
+## opts: direct beacon, set update, game
+################################################################################
+sub add_server_new {
+ my $self = shift;
+ my %o = (
+ updated => time,
+ @_);
+
+ # try updating it in serverlist
+ my %H = (
+ $o{direct} ? ( 'b333ms = CAST(? AS BOOLEAN)' => $o{direct}) : (),
+ $o{updated} ? ( 'updated = to_timestamp(?)' => $o{updated}) : (),
+ $o{beacon} ? ( 'beacon = to_timestamp(?)' => $o{beacon}) : (),
+ $o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (),
+ );
+
+ my($q, @p) = sqlprint("UPDATE serverlist !H
+ WHERE ip = ? AND port = ?", \%H, $o{ip}, $o{heartbeat});
+
+ my $n = $self->{dbh}->do($q, undef, @p);
+
+ # if serverlist was updated
+ return 0 if ($n > 0);
+
+
+ # try updating it in pending
+ %H = (
+ $o{added} ? ( 'added = ?' => $o{added}) : (),
+ $o{secure} ? ( 'secure = ?' => $o{secure}) : (),
+ $o{gamename} ? ( 'gamename = ?' => lc $o{gamename}) : (),
+ $o{beaconport} ? ('beaconport = ?' => $o{beaconport}) : (),
+ );
+
+ ($q, @p) = sqlprint("UPDATE pending !H
+ WHERE ip = ? AND heartbeat = ?", \%H, $o{ip}, $o{heartbeat});
+
+ # exec query
+ $n = $self->{dbh}->do($q, undef, @p);
+
+ # if beacon was updated
+ return 1 if ($n > 0);
+
+ # if not found at all, add to pending
+ $n = $self->{dbh}->do(
+ "INSERT INTO pending (
+ ip,
+ beaconport,
+ heartbeat,
+ gamename,
+ secure)
+ SELECT ?, ?, ?, ?, ?",
+ undef, $o{ip}, $o{beaconport}, $o{heartbeat}, lc $o{gamename}, $o{secure});
+
+ # it was added to pending
+ return 2 if ($n > 0);
+}
+
+################################################################################
+## Update the server info in the serverlist
+################################################################################
+sub update_server_list {
+ my $self = shift;
+ my %o = (
+ updated => time,
+ @_);
+
+ # try updating it in serverlist
+ my %H = (
+ $o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (),
+ $o{gamever} ? ( 'gamever = ?' => $o{gamever}) : (),
+ $o{hostname} ? ('hostname = ?' => $o{hostname}) : (),
+ $o{hostport} ? ('hostport = ?' => $o{hostport}) : (),
+ $o{updated} ? ( 'updated = to_timestamp(?)' => $o{updated}) : (),
+ );
+
+ my($q, @p) = sqlprint("UPDATE serverlist !H
+ WHERE ip = ? AND port = ?", \%H, $o{ip}, $o{port});
+
+ return $self->{dbh}->do($q, undef, @p);
+}
+
+################################################################################
+## beacon was verified or otherwise accepted and will now be added to the
+## serverlist.
+################################################################################
+sub add_server_list {
+ my $self = shift;
+ my %o = @_;
+
+ # insert basic data
+ return $self->{dbh}->do("INSERT INTO serverlist (ip, port, gamename, country)
+ SELECT ?, ?, ?, ?", undef,
+ $o{ip}, $o{port}, lc $o{gamename}, $self->ip2country($o{ip}));
+}
+
+################################################################################
+## add new addresses to the pending list, but do not update timestamps. masters
+## that sync with each other would otherwise update the timestamp for a server
+## which is no longer online.
+################################################################################
+sub syncer_add {
+ my ($self, $ip, $port, $gamename, $secure) = @_;
+
+ # if address is in the list AND up to date,
+ # acknowledge its existance but don't do anything with it
+ my $u = $self->{dbh}->do(
+ "SELECT * FROM serverlist
+ WHERE ip = ?
+ AND port = ?
+ AND updated > to_timestamp(?)",
+ undef, $ip, $port, time-7200);
+
+ # if found, return 0
+ return 0 if ($u > 0);
+
+ # if it is already in the pending list, update it with a new challenge
+ $u = $self->{dbh}->do(
+ "UPDATE pending
+ SET secure = ?
+ WHERE ip = ?
+ AND heartbeat = ?",
+ undef, $secure, $ip, $port);
+
+ # notify
+ $self->log("update","$ip:$port was updated by syncer") if ($u > 0);
+
+ # return 1 if found
+ return 1 if ($u > 0);
+
+ # if not found or out of date, add it to pending to be checked again
+ $u = $self->{dbh}->do(
+ "INSERT INTO pending (ip, heartbeat, gamename, secure)
+ SELECT ?, ?, ?, ?",
+ undef, $ip, $port, lc $gamename, $secure);
+
+ # notify
+ $self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0);
+
+ # return 2 if added new
+ return 2 if ($u > 0);
+
+ # or else report error
+ $self->log("error", "an error occurred adding $ip:$port after sync");
+ return -1;
+}
+
+1;
diff --git a/lib/MasterServer/Database/dbCiphers.pm b/lib/MasterServer/Database/dbCiphers.pm
new file mode 100755
index 0000000..e62552d
--- /dev/null
+++ b/lib/MasterServer/Database/dbCiphers.pm
@@ -0,0 +1,57 @@
+
+package MasterServer::Database::dbCiphers;
+
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw| clear_ciphers
+ insert_cipher
+ get_game_props |;
+
+################################################################################
+## Clear all existing ciphers from the database
+################################################################################
+sub clear_ciphers {
+ my $self = shift;
+
+ # delete ALL entries
+ my $u = $self->{dbh}->do("DELETE FROM games");
+}
+
+################################################################################
+## Insert the list of supported games and their ciphers / default ports /
+## descriptions included from the data/supportedgames.pl file.
+################################################################################
+sub insert_cipher {
+ my ($self, %opt) = @_;
+
+ # insert a single cipher/key combo
+ my $u = $self->{dbh}->do(
+ "INSERT INTO games (
+ gamename,
+ cipher,
+ description,
+ default_qport)
+ VALUES(?, ?, ?, ?)", undef,
+ lc $opt{gamename}, $opt{cipher}, $opt{description}, $opt{default_qport});
+ return 1 if ($u and $u > 0);
+
+ # or else report error
+ $self->log("error", "An error occurred adding a cipher for $opt{gamename}");
+}
+
+################################################################################
+## get the cipher, description and default port that goes with given gamename
+################################################################################
+sub get_game_props {
+ my ($self, $gn) = @_;
+
+ # get cipher from db if gamename exists
+ return $self->{dbh}->selectall_arrayref(
+ 'SELECT * FROM games WHERE gamename = ?',
+ {Slice=>{}},
+ lc $gn)->[0];
+}
+
+1;
diff --git a/lib/MasterServer/Database/dbCore.pm b/lib/MasterServer/Database/dbCore.pm
new file mode 100755
index 0000000..4ea0c79
--- /dev/null
+++ b/lib/MasterServer/Database/dbCore.pm
@@ -0,0 +1,83 @@
+
+package MasterServer::Database::dbCore;
+
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw| database_login |;
+
+################################################################################
+## login to the database with credentials provided in the config file.
+## returns dbh object or quits application on error.
+##
+## Recommended database types: Postgresql, MySQL or SQLite. Warranty void if
+## other database types are used. Use at your own risk.
+################################################################################
+sub database_login {
+ my $self = shift;
+
+ # read db type from db login
+ my @db_type = split(':', $self->{dblogin}->[0]);
+
+ # if the type is an SQLite database, check if the database file exists
+ if ( "SQLite" =~ m/$db_type[1]/i) {
+
+ # check if database file exists
+ my $db_file = [split(':', $self->{dblogin}->[0])]->[2];
+ $db_file =~ s/dbname=//i;
+
+ unless (-e $db_file) {
+ # fatal error
+ $self->log("fatal", "Database file $db_file does not exist!");
+
+ # end program
+ $self->halt();
+ }
+ }
+
+ # inform what DB we try to load
+ # $self->log("info","Database: $db_type[1]");
+
+ # create the dbi object
+ my $dbh = DBI->connect(@{$self->{dblogin}}, {PrintError => $self->{db_print}});
+
+ # verify that the database connected
+ if (defined $dbh) {
+
+ # log the event
+ $self->log("info","Connected to the $db_type[1] database.");
+
+ # turn on error printing
+ $dbh->{printerror} = 1;
+
+ # if the type is an SQLite database, disable sync rw.
+ if ( "SQLite" =~ m/$db_type[1]/i) {
+
+ # synchronous read/writing to the SQLite file OFF. That means: when the script
+ # shuts down unexpectedly, i.e. because of power failure or a crash, changes
+ # to the database are NOT SAVED. However, if this setting is not turned OFF,
+ # it takes too long to write to the database, which means that new beacons,
+ # requests and servers cannot be processed. You don't have a choice, really..
+ $dbh->do("PRAGMA synchronous = OFF");
+
+ # allow the use of foreign keys (referencing)
+ $dbh->do("PRAGMA foreign_keys = ON");
+ }
+
+ # return the dbi object for further use
+ return $dbh;
+ }
+ else {
+ # fatal error
+ $self->log("fatal", "$DBI::errstr!");
+
+ # end program
+ $self->halt();
+ }
+
+ # in case of any other error, return undef.
+ return undef;
+}
+
+1;
diff --git a/lib/MasterServer/Database/dbGetServers.pm b/lib/MasterServer/Database/dbGetServers.pm
new file mode 100755
index 0000000..5069462
--- /dev/null
+++ b/lib/MasterServer/Database/dbGetServers.pm
@@ -0,0 +1,139 @@
+package MasterServer::Database::dbGetServers;
+
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw| get_server
+ get_pending
+ get_gamenames |;
+
+################################################################################
+## get server details for one or multiple servers
+## opts: limit, see $order
+################################################################################
+sub get_server {
+ my $s = shift;
+ my %o = (
+ sort => '',
+ @_
+ );
+
+ my %where = (
+ $o{next_id} ? ( 'id > ?' => $o{next_id}) : (),
+ $o{id} ? ( 'id = ?' => $o{id}) : (),
+ $o{ip} ? ( 'ip = ?' => $o{ip}) : (),
+ $o{port} ? ( 'port = ?' => $o{port}) : (),
+ $o{gamename} ? ( 'gamename = ?' => lc $o{gamename}) : (),
+ $o{gamever} ? ( 'gamever = ?' => $o{gamever}) : (),
+ $o{hostname} ? ( 'hostname = ?' => $o{hostname}) : (),
+ $o{hostport} ? ( 'hostport = ?' => $o{hostport}) : (),
+ $o{country} ? ( 'country = ?' => $o{country}) : (),
+ $o{b333ms} ? ( 'b333ms = ?' => $o{b333ms}) : (),
+ $o{blacklisted} ? ('blacklisted = ?' => $o{blacklisted}) : (),
+ $o{added} ? ( 'added < to_timestamp(?)' => (time-$o{added})) : (),
+ $o{beacon} ? ( 'beacon > to_timestamp(?)' => (time-$o{beacon})) : (),
+ $o{updated} ? ('updated > to_timestamp(?)' => (time-$o{updated})) : (),
+ $o{before} ? ('updated < to_timestamp(?)' => (time-$o{before})) : (),
+ );
+
+ my @select = ( qw|
+ id
+ ip
+ port
+ gamename
+ gamever
+ hostname
+ hostport
+ country
+ b333ms
+ blacklisted
+ added
+ beacon
+ updated
+ |);
+
+ my $order = sprintf {
+ id => 'id %s',
+ ip => 'ip %s',
+ port => 'port %s',
+ gamename => 'gamename %s',
+ gamever => 'gamever %s',
+ hostname => 'hostname %s',
+ hostport => 'hostport %s',
+ country => 'country %s',
+ b333ms => 'b333ms %s',
+ blacklisted => 'blacklisted %s',
+ added => 'added %s',
+ beacon => 'beacon %s',
+ updated => 'updated %s',
+ }->{ $o{sort}||'id' }, $o{reverse} ? 'DESC' : 'ASC';
+
+ return $s->db_all( q|
+ SELECT !s FROM serverlist
+ !W
+ ORDER BY !s|
+ .($o{limit} ? " LIMIT ?" : ""),
+ join(', ', @select), \%where, $order, ($o{limit} ? $o{limit} : ()),
+ );
+}
+
+################################################################################
+## get server details for one or multiple pending servers
+## opts: limit, next_id, beaconport, heartbeat, gamename, secure, enctype, added
+################################################################################
+sub get_pending {
+ my $s = shift;
+ my %o = (
+ sort => '',
+ @_
+ );
+
+ my %where = (
+ $o{next_id} ? ( 'id > ?' => $o{next_id}) : (),
+ $o{id} ? ( 'id = ?' => $o{id}) : (),
+ $o{ip} ? ( 'ip = ?' => $o{ip}) : (),
+ $o{beaconport} ? ('beaconport = ?' => $o{beaconport}) : (),
+ $o{heartbeat} ? ( 'heartbeat = ?' => $o{heartbeat}) : (),
+ $o{gamename} ? ( 'gamename = ?' => lc $o{gamename}) : (),
+ $o{secure} ? ( 'secure = ?' => $o{secure}) : (),
+ $o{enctype} ? ( 'enctype = ?' => $o{enctype}) : (),
+ $o{added} ? ('added < to_timestamp(?)' => (time-$o{added})) : (),
+ $o{after} ? ('added > to_timestamp(?)' => (time-$o{after})) : (),
+ );
+
+ my @select = ( qw| id ip beaconport heartbeat gamename secure enctype added |,);
+ my $order = sprintf {
+ id => 'id %s',
+ ip => 'ip %s',
+ beaconport => 'beaconport %s',
+ heartbeat => 'heartbeat %s',
+ gamename => 'gamename %s',
+ secure => 'secure %s',
+ enctype => 'enctype %s',
+ added => 'added %s',
+ }->{ $o{sort}||'id' }, $o{reverse} ? 'DESC' : 'ASC';
+
+ return $s->db_all( q|
+ SELECT !s FROM pending
+ !W
+ ORDER BY !s|
+ .($o{limit} ? " LIMIT ?" : ""),
+ join(', ', @select), \%where, $order, ($o{limit} ? $o{limit} : ()),
+ );
+}
+
+################################################################################
+## get a list of distinct gamenames currently in the database. it does not
+## matter whether they are recent or old, as long as the game is currently in
+## the database.
+################################################################################
+sub get_gamenames {
+ my $self = shift;
+
+ return $self->{dbh}->selectall_arrayref(
+ "SELECT distinct gamename
+ FROM serverlist");
+}
+
+1;
diff --git a/lib/MasterServer/Database/dbMaintenance.pm b/lib/MasterServer/Database/dbMaintenance.pm
new file mode 100755
index 0000000..41ea93a
--- /dev/null
+++ b/lib/MasterServer/Database/dbMaintenance.pm
@@ -0,0 +1,40 @@
+package MasterServer::Database::dbMaintenance;
+
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw| delete_old_pending
+ remove_pending |;
+
+################################################################################
+## delete unresponsive servers from the pending list
+## where the server is unresponsive for more than 1 hour
+################################################################################
+sub delete_old_pending {
+ my ($self) = shift;
+
+ # remove servers
+ my $u = $self->{dbh}->do(
+ "DELETE FROM pending
+ WHERE added < to_timestamp(?)", undef, time-3600);
+
+ # notify
+ $self->log("delete", "Removed $u entries from pending.") if ($u > 0);
+}
+
+################################################################################
+## Remove an entry from the pending list. Returns 0 if removed or -1 in case
+## of error(s).
+################################################################################
+sub remove_pending {
+ my ($self, $id) = @_;
+
+ # if address is in list, update the timestamp
+ my $u = $self->{dbh}->do("DELETE FROM pending WHERE id = ?", undef, $id);
+
+ # notify
+ $self->log("delete", "removed pending id $id from pending") if ($u > 0);
+}
+
+1;
diff --git a/lib/MasterServer/Database/dbStats.pm b/lib/MasterServer/Database/dbStats.pm
new file mode 100755
index 0000000..70962dc
--- /dev/null
+++ b/lib/MasterServer/Database/dbStats.pm
@@ -0,0 +1,117 @@
+package MasterServer::Database::dbStats;
+
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw| get_gamelist_stats
+ write_direct_beacons
+ write_stat
+ write_kfstats |;
+
+################################################################################
+# calculate stats for all individual games
+################################################################################
+sub get_gamelist_stats {
+ my $self = shift;
+
+ return $self->{dbh}->selectall_arrayref(
+ "SELECT DISTINCT gamename AS gamename,
+ COUNT(NULLIF(b333ms AND updated > to_timestamp(?), FALSE)) AS numdirect,
+ COUNT(NULLIF(updated > to_timestamp(?), FALSE)) AS numtotal
+ FROM serverlist
+ GROUP BY gamename", undef, time-7200, time-7200);
+}
+
+################################################################################
+# Determine from the last beacon whether the server is still uplinking
+# directly to us, or whether it stopped uplinking and is now artificially
+# kept in the database.
+################################################################################
+sub write_direct_beacons {
+ my $self = shift;
+ my $u = $self->{dbh}->do(
+ "UPDATE serverlist
+ SET b333ms = CAST(0 AS BOOLEAN)
+ WHERE beacon < to_timestamp(?) AND b333ms",
+ undef, time-3600);
+
+ # notify
+ $self->log("unset", "Lost $u direct beacons.") if ($u > 0);
+}
+
+################################################################################
+# Write the stats to the games table
+# A stat can not exist without existing gamename. Was inserted by cipher loader.
+################################################################################
+sub write_stat {
+ my ($self, %opt) = @_;
+
+ # if it is already in the pending list, update it with a new challenge
+ my $u = $self->{dbh}->do(
+ "UPDATE games
+ SET num_uplink = ?,
+ num_total = ?
+ WHERE gamename = ?",
+ undef, $opt{num_uplink}, $opt{num_total}, lc $opt{gamename});
+
+ # notify
+ $self->log("update", "Updated stats for $opt{gamename}.") if ($u > 0);
+
+}
+
+################################################################################
+## Write the KFStats to the database
+################################################################################
+sub write_kfstats {
+ my ($self, $h) = @_;
+
+ # check if entry already excists.
+ my $u = $self->{dbh}->selectall_arrayref(
+ "SELECT * FROM kfstats WHERE UTkey = ? ", undef, $h->{UTkey});
+
+ if ( !defined $u->[0] ) {
+ $u = $self->{dbh}->do(
+ "INSERT INTO kfstats (UTkey, Username) VALUES (?,?)",
+ undef, $h->{UTkey}, $h->{Username});
+
+ # notify
+ $self->log("kfnew", "New KF Player $h->{Username} added");
+ }
+
+ # update existing information
+ $u = $self->{dbh}->do("UPDATE kfstats SET
+ Username = ?,
+ CurrentVeterancy = ?,
+ TotalKills = ?,
+ DecaptedKills = ?,
+ TotalMeleeDamage = ?,
+ MeleeKills = ?,
+ PowerWpnKills = ?,
+ BullpupDamage = ?,
+ StalkerKills = ?,
+ TotalWelded = ?,
+ TotalHealed = ?,
+ TotalPlaytime =?,
+ GamesWon = ?,
+ GamesLost = ?
+ WHERE UTkey = ?", undef,
+ $h->{Username},
+ $h->{CurrentVeterancy},
+ $h->{TotalKills},
+ $h->{DecaptedKills},
+ $h->{TotalMeleeDamage},
+ $h->{MeleeKills},
+ $h->{PowerWpnKills},
+ $h->{BullpupDamage},
+ $h->{StalkerKills},
+ $h->{TotalWelded},
+ $h->{TotalHealed},
+ $h->{TotalPlaytime},
+ $h->{GamesWon},
+ $h->{GamesLost},
+ $h->{UTkey}
+ );
+}
+
+1;
diff --git a/lib/MasterServer/Database/dbUTServerInfo.pm b/lib/MasterServer/Database/dbUTServerInfo.pm
new file mode 100755
index 0000000..9352e38
--- /dev/null
+++ b/lib/MasterServer/Database/dbUTServerInfo.pm
@@ -0,0 +1,238 @@
+
+package MasterServer::Database::dbUTServerInfo;
+
+use strict;
+use warnings;
+use MasterServer::Core::Util 'sqlprint';
+use Exporter 'import';
+
+our @EXPORT = qw| get_utserver
+ add_utserver
+ update_utserver
+ delete_utplayers
+ insert_utplayer |;
+
+################################################################################
+## get server details for one or multiple UT servers
+## opts: limit, see $order
+################################################################################
+sub get_utserver {
+ my $s = shift;
+ my %o = (
+ sort => '',
+ @_
+ );
+
+ my %where = (
+ $o{id} ? ('server_id = ?' => $o{id}) : (),
+ $o{minnetver} ? ('minnetver = ?' => $o{minnetver}) : (),
+ $o{gamever} ? ('gamever = ?' => $o{gamever}) : (),
+ $o{location} ? ('location = ?' => $o{location}) : (),
+ $o{listenserver} ? ('listenserver = ?' => $o{listenserver}) : (),
+ $o{hostport} ? ('hostport = ?' => $o{hostport}) : (),
+ $o{hostname} ? ('hostname = ?' => $o{hostname}) : (),
+ $o{adminname} ? ('adminname = ?' => $o{adminname}) : (),
+ $o{adminemail} ? ('adminemail = ?' => $o{adminemail}) : (),
+ $o{password} ? ('password = ?' => $o{password}) : (),
+ $o{gametype} ? ('gametype = ?' => $o{gametype}) : (),
+ $o{gamestyle} ? ('gamestyle = ?' => $o{gamestyle}) : (),
+ $o{changelevels} ? ('changelevels = ?' => $o{changelevels}) : (),
+ $o{maptitle} ? ('maptitle = ?' => $o{maptitle}) : (),
+ $o{mapname} ? ('mapname = ?' => $o{mapname}) : (),
+ $o{numplayers} ? ('numplayers = ?' => $o{numplayers}) : (),
+ $o{maxplayers} ? ('maxplayers = ?' => $o{maxplayers}) : (),
+ $o{minplayers} ? ('minplayers = ?' => $o{minplayers}) : (),
+ $o{botskill} ? ('botskill = ?' => $o{botskill}) : (),
+ $o{balanceteams} ? ('balanceteams = ?' => $o{balanceteams}) : (),
+ $o{playersbalanceteams} ? ('playersbalanceteams = ?' => $o{playersbalanceteams}) : (),
+ $o{friendlyfire} ? ('friendlyfire = ?' => $o{friendlyfire}) : (),
+ $o{maxteams} ? ('maxteams = ?' => $o{maxteams}) : (),
+ $o{timelimit} ? ('timelimit = ?' => $o{timelimit}) : (),
+ $o{goalteamscore} ? ('goalteamscore = ?' => $o{goalteamscore}) : (),
+ $o{fraglimit} ? ('fraglimit = ?' => $o{fraglimit}) : (),
+ $o{mutators} ? ('hostname ILIKE ?' => "%$o{mutators}%") : (),
+ $o{updated} ? ('updated > to_timestamp(?)'=> (time-$o{updated})) : (),
+ );
+
+ my @select = ( qw|
+ server_id
+ minnetver
+ gamever
+ location
+ listenserver
+ hostport
+ hostname
+ adminname
+ adminemail
+ password
+ gametype
+ gamestyle
+ changelevels
+ maptitle
+ mapname
+ numplayers
+ maxplayers
+ minplayers
+ botskill
+ balanceteams
+ playersbalanceteams
+ friendlyfire
+ maxteams
+ timelimit
+ goalteamscore
+ fraglimit
+ mutators
+ updated
+ |);
+
+ my $order = sprintf {
+ server_id => 'server_id %s',
+ minnetver => 'minnetver %s',
+ gamever => 'gamever %s',
+ location => 'location %s',
+ listenserver => 'listenserver %s',
+ hostport => 'hostport %s',
+ hostname => 'hostname %s',
+ adminname => 'adminname %s',
+ adminemail => 'adminemail %s',
+ password => 'password %s',
+ gametype => 'gametype %s',
+ gamestyle => 'gamestyle %s',
+ changelevels => 'changelevels %s',
+ maptitle => 'maptitle %s',
+ mapname => 'mapname %s',
+ numplayers => 'numplayers %s',
+ maxplayers => 'maxplayers %s',
+ minplayers => 'minplayers %s',
+ botskill => 'botskill %s',
+ balanceteams => 'balanceteams %s',
+ playersbalanceteams => 'playersbalanceteams %s',
+ friendlyfire => 'friendlyfire %s',
+ maxteams => 'maxteams %s',
+ timelimit => 'timelimit %s',
+ goalteamscore => 'goalteamscore %s',
+ fraglimit => 'fraglimit %s',
+ mutators => 'mutators %s',
+ updated => 'updated %s',
+ }->{ $o{sort}||'server_id' }, $o{reverse} ? 'DESC' : 'ASC';
+
+ return $s->db_all( q|
+ SELECT !s FROM utserver_info
+ !W
+ ORDER BY !s|
+ .($o{limit} ? " LIMIT ?" : ""),
+ join(', ', @select), \%where, $order, ($o{limit} ? $o{limit} : ()),
+ );
+}
+
+
+################################################################################
+## Update serverinfo for an existing address to the utserver list.
+## opts: all server info data fields.
+################################################################################
+sub update_utserver {
+ my $self = shift;
+ my $id = shift;
+ my %s = (
+ # defaults
+ updated => time,
+ @_);
+
+ # try updating it in serverlist
+ my %H = (
+ $s{minnetver} ? ( 'minnetver = ?' => $s{minnetver} ) : (),
+ $s{gamever} ? ( 'gamever = ?' => int( $s{gamever}) ) : (),
+ $s{location} ? ( 'location = ?' => $s{location} ) : (),
+ $s{listenserver} ? ( 'listenserver = ?' => ( $s{listenserver} ? 1 : 0) ) : (),
+ $s{hostport} ? ( 'hostport = ?' => $s{hostport}) : (),
+ $s{hostname} ? ( 'hostname = ?' => $s{hostname}) : (),
+ $s{AdminName} ? ( 'adminname = ?' => $s{AdminName}) : (),
+ $s{AdminEMail} ? ( 'adminemail = ?' => $s{AdminEMail}) : (),
+ $s{password} ? ( 'password = ?' => ( $s{password} ? 1 : 0) ) : (),
+ $s{gametype} ? ( 'gametype = ?' => $s{gametype}) : (),
+ $s{gamestyle} ? ( 'gamestyle = ?' => $s{gamestyle}) : (),
+ $s{changelevels} ? ( 'changelevels = ?' => ( $s{changelevels} ? 1 : 0) ) : (),
+ $s{maptitle} ? ( 'maptitle = ?' => $s{maptitle}) : (),
+ $s{mapname} ? ( 'mapname = ?' => $s{mapname}) : (),
+ $s{numplayers} ? ( 'numplayers = ?' => $s{numplayers}) : ('numplayers = ?' => 0),
+ $s{maxplayers} ? ( 'maxplayers = ?' => $s{maxplayers}) : ('maxplayers = ?' => 0),
+ $s{minplayers} ? ( 'minplayers = ?' => $s{minplayers}) : ('minplayers = ?' => 0),
+ $s{botskill} ? ( 'botskill = ?' => $s{botskill}) : (),
+ $s{balanceteams} ? ( 'balanceteams = ?' => ( $s{balanceteams} ? 1 : 0) ) : (),
+ $s{playersbalanceteams} ? ( 'playersbalanceteams = ?' => ( $s{playersbalanceteams} ? 1 : 0) ) : (),
+ $s{friendlyfire} ? ( 'friendlyfire = ?' => $s{friendlyfire}) : (),
+ $s{maxteams} ? ( 'maxteams = ?' => $s{maxteams}) : (),
+ $s{timelimit} ? ( 'timelimit = ?' => $s{timelimit}) : (),
+ $s{goalteamscore} ? ( 'goalteamscore = ?' => int( $s{goalteamscore}) ) : (),
+ $s{fraglimit} ? ( 'fraglimit = ?' => int( $s{fraglimit}) ) : (),
+ $s{mutators} ? ( 'mutators = ?' => $s{mutators}) : ('mutators = ?' => "None"),
+ $s{updated} ? ('updated = to_timestamp(?)' => $s{updated}) : (),
+ );
+
+ my($q, @p) = sqlprint("UPDATE utserver_info !H WHERE server_id = ?", \%H, $id);
+ return $self->{dbh}->do($q, undef, @p);
+}
+
+
+################################################################################
+## Add a new utserver and trigger the update routine above.
+## opts: id, server info data
+################################################################################
+sub add_utserver {
+ my ($self, $ip, $port) = @_;
+
+ # create new entry
+ return $self->{dbh}->do(
+ "INSERT INTO utserver_info (server_id)
+ SELECT (SELECT id FROM serverlist WHERE ip = ? AND port = ?)",
+ undef, $ip, $port);
+}
+
+
+################################################################################
+## Delete all players from a certain server ID
+## opts: server id
+################################################################################
+sub delete_utplayers {
+ my ($self, $sid) = @_;
+
+ # delete players for server_id
+ return $self->{dbh}->do(
+ "DELETE FROM utplayer_info WHERE server_id = ?",
+ undef, $sid);
+}
+
+################################################################################
+## Insert player info for a single player in server sid
+## opts: server id, player info
+################################################################################
+sub insert_utplayer {
+ my $self = shift;
+ my $sid = shift;
+ my %s = (
+ updated => time,
+ @_);
+
+ # apparently useless chunk of code
+ # FIXME move to site part
+ my %H = (
+ $s{server_id} ? ( 'server_id = ?' => $s{server_id}) : (),
+ $s{player} ? ( 'player = ?' => $s{player}) : (),
+ $s{team} ? ( 'team = ?' => int( $s{team})) : (),
+ $s{frags} ? ( 'frags = ?' => int( $s{frags})) : (),
+ $s{mesh} ? ( 'mesh = ?' => $s{mesh}) : (),
+ $s{skin} ? ( 'skin = ?' => $s{skin}) : (),
+ $s{face} ? ( 'face = ?' => $s{face}) : (),
+ $s{ping} ? ( 'ping = ?' => int( $s{ping})) : (),
+ $s{ngsecret} ? ( 'ngsecret = ?' => $s{ngsecret}) : (),
+ $s{updated} ? ('updated = to_timestamp(?)' => $s{updated}) : (),
+ );
+
+ # insert
+ return $self->{dbh}->do(
+ "INSERT INTO utplayer_info (server_id, player, team, frags, mesh, skin, face, ping, ngsecret)
+ VALUES (?,?,?,?,?,?,?,?,?)",
+ undef, $sid, $s{player}, $s{team}, $s{frags}, $s{mesh}, $s{skin}, $s{face}, $s{ping}, $s{ngsecret});
+}
+
+1;
diff --git a/lib/MasterServer/Database/mysql/dbBeacon.pm b/lib/MasterServer/Database/mysql/dbBeacon.pm
deleted file mode 100755
index fd956b7..0000000
--- a/lib/MasterServer/Database/mysql/dbBeacon.pm
+++ /dev/null
@@ -1,203 +0,0 @@
-
-package MasterServer::Database::mysql::dbBeacon;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| add_beacon
- add_pending
- remove_pending
- get_pending_beacon
- get_pending_info
- get_next_pending |;
-
-################################################################################
-## Update beacon in serverlist or pending list. Add if beacon does not exist in
-## either list. Return 0,1,2 if success in adding or -1 on error.
-################################################################################
-sub add_beacon {
- my ($self, $ip, $beaconport, $heartbeat, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "UPDATE serverlist
- SET beacon = NOW(),
- updated = NOW(),
- gamename = ?,
- b333ms = 1
- WHERE ip = ?
- AND port = ?",
- undef, lc $gamename, $ip, $heartbeat);
-
- # notify
- $self->log("update", "beacon heartbeat for $ip:$heartbeat") if ($u > 0);
-
- # if serverlist was updated return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET added = NOW(),
- beaconport = ?,
- gamename = ?,
- secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $beaconport, lc $gamename, $secure, $ip, $heartbeat);
-
- # notify
- $self->log("update", "beacon heartbeat $ip:$beaconport pending $gamename:$heartbeat") if ($u > 0);
-
- # beacon was already in pending list and was updated
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (
- ip,
- beaconport,
- heartbeat,
- gamename,
- secure)
- SELECT ?, ?, ?, ?, ?",
- undef, $ip, $beaconport, $heartbeat, lc $gamename, $secure);
-
- # notify
- $self->log("add", "beacon heartbeat $ip:$beaconport pending $gamename:$heartbeat") if ($u > 0);
-
- # it was added to pending
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding beacon $ip:$beaconport with $gamename:$heartbeat to the pending list");
- return -1;
-}
-
-################################################################################
-## Add an address to the database that was obtained via a method other than
-## an udp beacon. Return 0,1,2 if success in adding or -1 on error.
-################################################################################
-sub add_pending {
- my ($self, $ip, $port, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "UPDATE serverlist
- SET updated = NOW()
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "updated serverlist with $ip:$port") if ($u > 0);
-
- # if updated, return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET added = NOW(),
- secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $secure, $ip, $port);
-
- # notify
- $self->log("update", "updated pending with $ip:$port") if ($u > 0);
-
- # return 1 if updated
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (
- ip,
- heartbeat,
- gamename,
- secure)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $secure);
-
- # notify
- $self->log("add", "$ip:$port added pending $gamename") if ($u > 0);
-
- # return 2 if added new
- return 2 if ($u > 0);
-
- # else
- return -1;
-}
-
-################################################################################
-## Remove an entry from the pending list. Returns 0 if removed or -1 in case
-## of error(s).
-################################################################################
-sub remove_pending {
- my ($self, $id) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "DELETE FROM pending
- WHERE id = ?",
- undef, $id);
-
- # notify
- $self->log("delete", "removed pending id $id from pending") if ($u > 0);
-
- # it was removed from pending
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "error deleting server $id from pending");
- return -1;
-}
-
-################################################################################
-## Get pending server by ip, beacon port. Returns * or undef
-################################################################################
-sub get_pending_beacon {
- my ($self, $ip, $port) = @_;
-
- # if address is in list, update the timestamp
- return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND beaconport = ?",
- undef, $ip, $port)->[0];
-}
-
-################################################################################
-## Get pending server by ip, heartbeat port. Returns * or undef
-################################################################################
-sub get_pending_info {
- my ($self, $ip, $port) = @_;
-
- # if address is in list, update the timestamp
- return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $ip, $port)->[0];
-}
-
-################################################################################
-## Get server info from any entry with an id higher than the provided one. The
-## server is added to pending at least 15 seconds ago. Returns info or undef.
-################################################################################
-sub get_next_pending {
- my ($self, $id) = @_;
-
- # get 1 pending id that is older than 15s
- return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, heartbeat, secure FROM pending
- WHERE added < NOW() - INTERVAL 15 SECOND
- AND id > ?
- ORDER BY id ASC LIMIT 1",
- undef, $id)->[0];
-}
-
-
-1;
diff --git a/lib/MasterServer/Database/mysql/dbCiphers.pm b/lib/MasterServer/Database/mysql/dbCiphers.pm
deleted file mode 100755
index 31db78b..0000000
--- a/lib/MasterServer/Database/mysql/dbCiphers.pm
+++ /dev/null
@@ -1,98 +0,0 @@
-
-package MasterServer::Database::mysql::dbCiphers;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| clear_ciphers
- insert_cipher
- get_cipher
- get_default_port |;
-
-################################################################################
-## Clear all existing ciphers from the database
-################################################################################
-sub clear_ciphers {
- my $self = shift;
-
- # delete ALL entries
- my $u = $self->{dbh}->do("DELETE FROM games");
-
- # notify
- $self->log("delete", "Removed all ciphers") if ($u > 0);
-
- # removed from games
- return 2 if ($u > 0);
-
- # or else report notice
- $self->log("notice", "No ciphers deleted!");
- return -1;
-
-}
-
-################################################################################
-## Insert the list of supported games and their ciphers / default ports /
-## descriptions included from the data/supportedgames.pl file.
-################################################################################
-sub insert_cipher {
- my ($self, %opt) = @_;
-
- # insert a single cipher/key combo
- my $u = $self->{dbh}->do(
- "INSERT INTO games (
- gamename,
- cipher,
- description,
- default_qport)
- VALUES(?, ?, ?, ?)", undef,
- $opt{gamename}, $opt{cipher}, $opt{description}, $opt{default_qport});
-
- # notify
- $self->log("add", "Added cipher for $opt{gamename}") if ($u and $u > 0);
- return 1 if ($u and $u > 0);
-
- # or else report error
- $self->log("error", "An error occurred adding a cipher for $opt{gamename}");
- return -1;
-
-}
-
-
-################################################################################
-## get the cipher that goes with gamename
-################################################################################
-sub get_cipher {
- my ($self, $gn) = @_;
-
- # no gamename specified? "undef" is not a known cipher, so send that instead.
- return 'undef' if !$gn;
-
- # get cipher from db if gamename exists
- my $cipher = $self->{dbh}->selectall_arrayref(
- 'SELECT cipher FROM games WHERE gamename = ?', undef,
- lc $gn)->[0]->[0];
-
- # return a non-zero-length string
- return ($cipher ? $cipher : 'undef');
-}
-
-################################################################################
-## get the default query port that goes with gamename
-################################################################################
-sub get_default_port {
- my ($self, $gn) = @_;
-
- # no gamename specified? default port is 0
- return 0 if !$gn;
-
- # get port from db if gamename exists
- my $p = $self->{dbh}->selectall_arrayref(
- 'SELECT default_qport FROM games WHERE gamename = ?', undef,
- lc $gn)->[0]->[0];
-
- # return port or zero
- return $p || 0;
-}
-
-1;
diff --git a/lib/MasterServer/Database/mysql/dbClientList.pm b/lib/MasterServer/Database/mysql/dbClientList.pm
deleted file mode 100755
index 898eb28..0000000
--- a/lib/MasterServer/Database/mysql/dbClientList.pm
+++ /dev/null
@@ -1,45 +0,0 @@
-
-package MasterServer::Database::mysql::dbClientList;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| get_gamenames
- get_game_list |;
-
-
-################################################################################
-## get a list of distinct gamenames currently in the database. it does not
-## matter whether they are recent or old, as long as the game is currently in
-## the database.
-##
-## returns: hashref of gamenames
-################################################################################
-sub get_gamenames {
- my $self = shift;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT distinct gamename
- FROM serverlist");
-}
-
-################################################################################
-## get the list of games of a certain $gamename, excluding the ones excempted
-## via the blacklist
-## only returns server addresses that are no more than 1 hours old
-################################################################################
-sub get_game_list {
- my ($self, $gamename) = @_;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT ip, port
- FROM serverlist
- WHERE updated > NOW() - INTERVAL 10800 SECOND
- AND gamename = ?
- AND NOT blacklisted",
- undef, lc $gamename);
-}
-
-
-1;
diff --git a/lib/MasterServer/Database/mysql/dbCore.pm b/lib/MasterServer/Database/mysql/dbCore.pm
deleted file mode 100755
index 30fe3ef..0000000
--- a/lib/MasterServer/Database/mysql/dbCore.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-
-package MasterServer::Database::mysql::dbCore;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| database_login |;
-
-################################################################################
-## login to the database with credentials provided in the config file.
-## returns dbh object or quits application on error.
-################################################################################
-sub database_login {
- my $self = shift;
-
- # create the dbi object
- my $dbh = DBI->connect(@{$self->{dblogin}}, {PrintError => $self->{db_print}});
-
- # verify that the database connected
- if (defined $dbh) {
-
- # log the event
- $self->log("load","Connected to the MySQL database.");
-
- # turn on error printing
- $dbh->{printerror} = 1;
-
- # return the dbi object for further use
- return $dbh;
- }
- else {
- # fatal error
- $self->log("fatal", "$DBI::errstr!");
-
- # end program
- $self->halt();
- }
-
- # unreachable
- return undef;
-}
-
-1;
diff --git a/lib/MasterServer/Database/mysql/dbServerlist.pm b/lib/MasterServer/Database/mysql/dbServerlist.pm
deleted file mode 100755
index 5bc1cd3..0000000
--- a/lib/MasterServer/Database/mysql/dbServerlist.pm
+++ /dev/null
@@ -1,153 +0,0 @@
-
-package MasterServer::Database::mysql::dbServerlist;
-
-use strict;
-use warnings;
-use Exporter 'import';
-
-our @EXPORT = qw| add_to_serverlist
- update_serverlist
- syncer_add
- get_next_server |;
-
-################################################################################
-## beacon was verified or otherwise accepted and will now be added to the
-## serverlist.
-################################################################################
-sub add_to_serverlist {
- my ($self, $ip, $port, $gamename) = @_;
-
- # update or add server to serverlist
- my $u = $self->{dbh}->do("UPDATE serverlist
- SET updated = NOW()
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "$ip:$port timestamp updated") if ($u > 0);
-
- # if found, updated; done
- return 0 if ($u > 0);
-
- # if not found, add it.
- $u = $self->{dbh}->do("INSERT INTO serverlist (ip, port, gamename, country)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $self->ip2country($ip));
-
- # notify
- $self->log("add", "$ip:$port added to serverlist") if ($u > 0);
-
- # return added
- return 1 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding server $ip:$port ($gamename) to the serverlist");
- return -1;
-}
-
-################################################################################
-## same as add_to_serverlist above, but does not add the server to serverlist
-## if it does not exist in serverlist. it must be added by another function
-## first.
-################################################################################
-sub update_serverlist {
- my ($self, $ip, $port, $s) = @_;
-
- # update server info
- my $u = $self->{dbh}->do(
- 'UPDATE serverlist
- SET updated = NOW(),
- gamename = ?,
- gamever = ?,
- hostname = ?,
- hostport = ?
- WHERE ip = ?
- AND port = ?', undef,
- $s->{gamename}, $s->{gamever}, $s->{hostname}, $s->{hostport},
- $ip, $port);
-
- # notify
- $self->log("update", "server $ip:$port info updated") if ($u > 0);
-
- # return 0 if updated
- return 0 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred updating server $ip:$port in the serverlist");
- return -1;
-}
-
-
-################################################################################
-## add new addresses to the pending list, but do not update timestamps. masters
-## that sync with each other would otherwise update the timestamp for a server
-## which is no longer online.
-################################################################################
-sub syncer_add {
- my ($self, $ip, $port, $gamename, $secure) = @_;
-
- # if address is in list, update the timestamp
- my $u = $self->{dbh}->do(
- "SELECT * FROM serverlist
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("read","syncer found entry for $ip:$port") if ($u > 0);
-
- # if found, return 0
- return 0 if ($u > 0);
-
- # if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
- "UPDATE pending
- SET secure = ?
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $secure, $ip, $port);
-
- # notify
- $self->log("update","$ip:$port was updated by syncer",
- $self->{log_settings}->{db_updated}) if ($u > 0);
-
- # return 1 if found
- return 1 if ($u > 0);
-
- # if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (ip, heartbeat, gamename, secure)
- SELECT ?, ?, ?, ?",
- undef, $ip, $port, $gamename, $secure);
-
- # notify
- $self->log("add","beacon: $ip:$port was added for $gamename after sync") if ($u > 0);
-
- # return 2 if added new
- return 2 if ($u > 0);
-
- # or else report error
- $self->log("error", "an error occurred adding $ip:$port after sync");
- return -1;
-}
-
-################################################################################
-## get a server address of the next server in line to be queried for game info.
-## query must be older than 30 seconds (in case it just got added) and not
-## older than 3 hours. FIXME: now older servers are ignored!
-################################################################################
-sub get_next_server {
- my ($self, $id) = @_;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, port FROM serverlist
- WHERE added < NOW() - INTERVAL 15 SECOND
- AND updated > NOW() - INTERVAL 10800 SECOND
- AND id > ?
- AND NOT blacklisted
- ORDER BY id ASC LIMIT 1", undef, $id)->[0];
-
-}
-
-1;
diff --git a/lib/MasterServer/TCP/BrowserHost.pm b/lib/MasterServer/TCP/BrowserHost.pm
index 3eb22eb..855b2c0 100755
--- a/lib/MasterServer/TCP/BrowserHost.pm
+++ b/lib/MasterServer/TCP/BrowserHost.pm
@@ -16,9 +16,6 @@ our @EXPORT = qw| browser_host clean_tcp_handle|;
################################################################################
sub browser_host {
my $self = shift;
-
- # log: TCP host is active
- $self->log("load","Loading TCP Browser Host.");
my $browser = tcp_server undef, $self->{listen_port}, sub {
my ($fh, $a, $p) = @_;
@@ -51,7 +48,7 @@ sub browser_host {
};
# startup of TCP server complete
- $self->log("load", "Listening for TCP connections on port $self->{listen_port}.");
+ $self->log("info", "Listening for TCP connections on port $self->{listen_port}.");
return $browser;
}
diff --git a/lib/MasterServer/TCP/Handler.pm b/lib/MasterServer/TCP/Handler.pm
index 2fb05f5..1a075bf 100755
--- a/lib/MasterServer/TCP/Handler.pm
+++ b/lib/MasterServer/TCP/Handler.pm
@@ -33,47 +33,53 @@ sub read_tcp_handle {
# allow multiple blocks to add to the response string
my $response = "";
+
+ # print debug values
+ $self->log("debug","$a:$p sent $rxbuf");
# replace empty values for the string "undef" and replace line endings from netcatters
# parse the received data and extrapolate all the query commands found
my %r = ();
$m =~ s/\\\\/\\undef\\/;
- $m =~ s/\n//;
+ $m =~ s/\\$/\\undef\\/;
$m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
# secure/validate challenge
# part 2: receive \gamename\ut\location\0\validate\$validate\final\
$val = $self->handle_validate(\%r, $h, $secure, $a, $p)
if (exists $r{validate} && !$val);
-
+
# about query
$response .= $self->handle_about($r{about}, $a, $p) if (exists $r{about});
# return address list
- # part 3: wait for the requested action: \list\gamename\ut\
+ # part 3: wait for the requested action: \list\\gamename\ut\
$self->handle_list($val, \%r, $c, $a, $p) if (exists $r{list} && exists $r{gamename});
# Sync request from another 333networks-based masterserver. Respond with list
# of requested games (or all games).
$self->handle_sync($val, \%r, $c, $a, $p) if (exists $r{sync});
+ #
+ # Support echo: doesn't do anything but print to log if not suppressed.
+ $self->log("echo","($a:$p): $r{echo}") if $r{echo};
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# improper syntax/protocol -- no valid commands found
# respond with an error.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
- if ($m =~ m/!(about|sync|validate|list)/) {
+ if ("about sync validate list" =~ m/\Q$response\E/i) {
# error message to client
$c->push_write("\\echo\\333networks did not understand your request. ".
"Contact us via 333networks.com\\final\\");
# and log it
- $self->log("error","invalid request from Browser $a:$p with unknown message \"$rxbuf\"", $self->{log_settings}->{handler_error});
+ $self->log("error","invalid request from Browser $a:$p with unknown message \"$rxbuf\".");
} # end if weird query
else {
$c->push_write($response . "\\final\\") if ($response ne "");
}
-
}
@@ -87,22 +93,28 @@ sub handle_validate {
# auth var init
my $val = 0;
-
+
# pass or fail the secure challenge
- if (exists $r->{gamename} && length $self->get_cipher(lc $r->{gamename}) > 1 ) {
+ if (exists $r->{gamename} && length $self->get_game_props(lc $r->{gamename})->{cipher} > 1 ) {
# game exists and we have the key to verify the response
- $val = $self->validated_request($r->{gamename}, $secure, $r->{enctype}, $r->{validate});
-
+ $val = $self->compare_challenge(
+ gamename => $r->{gamename},
+ secure => $secure,
+ enctype => $r->{enctype},
+ validate => $r->{validate},
+ ignore => $self->{ignore_browser_key},
+ );
+
# update for future queries
$self->{browser_clients}->{$h}[1] = $val;
}
elsif (exists $r->{gamename}) {
# log
- $self->log("support", "received unknown gamename request \"$r->{gamename}\" from $a:$p");
+ $self->log("support", "received unknown gamename request \"$r->{gamename}\" from $a:$p.");
}
- # log
- $self->log("secure","$a:$p validated with $val for $r->{gamename}, $secure, $r->{validate}");
+ # log (the spam!)
+ #$self->log("secure","$a:$p validated with $val for $r->{gamename}, $secure, $r->{validate}");
# return auth status
return $val;
@@ -124,47 +136,30 @@ sub handle_validate {
sub handle_about {
my ($self, $about, $a, $p) = @_;
my $response = "";
-
- #
+
# contact info
- #
if ($about =~ /^contact$/i or $about =~ /^undef$/i) {
- $response .= "\\about\\$self->{contact_details}";
-
- # log/print
+ $response .= "\\about\\$self->{masterserver_hostname}, contact: $self->{masterserver_contact}";
$self->log("about","communicating to $a:$p my contact information.");
}
- #
- # build info
- #
- if ($about =~ /^build$/i or $about =~ /^undef$/i) {
-
+ # build/version info
+ if ($about =~ /^build$/i or $about =~ /^version$/i or $about =~ /^undef$/i) {
$response .= "\\build\\$self->{build_type} $self->{build_version} written "
. "by $self->{build_author}, released $self->{build_date}";
-
- # log/print
$self->log("about","telling $a:$p my build info.");
}
- #
# address info
- #
if ($about =~ /^address$/i or $about =~ /^undef$/i) {
-
$response .= "\\address\\$self->{masterserver_address}"
. "\\listen_port\\$self->{listen_port}"
. "\\beacon_port\\$self->{beacon_port}";
-
- # log/print
$self->log("about","telling $a:$p my address/config info.");
}
- #
# support info
- #
if ($about =~ /^support$/i or $about =~ /^undef$/i) {
-
# string games in database
my $sg = $self->get_gamenames();
my $sgs = "";
@@ -172,13 +167,15 @@ sub handle_about {
$sgs .= " " if (length $sgs > 0);
$sgs .= $_->[0];
}
-
- # print response
$response .= "\\support\\$sgs";
-
- #log/print
$self->log("about","telling $a:$p which games are supported.");
}
+
+ # unsupported query
+ if ("contact build address support version undef" !~ m/$about/i) {
+ $response .= "\\echo\\incorrect query usage, supported queries are: contact build version address support.";
+ $self->log("about","incorrect query \"$about\", telling $a:$p the supported \"about\" queries.");
+ }
# return response string
return $response;
@@ -200,7 +197,7 @@ sub handle_list {
my $data = "";
# determine the return format
- if ($self->{hex_format} =~ m/$r->{gamename}/i or $r->{gamename} =~ /^cmp$/i) {
+ if ($self->{hex_format} =~ m/$r->{gamename}/i or $r->{list} =~ /^cmp$/i) {
# return addresses as byte format (ip=ABCD port=EF)
$data .= $self->compile_list_cmp($r->{gamename});
}
@@ -215,7 +212,7 @@ sub handle_list {
# immediately send to client
$c->push_write($data);
- # log successful (debug)
+ # log successful
$self->log("list","$a:$p successfully retrieved the list for $r->{gamename}.");
# clean and close the connection
@@ -244,7 +241,7 @@ sub handle_sync {
my ($self, $val, $r, $c, $a, $p) = @_;
# alternate part 3: wait for the requested action: \sync\(all|list of games)\sender\domainname
- $self->log("tcp","Sync request from $a:$p found");
+ $self->log("tcp","Sync request from $a:$p found.");
if ($val && exists $r->{sync}) {
@@ -256,8 +253,7 @@ sub handle_sync {
$c->push_write($data);
# log successful (debug)
- if (exists $r->{sender}) {$self->log("sync","$r->{sender} successfully synced.");}
- else {$self->log("sync","$a:$p successfully synced.");}
+ $self->log("sync-tx","$a:$p successfully synced.");
# clean and close the connection
$self->clean_tcp_handle($c);
diff --git a/lib/MasterServer/TCP/ListCompiler.pm b/lib/MasterServer/TCP/ListCompiler.pm
index 1863757..a5571d0 100755
--- a/lib/MasterServer/TCP/ListCompiler.pm
+++ b/lib/MasterServer/TCP/ListCompiler.pm
@@ -3,6 +3,7 @@ package MasterServer::TCP::ListCompiler;
use strict;
use warnings;
+
use Exporter 'import';
our @EXPORT = qw| compile_list compile_list_cmp compile_sync |;
@@ -15,7 +16,10 @@ sub compile_list {
my ($self, $gamename) = @_;
# get the list from database
- my $serverlist = $self->get_game_list($gamename);
+ my $serverlist = $self->get_server(
+ updated => 3600,
+ gamename => $gamename,
+ );
# prepare empty return string
my $response_string = "";
@@ -24,9 +28,9 @@ sub compile_list {
for (@{$serverlist}){
# append \ip\ip:port to string
- $response_string .= "\\ip\\$_->[0]:$_->[1]";
+ $response_string .= "\\ip\\$_->{ip}:$_->{port}";
}
-
+
# return the string with data
return $response_string;
}
@@ -39,7 +43,10 @@ sub compile_list_cmp {
my ($self, $gamename) = @_;
# get the list from database
- my $serverlist = $self->get_game_list($gamename);
+ my $serverlist = $self->get_server(
+ updated => 3600,
+ gamename => $gamename,
+ );
# prepare empty return string
my $response_string = "";
@@ -48,8 +55,8 @@ sub compile_list_cmp {
for (@{$serverlist}){
# convert ip address to ABCDEF mode
- my ($A, $B, $C, $D) = ($_->[0] =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/);
- my ($E, $F) = ($_->[1] >> 8, $_->[1] & 0xFF);
+ my ($A, $B, $C, $D) = ($_->{ip} =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/);
+ my ($E, $F) = ($_->{port} >> 8, $_->{port} & 0xFF);
# print as chr string of 6 bytes long
my $bin = ""; $bin .= (chr $A) . (chr $B) . (chr $C) . (chr $D) . (chr $E) . (chr $F);
@@ -87,20 +94,23 @@ sub compile_sync {
}
# only get unique values from array
- @games = map { $_ => 1 } @games;
+ my %games = map { $_ => 1 } @games;
# get the list for every requested gamename
- for my $g (@games) {
-
+ for my $g (keys %games) {
+
# $g is now a gamename -- check if it's supported. Else ignore.
- if (length $self->get_cipher(lc $g) > 1) {
+ if ($self->get_game_props($g)) {
# get list from database
- my $list = $self->get_game_list($g);
+ my $list = $self->get_server(
+ updated => 7200,
+ gamename => $g,
+ );
# add all games to string separated by spaces
my $gamestring = "";
- foreach $_ (@{$list}) {$gamestring .= "$_->[0]:$_->[1] ";}
+ foreach $_ (@{$list}) {$gamestring .= "$_->{ip}:$_->{port} ";}
# if it contains at least one entry, add the list to the response list
$response_string .= "\\$g\\$gamestring" if (length $gamestring >= 7);
diff --git a/lib/MasterServer/TCP/Syncer.pm b/lib/MasterServer/TCP/Syncer.pm
index 2ba52ca..bfe3d78 100755
--- a/lib/MasterServer/TCP/Syncer.pm
+++ b/lib/MasterServer/TCP/Syncer.pm
@@ -7,39 +7,9 @@ use AnyEvent;
use AnyEvent::Handle;
use Exporter 'import';
-our @EXPORT = qw| syncer_scheduler sync_with_master process_sync_list|;
-
-################################################################################
-## Syncer Scheduler
-## Request the masterlist for selected or all games from other
-## 333networks-based masterservers.
-################################################################################
-sub syncer_scheduler {
- my $self = shift;
-
- # log active
- $self->log("load", "Synchronisation module active.");
-
- # go through the list of provided addresses
- my $i = 0;
- return AnyEvent->timer (
- after => $self->{sync_time}[0],
- interval => $self->{sync_time}[1],
- cb => sub {
- # check if there's a master server entry to be synced. If not, return
- # to zero and go all over again.
- $i = 0 unless $self->{sync_masters}[$i];
- return if (!defined $self->{sync_masters}[$i]);
-
- # synchronze with master $i
- $self->log("tcp", "Attempting to synchronize with $self->{sync_masters}[$i]->{address}");
- $self->sync_with_master($self->{sync_masters}[$i]);
-
- #increment counter
- $i++;
- }
- );
-}
+our @EXPORT = qw| sync_with_master
+ process_sync_list
+ masterserver_list |;
################################################################################
## Sends synchronization request to another 333networks based master server and
@@ -47,6 +17,9 @@ sub syncer_scheduler {
################################################################################
sub sync_with_master {
my ($self, $ms) = @_;
+
+ # announce
+ $self->log("tcp", "Attempting to synchronize with $ms->{ip}");
# list to store all IPs in.
my $sync_list = "";
@@ -54,11 +27,11 @@ sub sync_with_master {
# connection handle
my $handle;
$handle = new AnyEvent::Handle(
- connect => [$ms->{address} => $ms->{port}],
- timeout => 3,
+ connect => [$ms->{ip} => $ms->{tcp}],
+ timeout => 4,
poll => 'r',
- on_error => sub {$self->log("error","$! on $ms->{address} $ms->{port}"); $handle->destroy;},
- on_eof => sub {$self->process_sync_list($sync_list, $ms); $handle->destroy;},
+ on_error => sub {$self->log("error","$! on $ms->{ip} $ms->{tcp}"); $handle->destroy;},
+ on_eof => sub {$self->process_sync_list($sync_list, $ms); $handle->destroy;},
on_read => sub {
# receive and clear buffer
my $m = $_[0]->rbuf;
@@ -78,13 +51,20 @@ sub sync_with_master {
$m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
# respond to the validate challenge
- my $validate = $self->validate_string("333networks", $r{secure}, $r{enctype});
+ my $validate = $self->validate_string(
+ gamename => "333networks",
+ secure => $r{secure},
+ enctype => $r{enctype}
+ );
# part 2: send \gamename\ut\location\0\validate\$validate\final\
$handle->push_write("\\gamename\\333networks\\location\\0\\validate\\$validate\\final\\");
# part 3: request the list \sync\gamenames consisting of space-seperated game names or "all"
- my $request = "\\sender\\$self->{masterserver_address}\\sync\\".(($self->{sync_games}[0] == 0) ? "all" : $self->{sync_games}[1])."\\final\\";
+ # compatibility note: old queries use "new", instead treat them as "all".
+ my $request = "\\sync\\"
+ . (($self->{sync_games}[0] == 0) ? ("all" or "new") : $self->{sync_games}[1])
+ . "\\final\\";
# push the request to remote host
$handle->push_write($request);
@@ -119,7 +99,7 @@ sub process_sync_list {
if (exists $r{echo}) {
# remote address says...
- $self->log("error", "$ms->{address} replied: $r{echo}");
+ $self->log("error", "$ms->{ip} replied: $r{echo}");
}
@@ -152,7 +132,7 @@ sub process_sync_list {
}
else {
# invalid address, log
- $self->log("error", "invalid address found while syncing at $ms->{address}: $l!");
+ $self->log("error", "invalid address found while syncing at $ms->{ip}: $l!");
}
} # endif ($l =~ /:/)
@@ -165,7 +145,41 @@ sub process_sync_list {
} # end while
# end message
- $self->log("sync", "received $c addresses after syncing from $ms->{address}");
+ $self->log("sync-rx", "received $c addresses after syncing from $ms->{ip}:$ms->{tcp}");
}
-
+
+################################################################################
+## Determine a list of all unique 333networks-compatible masterservers
+## and return this list. Join the brotherhood!
+################################################################################
+sub masterserver_list {
+ my $self = shift;
+ my %brotherhood;
+
+ # start with the masterservers defined in our configuration file
+ for my $ms (@{$self->{sync_masters}}) {
+ my $ip = $self->host2ip($ms->{address});
+ $brotherhood{"$ip:$ms->{port}"} = {ip => $ip, tcp => $ms->{port}, udp => $ms->{beacon}} if $ip;
+ }
+
+ # get the list of uplinking masterservers
+ my $serverlist = $self->get_server(
+ updated => 3600,
+ gamename => "333networks",
+ limit => 50, # more would be ridiculous.. right?..
+ );
+
+ # overwrite existing entries, add new
+ for my $ms (@{$serverlist}) {
+ $brotherhood{"$ms->{ip}:$ms->{hostport}"} = {ip => $ms->{ip}, tcp => $ms->{hostport}, udp => $ms->{port}};
+ }
+
+ # masterservers that sync with us can not be derived directly, but by reading
+ # the server log we can add them manually. Lot of work, little gain, as those
+ # syncing masterservers will most likely be uplinking as well between now and
+ # a few weeks/months.
+
+ return \%brotherhood;
+}
+
1;
diff --git a/lib/MasterServer/UDP/UCCAppletQuery.pm b/lib/MasterServer/TCP/UCCAppletQuery.pm
index f92c20c..e3eb587 100755
--- a/lib/MasterServer/UDP/UCCAppletQuery.pm
+++ b/lib/MasterServer/TCP/UCCAppletQuery.pm
@@ -1,5 +1,5 @@
-package MasterServer::UDP::UCCAppletQuery;
+package MasterServer::TCP::UCCAppletQuery;
use strict;
use warnings;
@@ -7,34 +7,7 @@ use AnyEvent;
use AnyEvent::Handle;
use Exporter 'import';
-our @EXPORT = qw| ucc_applet_query_scheduler query_applet |;
-
-################################################################################
-## Query Epic Games'-based UCC applets periodically to get an additional
-## list of online UT, Unreal (or other) game servers.
-################################################################################
-sub ucc_applet_query_scheduler {
- my $self = shift;
- $self->log("load", "UCC Applet Query Scheduler is loaded.");
-
- my $i = 0;
- return AnyEvent->timer (
- after => $self->{master_applet_time}[0],
- interval => $self->{master_applet_time}[1],
- cb => sub {
- # check if there's a master server entry to be queried. If not, return
- # to zero and go all over again.
- $i = 0 unless $self->{master_applet}[$i];
- return if (!defined $self->{master_applet}[$i]);
-
- # perform the query
- $self->query_applet($self->{master_applet}[$i]);
-
- #increment counter
- $i++;
- }
- );
-}
+our @EXPORT = qw| query_applet |;
################################################################################
## The UCC Applet (Epic Megagames, Inc.) functions as a master server for one
@@ -45,7 +18,7 @@ sub query_applet {
my ($self, $ms) = @_;
# be nice to notify
- $self->log("query","start querying $ms->{ip}:$ms->{port} for '$ms->{game}' games");
+ $self->log("tcp","start querying $ms->{ip}:$ms->{port} for '$ms->{game}' games");
# list to store all IPs in.
my $master_list = "";
@@ -69,9 +42,22 @@ sub query_applet {
# part 1: receive \basic\\secure\$key
if ($m =~ m/\\basic\\\\secure\\/) {
- # skip to part 3: also request the list \list\gamename\ut -- skipped in UCC applets
- #$handle->push_write("\\list\\\\gamename\\$ms->{game}");
- $handle->push_write("\\list\\");
+
+ # received data
+ my %r;
+ $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
+
+ # respond to challenge
+ my $validate = $self->validate_string(gamename => $ms->{game},
+ enctype => $r{enctype}||0,
+ secure => $r{secure});
+
+ # send response
+ $handle->push_write("\\gamename\\$ms->{game}\\location\\0\\validate\\$validate\\final\\");
+
+ # part 3: also request the list \list\gamename\ut -- skipped in UCC applets
+ $handle->push_write("\\list\\\\gamename\\$ms->{game}\\final\\");
+
}
# part 3b: receive the entire list in multiple steps.
diff --git a/lib/MasterServer/UDP/BeaconCatcher.pm b/lib/MasterServer/UDP/BeaconCatcher.pm
index 0d5ce81..06fd38a 100755
--- a/lib/MasterServer/UDP/BeaconCatcher.pm
+++ b/lib/MasterServer/UDP/BeaconCatcher.pm
@@ -16,8 +16,8 @@ our @EXPORT = qw| beacon_catcher on_beacon_receive|;
sub beacon_catcher {
my $self = shift;
- # module startup log
- $self->log("load","Loading UDP Beacon Catcher.");
+ # display that the server is up and listening for beacons
+ $self->log("info", "Listening for UDP beacons on port $self->{beacon_port}.");
# UDP server
my $udp_server;
@@ -30,9 +30,6 @@ sub beacon_catcher {
on_recv => sub {$self->on_beacon_receive(@_)},
);
- # display that the server is up and listening for beacons
- $self->log("info", "Listening for UT Beacons on port $self->{beacon_port}.");
-
# allow object to exist beyond this scope. Objects have ambitions too.
return $udp_server;
}
@@ -57,14 +54,18 @@ sub on_beacon_receive {
# truncate and try to continue
$b = substr $b, 0, 64;
}
-
+
# if a heartbeat format was detected...
$self->process_udp_beacon($udp, $pa, $b, $peer_addr, $port)
if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/);
-
+
# or if this is a secure response, verify the response
$self->process_udp_validate($b, $peer_addr, $port, undef)
if ($b =~ m/\\validate\\/);
+
+ # or if other masterservers check if we're still alive
+ $self->process_udp_basic($udp, $pa, $b, $peer_addr)
+ if ($b =~ m/\\basic\\/ || $b =~ m/\\status\\/ || $b =~ m/\\info\\/);
}
1;
diff --git a/lib/MasterServer/UDP/BeaconChecker.pm b/lib/MasterServer/UDP/BeaconChecker.pm
index a99be72..f74378d 100755
--- a/lib/MasterServer/UDP/BeaconChecker.pm
+++ b/lib/MasterServer/UDP/BeaconChecker.pm
@@ -6,93 +6,7 @@ use warnings;
use AnyEvent::Handle::UDP;
use Exporter 'import';
-our @EXPORT = qw| beacon_checker query_udp_server|;
-
-################################################################################
-## When addresses are stored in the 'pending' list, they are supposed to be
-## queried immediately with the secure/validate challenge to testify that
-## the server is genuine and alive.
-##
-## Some servers do not support the secure-challenge on the Uplink port. These
-## servers are verified with a secure-challenge on their heartbeat ports,
-## which are designed to respond to secure queries, as well as status queries.
-##
-## Addresses collected by other scripts, whether from the UCC applet or manual
-## input via the website, are added to the pending list. It is more
-## important to verify pending beacons and new server addresses, than to
-## update the status of existing addresses. Therefore, pending addresses are
-## prioritized.
-################################################################################
-sub beacon_checker {
- my $self = shift;
- $self->log("load", "UDP Beacon Checker is loaded.");
-
- # queue -- which address is next in line?
- my %q = ( pending_id => 0, server_id => 0,
- start_time => time+$self->{beacon_checker_time}[0]-1); #time+grace
-
- # go through all servers one by one, new and old
- my $server_info = AnyEvent->timer (
- after => $self->{beacon_checker_time}[0],
- interval => $self->{beacon_checker_time}[1],
- cb => sub {
-
- # first of all, check whether we exceeded our time cap limit
- if ( (time - $q{start_time}) >= $self->{beacon_checker_time}[2] ){
- # reset queue variables
- $q{pending_id} = 0;
- $q{server_id} = 0;
- $q{start_time} = time;
- }
-
- # See if there are pending servers, and use existing secure string for
- # the challenge.
- my $n = $self->get_next_pending($q{pending_id});
-
- # if any entries were found, proceed
- if ( $n->[0] ) {
-
- # next pending id will be > $n
- $q{pending_id} = $n->[0];
-
- # query the server
- $self->query_udp_server($n->[1], $n->[2], $n->[3]);
-
- # work done. Wait for the next round for the next timer tick.
- return;
- }
-
- # if no pending servers left, update the other entries
- $n = $self->get_next_server($q{server_id});
-
- # if any entries were found, proceed
- if ( $n->[0] ) {
-
- # next server id will be > $n
- $q{server_id} = $n->[0];
-
- # query the server (no secure string)
- $self->query_udp_server($n->[1], $n->[2], "");
-
- # work done. Wait for the next round for the next task.
- return;
- }
-
- # At this point, we are out of server entries. When new servers are
- # added, they are immediately queried on the next round.
- # From here on, just count down until the cycle is complete and handle
- # new entries while they are added to the list.
-
- }
- );
-
- # at the start of the module, remind host how often this happens
- $self->log("info", "Verifying servers every $self->{beacon_checker_time}[2] seconds.");
-
- # return the timer object to keep it alive outside of this scope
- return $server_info;
-}
-
+our @EXPORT = qw| query_udp_server|;
################################################################################
## Get the server status from any server over UDP and store the received
@@ -100,11 +14,11 @@ sub beacon_checker {
## secure/pending or information.
################################################################################
sub query_udp_server {
- my ($self, $ip, $port, $secure) = @_;
+ my ($self, $id, $ip, $port, $secure, $message_type) = @_;
my $buf = "";
# debug spamming
- $self->log("udp", "Query server $ip:$port");
+ $self->log("udp", "Query server $id ($ip:$port)");
# connect with UDP server
my $udp_client; $udp_client = AnyEvent::Handle::UDP->new(
@@ -118,40 +32,51 @@ sub query_udp_server {
# add packet to buffer
$buf .= $_[0];
+ # message type 0: \basic\\info\
+ # if gamename, ver, hostname and hostport are available, but NOT the value
+ # "listenserver", it would have been \basic\info
+ if ($buf =~ m/\\gamename\\/ &&
+ $buf =~ m/\\hostname\\/ &&
+ $buf =~ m/\\hostport\\/ &&
+ $buf !~ m/\\listenserver\\/ ) {
+ $self->process_query_response($buf, $ip, $port);
+ }
+
+ # message type 1: \basic\\secure\wookie
# if validate, assume that we sent a \basic\secure request.
if ($buf =~ m/\\validate\\/){
$self->process_udp_validate($buf, $ip, undef, $port);
}
- # if gamename, ver, hostname and hostport are available, it should
- # have been \basic\info
- elsif ($buf =~ m/\\gamename\\/ && $buf =~ m/\\gamever\\/
- && $buf =~ m/\\hostname\\/ && $buf =~ m/\\hostport\\/) {
- $self->process_query_response($buf, $ip, $port);
+
+ # message type 2: \status\
+ # contains same info as \basic\\info, but also "listenserver". Only for UT.
+ if ($buf =~ m/\\gamename\\ut/ &&
+ $buf =~ m/\\hostname\\/ &&
+ $buf =~ m/\\hostport\\/ &&
+ $buf =~ m/\\listenserver\\/ ) {
+ $self->process_status_response($buf, $ip, $port);
}
+
# else partial information received. wait for more.
- else{ }
+ # else { }
},
);
#
# Send secure message or status, depending on provided variables
- #
+ # Message types can be
+ # 0: \basic\\info\
+ # 1: \basic\\secure\wookie
+ # 2: \status\
+ #
- # secure servers enabled and secure key provided
- if ($secure ne "" && $self->{require_secure_beacons} > 0) {
- # send secure
- $udp_client->push_send("\\basic\\\\secure\\$secure");
-
- # and log that we sent it
- $self->log("udp", "sending secure=\"$secure\" to $ip:$port");
- }
- else {
- # send information request
- $udp_client->push_send("\\basic\\\\info\\");
-
- # and log that we sent it
- $self->log("udp","sending basic request to $ip:$port");
- }
+ # determine the message
+ my $message = "\\basic\\\\info\\"; # default 0
+ $message = "\\basic\\\\secure\\$secure" if ($secure ne "" && $self->{require_secure_beacons} > 0); # message_type 1
+ $message = "\\status\\" if ($message_type == 2);
+
+ # send selected message
+ $udp_client->push_send($message);
}
1;
diff --git a/lib/MasterServer/UDP/DatagramProcessor.pm b/lib/MasterServer/UDP/DatagramProcessor.pm
index f80f6fb..863512c 100755
--- a/lib/MasterServer/UDP/DatagramProcessor.pm
+++ b/lib/MasterServer/UDP/DatagramProcessor.pm
@@ -1,4 +1,3 @@
-
package MasterServer::UDP::DatagramProcessor;
use strict;
@@ -10,6 +9,7 @@ use Exporter 'import';
our @EXPORT = qw| process_udp_beacon
process_udp_validate
process_query_response
+ process_status_response
process_ucc_applet_query |;
################################################################################
@@ -23,8 +23,10 @@ sub process_udp_beacon {
# received heartbeat in $buf: \heartbeat\7778\gamename\ut\
my %r;
+ my $raw = $buf; # raw buffer for logging if necessary
$buf = encode('UTF-8', $buf);
$buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
+
# check whether the beacon has a gamename
if (defined $r{gamename}) {
@@ -32,19 +34,37 @@ sub process_udp_beacon {
$self->log("beacon", "$peer_addr:$r{heartbeat} for $r{gamename}");
# some games (like bcommander) have a default port and don't send a heartbeat port.
- $r{heartbeat} = $self->get_default_port($r{gamename}) if ($r{heartbeat} == 0);
+ $r{heartbeat} = $self->get_game_props($r{gamename})->{heartbeat} if ($r{heartbeat} == 0);
#
# verify valid server address (ip+port)
if ($self->valid_address($peer_addr,$r{heartbeat})) {
+
+ # check if the entry already was not added within the last 5 seconds, throttle otherwise
+ my $throttle = $self->get_pending(
+ ip => $peer_addr,
+ heartbeat => $r{heartbeat},
+ gamename => $r{gamename},
+ after => 5,
+ sort => "added",
+ limit => 1
+ )->[0];
+ return if (defined $throttle);
# generate a new secure string
my $secure = $self->secure_string();
# update beacon in serverlist if it already exists, otherwise update
# or add to pending with new secure string.
- my $auth = $self->add_beacon($peer_addr, $port, $r{heartbeat}, $r{gamename}, $secure);
-
+ my $auth = $self->add_server_new(ip => $peer_addr,
+ beaconport => $port,
+ heartbeat => $r{heartbeat},
+ gamename => $r{gamename},
+ secure => $secure,
+ direct => 1,
+ updated => time,
+ beacon => time);
+
# send secure string back
if ($auth > 0) {
@@ -59,14 +79,21 @@ sub process_udp_beacon {
# invalid ip+port combination, like \heartbeat\0\ or local IP
else {
# Log that beacon had incorrect information, such as port 0 or so. Spams log!
- $self->log("invalid","$peer_addr:$r{heartbeat} ($r{heartbeat}) had bad information");
+ $self->log("invalid","$peer_addr had bad information --> $raw");
}
}
- # gamename not valid or not found in supportedgames.pl
+ # gamename not valid or recognized, display raw buffer in case data could not
+ # be extrapolated from the heartbeat
else {
# log
- $self->log("support", "received unknown beacon \"$r{gamename}\" from $peer_addr:$r{heartbeat}");
+ $self->log("support", "received unknown beacon from $peer_addr --> $raw");
+ #
+ # TODO: more practical way to log this to the database: new table
+ # named "unsupported" where messages are logged by ip, port, gamename (if
+ # applicable) and TEXT raw message.
+ #
+
}
}
@@ -78,51 +105,83 @@ sub process_udp_validate {
# $self, udp data, ip, port
my ($self, $buf, $peer_addr, $port, $heartbeat) = @_;
+ # debug spamming
+ # $self->log("udp", "Received response from $peer_addr:$heartbeat, sent |$buf|");
+
# received heartbeat in $b: \validate\string\queryid\99.9\
my %r;
$buf = encode('UTF-8', $buf);
$buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
# get our existing knowledge about this server from the database
- # if the heartbeat/queryport known? then use that instead as beacon ports --> may vary after server restarts!
- my $pending = (defined $heartbeat)
- ? $self->get_pending_info($peer_addr, $heartbeat)
- : $self->get_pending_beacon($peer_addr, $port);
+ my $pending = $self->get_pending(
+ ip => $peer_addr,
+ limit => 1,
+ ($heartbeat ? (heartbeat => $heartbeat) : () ),
+ ($port ? (beaconport => $port) : () ),
+ )->[0];
- # if indeed in the pending list, check -- if this entry is not (longer) in the list, it
+ # if indeed in the pending list, check; -- if this entry is not (longer) in the list, it
# was either removed by the BeaconChecker or cleaned out in maintenance (after X hours).
if (defined $pending) {
#determine if it uses any enctype
my $enc = (defined $r{enctype}) ? $r{enctype} : 0;
- # database may not contain the correct gamename (ucc applet, incomplete beacon, change of gameserver)
- $pending->[4] = (defined $r{gamename}) ? $r{gamename} : $pending->[4];
+ # database may not contain the correct gamename (ucc applet, incomplete beacon, other game)
+ $pending->{gamename} = $r{gamename} if (defined $r{gamename});
- # verify challenge gamename secure enctype validate_response
- my $val = $self->validated_beacon($pending->[4], $pending->[5], $enc, $r{validate});
-
- # log challenge results ($port may not have been provided)
- $port = (defined $port) ? $port : $heartbeat;
- $self->log("secure", "$peer_addr:$port validated with $val for $pending->[4]");
+ # verify challenge
+ my $val = $self->compare_challenge(
+ gamename => $pending->{gamename},
+ secure => $pending->{secure},
+ enctype => $r{enctype},
+ validate => $r{validate},
+ ignore => $self->{ignore_beacon_key},
+ );
- # if validated, add to db
- if ($val > 0) {
+ # if validated, add server to database
+ if ($val > 0 || $self->{require_secure_beacons} == 0) {
- # successfully added? ip, query port, gamename
- my $sa = $self->add_to_serverlist($pending->[1], $pending->[3], $pending->[4]);
+ # select server from serverlist -- should not exist yet.
+ my $srv = $self->get_server(ip => $pending->{ip}, port => $pending->{heartbeat})->[0];
- # remove the entry from pending if successfully added
- $self->remove_pending($pending->[0]) if ( $sa >= 0);
+ # was found, then update gamename and remove from pending
+ if (defined $srv) {
+ my $sa = $self->update_server_list(
+ ip => $pending->{ip},
+ port => $pending->{heartbeat},
+ gamename => $pending->{gamename}
+ );
+ # remove the entry from pending if successfully added
+ $self->remove_pending($pending->{id}) if ( $sa >= 0);
+ }
+ # was not found, insert clean and remove from pending
+ else {
+ my $sa = $self->add_server_list(
+ ip => $pending->{ip},
+ port => $pending->{heartbeat},
+ gamename => $pending->{gamename}
+ );
+ # remove the entry from pending if successfully added
+ $self->remove_pending($pending->{id}) if ( $sa > 0);
+ }
}
else {
- # else failed validation
- $self->log("error","beacon $peer_addr:$port failed validation for $pending->[4] (details: $pending->[5] sent, got $r{validate})");
+ # else failed validation
+ # calculate expected result for log
+ my $validate_string = $self->validate_string(
+ gamename => $pending->{gamename},
+ secure => $pending->{secure}
+ );
+ $self->log("secure","$pending->{id} for $pending->{gamename} sent: $pending->{secure}, got $r{validate}, expected $validate_string");
}
}
+ # if no entry found in pending list
else {
- # else failed validation
- $self->log("error","server not found in pending for $peer_addr and unknown heartbeat/port");
+ # 404 not found
+ $self->log("error","server not found in pending for $peer_addr:",
+ ($heartbeat ? $heartbeat : "" ), ($port ? $port : "" ), " !");
}
}
@@ -140,37 +199,116 @@ sub process_query_response {
$buf =~ s/\\([^\\]+)\\([^\\]+)/$s{$1}=$2/eg;
# check whether the gamename is supported in our db
- if (defined $s{gamename} && length $self->get_cipher(lc $s{gamename}) > 1) {
+ if (defined $s{gamename} &&
+ length $self->get_game_props($s{gamename})->{cipher} > 1) {
# parse variables
my %nfo = ();
-
- $nfo{gamename} = lc $s{gamename};
$nfo{gamever} = exists $s{gamever} ? $s{gamever} : "";
$nfo{hostname} = exists $s{hostname} ? $s{hostname} : "$ip:$port";
$nfo{hostport} = exists $s{hostport} ? $s{hostport} : 0;
- # some mor0ns have values longer than 100 characters
- $nfo{hostname} = substr $nfo{hostname}, 0, 99 if (length $nfo{hostname} >= 99);
+ # some mor0ns have hostnames longer than 200 characters
+ $nfo{hostname} = substr $nfo{hostname}, 0, 199 if (length $nfo{hostname} >= 199);
# log results
- $self->log("hostname", "$ip:$port\t is now known as\t $nfo{hostname}");
+ $self->log("hostname", "$ip:$port is now known as $nfo{hostname}");
- # if only validated servers are allowed in the list
- if ($self->{require_secure_beacons} > 0) {
- # only update in database
- $self->update_serverlist($ip, $port, \%nfo);
- }
- # otherwise also add the server to serverlist if required
- else{
- # add to serverlist and update anyway
- $self->add_to_serverlist($ip, $port, $nfo{gamename});
- $self->update_serverlist($ip, $port, \%nfo);
+ # add or update in serverlist (assuming validation is complete)
+ $self->update_server_list(
+ ip => $ip,
+ port => $port,
+ gamename => $s{gamename},
+ %nfo);
+
+ # if address is in pending list, remove it
+ my $pen = $self->get_pending(ip => $ip, heartbeat => $port)->[0];
+ $self->remove_pending($pen->{id}) if $pen;
+ }
+}
+
+################################################################################
+## Process status data that was obtained with \status\ from the
+## UT serverstats checker module.
+################################################################################
+sub process_status_response {
+ # $self, udp data, ip, port
+ my ($self, $buf, $ip, $port) = @_;
+
+ #process datastream
+ my %s;
+ $buf = encode('UTF-8', $buf);
+ $buf =~ s/\\([^\\]+)\\([^\\]+)/$s{$1}=$2/eg;
+
+ # check whether this server is in our database
+ my $serverlist_id = $self->get_server(ip => $ip, port => $port)->[0];
+
+ # only allow servers that were approved/past pending
+ if (defined $serverlist_id) {
+
+ #
+ # pre-process variables before putting them in the db
+ #
+
+ # gamename should in all cases be "ut" (we only allow this for UT games at the moment!)
+ return if (!defined $s{gamename} || $s{gamename} ne "ut");
+
+ # some people trying to sneak their Unreal servers into the UT serverlist
+ return if (!defined $s{gamever} || $s{gamever} eq "227i");
+
+ # some sanity checks for the presentation
+ $s{hostname} = substr $s{hostname}, 0, 199 if ($s{hostname} && length $s{hostname} >= 199);
+ $s{mapname} = substr $s{mapname}, 0, 99 if ($s{mapname} && length $s{mapname} >= 99);
+ $s{maptitle} = substr $s{maptitle}, 0, 99 if ($s{maptitle} && length $s{maptitle} >= 99);
+
+ #
+ # Store info in database
+ #
+
+ # check if the ID already exists in the database
+ my $utserver_id = $self->get_utserver(id => $serverlist_id->{id})->[0];
+
+ # add and/or update
+ $self->add_utserver($ip, $port) if (not defined $utserver_id);
+ $self->update_utserver($serverlist_id->{id}, %s);
+
+ #
+ # Player info
+ #
+
+ # delete all players for this server.
+ $self->delete_utplayers($serverlist_id->{id});
+
+ # iterate through all player IDs and add them to the database
+ for (my $i = 0; exists $s{"player_$i"}; $i++) {
- # if address is in pending list, remove it
- my $pending = $self->get_pending_info($ip, $port);
- $self->remove_pending($pending->[0]) if $pending;
+ # shorten name (some people might be overcompensating their names)
+ $s{"player_$i"} = substr $s{"player_$i"}, 0, 39 if (length $s{"player_$i"} > 39);
+
+ my %player = ();
+ $player{player} = exists $s{"player_$i"} ? $s{"player_$i"} : "Player";
+ $player{team} = exists $s{"team_$i"} ? $s{"team_$i"} : 255;
+ $player{team} = ($player{team} =~ m/^[0-3]/ ) ? int($player{team}) : 255;
+ $player{frags} = exists $s{"frags_$i"} ? int($s{"frags_$i"}) : 0;
+ $player{mesh} = exists $s{"mesh_$i"} ? $s{"mesh_$i"} : "";
+ $player{skin} = exists $s{"skin_$i"} ? $s{"skin_$i"} : "";
+ $player{face} = exists $s{"face_$i"} ? $s{"face_$i"} : "";
+ $player{ping} = exists $s{"ping_$i"} ? int($s{"ping_$i"}) : 0;
+ $player{ngsecret} = exists $s{"ngsecret_$i"} ? $s{"ngsecret_$i"} : ""; # contains bot info
+
+ # write to db
+ $self->insert_utplayer($serverlist_id->{id}, %player);
}
+
+ #
+ # Prevent null concatenation in logging
+ $s{numplayers} ||= 0;
+ $s{maxplayers} ||= 0;
+ $s{mapname} ||= "Unknown map";
+ $s{hostname} ||= "Unknown hostname";
+
+ # log results
+ $self->log("utserver", "$serverlist_id->{id}, $ip:$port,\t $s{numplayers}/$s{maxplayers} players, $s{mapname}, $s{hostname}");
}
}
@@ -204,7 +342,12 @@ sub process_ucc_applet_query {
$self->log("add", "applet query added $ms->{game}\t$a\t$p");
# add server
- $self->add_pending($a, $p, $ms->{game}, $self->secure_string());
+ $self->add_server_new(ip => $a,
+ beaconport => $p,
+ heartbeat => $p,
+ gamename => $ms->{game},
+ secure => $self->secure_string(),
+ updated => time);
}
# invalid address, log
else {$self->log("error", "invalid address found at master applet $ms->{ip}: $l!");}
@@ -215,7 +358,7 @@ sub process_ucc_applet_query {
$self->{dbh}->commit;
# print findings
- $self->log("applet","found $c addresses at $ms->{ip} for $ms->{game}.");
+ $self->log("applet-rx","found $c addresses at $ms->{ip} for $ms->{game}.");
}
diff --git a/lib/MasterServer/UDP/UDPTicker.pm b/lib/MasterServer/UDP/UDPTicker.pm
new file mode 100755
index 0000000..5a34a8f
--- /dev/null
+++ b/lib/MasterServer/UDP/UDPTicker.pm
@@ -0,0 +1,298 @@
+
+package MasterServer::UDP::UDPTicker;
+
+use strict;
+use warnings;
+use AnyEvent::Handle::UDP;
+use Exporter 'import';
+
+our @EXPORT = qw| udp_ticker |;
+
+################################################################################
+## When addresses are stored in the 'pending' list, they are supposed to be
+## queried immediately with the secure/validate challenge to testify that
+## the server is genuine and alive.
+##
+## Some servers do not support the secure-challenge on the Uplink port. These
+## servers are verified with a secure-challenge on their heartbeat ports,
+## which are designed to respond to secure queries, as well as status queries.
+##
+## Addresses collected by other scripts, whether from the UCC applet or manual
+## input via the website, are added to the pending list. It is more
+## important to verify pending beacons and new server addresses, than to
+## update the status of existing addresses. Therefore, pending addresses are
+## prioritized.
+##
+## Another function required for 333networks is the "server info" part of the
+## site. UT servers are queried and stored in the database. This is the lowest
+## priority for the masterserver and is therefore performed last.
+##
+################################################################################
+sub udp_ticker {
+ my $self = shift;
+
+ # inform that we are running
+ $self->log("info", "UDP Ticker is loaded.");
+
+ # queue -- which address is next in line?
+ my %reset = (start => time, id => 0);
+ my %pending = (%reset, c => 0, limit => 900); # 900s ~ 15m
+ my %updater = (%reset, c => 0, limit => 1800); # 1800s ~ 30m
+ my %ut_serv = (%reset, c => 0, limit => 300); # 300s ~ 5m
+ my %oldserv = (%reset, c => 0, limit => 86400); # 86400s ~ 24h
+
+ my $debug_counter = 0;
+
+ # go through all servers that need querying
+ my $server_info = AnyEvent->timer (
+ after => 75, # first give beacons a chance to uplink
+ interval => 0.2, # 5 addresses per second is fast enough
+ cb => sub {
+
+ # after the first full run was completed, reset the counters when loop time expires
+ if (defined $self->{firstrun}) {
+ # reset timer
+ %reset = (start => time, id => 0, c => 0);
+
+ #
+ # it can happen that a run takes more than the allowed time
+ # in that case, allow more time
+ #
+
+ # pending
+ if (time - $pending{start} > $pending{limit}) {
+ if ($pending{c} > 0) {
+ # done within defined time, reset
+ %pending = (%pending, %reset);
+ }
+ }
+
+ # ut servers
+ if (time - $ut_serv{start} > $ut_serv{limit}) {
+ if ($ut_serv{c} > 0) {
+ # done within defined time, reset
+ %ut_serv = (%ut_serv, %reset)
+ }
+ }
+
+ # updater
+ if (time - $updater{start} > $updater{limit}) {
+ if ($updater{c} > 0) {
+ # done within defined time, reset
+ %updater = (%updater, %reset);
+ }
+ }
+
+ # old servers
+ if (time - $oldserv{start} > $oldserv{limit}) {
+ if ($oldserv{c} > 0) {
+ %oldserv = (%oldserv, %reset);
+ }
+ }
+
+ #
+ # else { print "Making overtime!" }
+
+=pod
+ # FIXME remove this if above works
+
+ # debug: detect premature resets
+ if (time - $pending{start} > $pending{limit}) {
+ if ($pending{c} == 0) {
+ print "Premature pending reset\n" ;
+ }
+ else{$pending{c} = 0;}
+ }
+
+ if (time - $updater{start} > $updater{limit}) {
+ if ($updater{c} == 0) {
+ print "Premature updater reset\n" ;
+ }
+ else{$updater{c} = 0;}
+ }
+
+ if (time - $ut_serv{start} > $ut_serv{limit}) {
+ if ($ut_serv{c} == 0) {
+ print "Premature ut_serv reset\n" ;
+ }
+ else{$ut_serv{c} = 0;}
+ }
+
+ if (time - $oldserv{start} > $oldserv{limit}) {
+ if ($oldserv{c} == 0) {
+ print "Premature oldserv reset\n" ;
+ }
+ else{$oldserv{c} = 0;}
+ }
+
+ # are we making overtime on any of the counters yet?
+ %pending = (%pending, %reset) if (time - $pending{start} > $pending{limit});
+ %updater = (%updater, %reset) if (time - $updater{start} > $updater{limit});
+ %ut_serv = (%ut_serv, %reset) if (time - $ut_serv{start} > $ut_serv{limit});
+ %oldserv = (%oldserv, %reset) if (time - $oldserv{start} > $oldserv{limit});
+=cut
+ }
+
+ #
+ # Check pending beacons
+ #
+
+ # pending beacons/servers (15 seconds grace time)
+ my $n = $self->get_pending(
+ next_id => $pending{id},
+ added => 15,
+ sort => "id",
+ limit => 1
+ )->[0] if $self->{beacon_checker_enabled};
+
+ # if next pending server/address exists:
+ if ( $n ) {
+ # next pending id will be > $n
+ $pending{id} = $n->{id};
+
+ # query the server using the heartbeat port provided in the beacon/manual add
+ $self->query_udp_server(
+ $n->{id},
+ $n->{ip},
+ $n->{heartbeat},
+ $n->{secure}, # secure string necessary!
+ 1, # request secure challenge
+ );
+
+ # our work is done for this cycle.
+ return;
+ }
+
+ # pending are done and is allowed to reset at a later stadium
+ $pending{c}++;
+
+
+ #
+ # Query Unreal Tournament 99 (demo) servers for serverstats
+ #
+
+ # next server in line
+ $n = $self->get_server(
+ next_id => $ut_serv{id},
+ updated => 3600,
+ gamename => "ut",
+ sort => "id",
+ limit => 1,
+ )->[0] if $self->{utserver_query_enabled};
+
+ # if next server/address exists:
+ if ( $n ) {
+ #next pending id will be > $n
+ $ut_serv{id} = $n->{id};
+
+ # query the server (no secure string)
+ $self->query_udp_server(
+ $n->{id},
+ $n->{ip},
+ $n->{port},
+ "", # no secure string necessary
+ 2, # request full status info
+ );
+
+ # our work is done for this cycle.
+ return;
+ }
+
+ # ut servers are done and is allowed to reset at a later stadium
+ $ut_serv{c}++;
+
+ #
+ # update existing servers (both ut/non-ut)
+ #
+
+ # next server in line
+ $n = $self->get_server(
+ next_id => $updater{id},
+ updated => 7200,
+ sort => "id",
+ limit => 1,
+ )->[0] if $self->{beacon_checker_enabled};
+
+ # if next server/address exists:
+ if ( $n ) {
+ #next pending id will be > $n
+ $updater{id} = $n->{id};
+
+ # query the server (no secure string)
+ $self->query_udp_server(
+ $n->{id},
+ $n->{ip},
+ $n->{port},
+ "", # no secure string necessary
+ 0, # request info
+ );
+
+ # our work is done for this cycle.
+ return;
+ }
+
+ # updating servers is done and is allowed to reset at a later stadium
+ $updater{c}++;
+
+ #
+ # Query servers older than 2 hours
+ #
+
+ # next server in line
+ $n = $self->get_server(
+ next_id => $oldserv{id},
+ before => 7200,
+ sort => "id",
+ limit => 1,
+ )->[0] if $self->{beacon_checker_enabled};
+
+ # if next server/address exists:
+ if ( $n ) {
+ #next old server id will be > $n
+ $oldserv{id} = $n->{id};
+
+ # query the server (no secure string)
+ $self->query_udp_server(
+ $n->{id},
+ $n->{ip},
+ $n->{port},
+ "", # no secure string necessary
+ 0, # request info
+ );
+
+ # our work is done for this cycle.
+ return;
+ }
+
+ # old servers are done and is allowed to reset at a later stadium
+ $oldserv{c}++;
+
+ # and notify about first run being completed
+ if (!defined $self->{firstrun}) {
+ # inform that first run is completed
+ my $t = time-$self->{firstruntime};
+ my $t_readable = ($t > 60) ? (($t/60). ":". ($t%60). "minutes") : ($t. "seconds");
+
+ $self->log("info", "First run completed after $t_readable.");
+ $self->{firstrun} = 0;
+
+ # reset all counters and follow procedure
+ %reset = (start => time, id => 0, c => 0);
+ %pending = (%pending, %reset);
+ %updater = (%updater, %reset);
+ %ut_serv = (%ut_serv, %reset);
+ %oldserv = (%oldserv, %reset);
+ }
+
+ # At this point, we are out of server entries. From here on, just count
+ # down until the cycle is complete and handle new entries while they are
+ # added to the list.
+
+ }
+ );
+
+ # return the timer object to keep it alive outside of this scope
+ return $server_info;
+}
+
+1;
diff --git a/lib/MasterServer/UDP/UpLink.pm b/lib/MasterServer/UDP/UpLink.pm
new file mode 100755
index 0000000..e5c703b
--- /dev/null
+++ b/lib/MasterServer/UDP/UpLink.pm
@@ -0,0 +1,169 @@
+package MasterServer::UDP::UpLink;
+
+use strict;
+use warnings;
+use Encode;
+use AnyEvent::Handle::UDP;
+use Socket qw(sockaddr_in inet_ntoa);
+use Exporter 'import';
+
+our @EXPORT = qw| send_heartbeats
+ do_uplink
+ process_uplink_response
+ process_udp_secure
+ process_udp_basic |;
+
+################################################################################
+## Broadcast heartbeats to other masterservers
+##
+################################################################################
+sub send_heartbeats {
+ my $self = shift;
+
+ # in order to be permitted to sync, you need to share your address too so
+ # others can sync from you too.
+ if ($self->{sync_enabled}) {
+
+ # uplink to every entry of the masterserver brotherhood list
+ foreach my $uplink (values %{$self->masterserver_list()}) {
+ # send uplink
+ $self->do_uplink($uplink->{ip}, $uplink->{udp});
+ }
+ }
+}
+
+
+################################################################################
+## Do an uplink to other 333networks-based masterservers so we can be shared
+## along the 333networks synchronization protocol. Other 333networks-based
+## masterservers are shared in this way too.
+################################################################################
+sub do_uplink {
+ my ($self, $ip, $port) = @_;
+
+ # do not proceed if not all information is available
+ return unless (defined $ip && defined $port && $port > 0);
+
+ # debug spamming
+ $self->log("uplink", "Uplink to Masterserver $ip:$port");
+
+ # connect with UDP server
+ my $udp_client; $udp_client = AnyEvent::Handle::UDP->new(
+ # Bind to this host and port
+ connect => [$ip, $port],
+ timeout => 5,
+ on_timeout => sub {$udp_client->destroy();}, # don't bother reporting timeouts
+ on_error => sub {$udp_client->destroy();}, # or errors
+ on_recv => sub {$self->process_uplink_response(@_)},
+ );
+
+ # Send heardbeat
+ $udp_client->push_send("\\heartbeat\\$self->{beacon_port}\\gamename\\333networks");
+}
+
+################################################################################
+## Process requests received after uplinking
+##
+################################################################################
+sub process_uplink_response {
+ # $self, beacon address, handle, packed client address
+ my ($self, $b, $udp, $pa) = @_;
+
+ # unpack ip from packed client address
+ my ($port, $iaddr) = sockaddr_in($pa);
+ my $peer_addr = inet_ntoa($iaddr);
+
+ # if the beacon has a length longer than a certain amount, assume it is
+ # a fraud or crash attempt
+ if (length $b > 64) {
+ # log
+ $self->log("attack","length exceeded in uplink response: $peer_addr:$port sent $b");
+
+ # truncate and try to continue
+ $b = substr $b, 0, 64;
+ }
+
+ # check if this is a secure challenge
+ $self->process_udp_secure($udp, $pa, $b, $peer_addr)
+ if ($b =~ m/\\secure\\/);
+}
+
+
+################################################################################
+## Process the received secure query and respond with the correct response
+##
+################################################################################
+sub process_udp_secure {
+ # $self, handle, packed address, udp data, peer ip address, $port
+ my ($self, $udp, $pa, $buf, $peer_addr) = @_;
+
+ # received secure in $buf: \basic\secure\l8jfVy
+ my %r;
+ my $raw = $buf; # raw buffer for logging if necessary
+ $buf = encode('UTF-8', $buf);
+ $buf =~ s/\\\\/\\undef\\/;
+ $buf =~ s/\n//;
+ $buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
+
+ # scope
+ my $response = "";
+
+ # provide basic information if asked for (not uncommon)
+ if (defined $r{basic}) {
+ # compile basic string (identical to process_udp_basic)
+
+ # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1
+ $response .= "\\gamename\\333networks"
+ . "\\gamever\\$self->{short_version}"
+ . "\\location\\0"
+ . "\\hostname\\$self->{masterserver_hostname}"
+ . "\\hostport\\$self->{listen_port}";
+ }
+
+ # we only respond with gamename = 333networks
+ if (defined $r{secure}) {
+ # get response
+ $response .= "\\validate\\"
+ . $self->validate_string(gamename => "333networks",
+ enctype => 0,
+ secure => $r{secure});
+ }
+
+ # send the response to the \basic\\secure\wookie query
+ $udp->push_send("$response\\final\\", $pa);
+}
+
+################################################################################
+## Respond to basic or status queries
+## TODO: abstract function for this -- otherwise these functions pile up.
+################################################################################
+sub process_udp_basic {
+ # $self, handle, packed address, udp data, peer ip address, $port
+ my ($self, $udp, $pa, $buf, $peer_addr) = @_;
+
+ # received basic or status in $buf: \basic\ or \status\
+ my %r;
+ $buf = encode('UTF-8', $buf);
+ $buf =~ s/\\\\/\\undef\\/;
+ $buf =~ s/\n//;
+ $buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
+
+ # scope
+ my $basic = "";
+ # provide basic information
+
+ if (defined $r{basic} || defined $r{status} || defined $r{info}) {
+ # compile basic string (identical to process_udp_basic)
+
+ # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1
+ $basic = "\\gamename\\333networks"
+ . "\\gamever\\$self->{short_version}"
+ . "\\location\\0"
+ . "\\hostname\\$self->{masterserver_hostname}"
+ . "\\hostport\\$self->{listen_port}";
+ }
+
+ # send the response to the \basic\ or \status\
+ $udp->push_send("$basic\\final\\", $pa);
+}
+1;
diff --git a/lib/MasterServer/Util/KFStatsWatcher.pm b/lib/MasterServer/Util/KFStatsWatcher.pm
new file mode 100755
index 0000000..916446e
--- /dev/null
+++ b/lib/MasterServer/Util/KFStatsWatcher.pm
@@ -0,0 +1,59 @@
+
+package MasterServer::Util::KFStatsWatcher;
+
+use strict;
+use warnings;
+use AnyEvent::IO;
+use Exporter 'import';
+
+our @EXPORT = qw| read_kfstats |;
+
+################################################################################
+## Read Killing Floor Statistics from the file.
+################################################################################
+sub read_kfstats {
+ my ($self) = shift;
+
+ # open file and read content
+ return aio_load($self->{kfstats_file},
+ sub {
+ my $f = shift;
+
+ # process player data as blocks
+ my $block = "";
+
+ # read player stats
+ for my $l (split /^/, $f) {
+
+ # add data to block
+ $block .= $l;
+
+ # if block contains last item GamesLost, process block
+ if ($l =~ m/^(GamesLost=)/i){
+
+ # treat as array
+ my @s = split "\n", $block;
+
+ # process items
+ my %h;
+ for my $m (@s) {
+ if ($m =~ m/(KFPlayerStats\])$/i) { $h{UTkey} = substr $m, 1, index($m, " ")-1; }
+ if ($m =~ m/=/) {$h{substr $m, 0, index($m, "=")} = substr $m, index($m, "=")+1; }
+ }
+
+ # store in db
+ $self->write_kfstats(\%h);
+
+ # clear block for next player
+ $block = "";
+ }
+ }
+
+ #notify
+ $self->log("kfstat", "Updated Killing Floor player stats.");
+ }
+ );
+}
+
+
+1;