aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/TCP
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MasterServer/TCP')
-rwxr-xr-xlib/MasterServer/TCP/BrowserHost.pm68
-rwxr-xr-xlib/MasterServer/TCP/Handler.pm280
-rwxr-xr-xlib/MasterServer/TCP/ListCompiler.pm114
-rwxr-xr-xlib/MasterServer/TCP/Syncer.pm166
4 files changed, 628 insertions, 0 deletions
diff --git a/lib/MasterServer/TCP/BrowserHost.pm b/lib/MasterServer/TCP/BrowserHost.pm
new file mode 100755
index 0000000..b30b27e
--- /dev/null
+++ b/lib/MasterServer/TCP/BrowserHost.pm
@@ -0,0 +1,68 @@
+
+package MasterServer::TCP::BrowserHost;
+
+use strict;
+use warnings;
+use AnyEvent::Socket;
+use AnyEvent::Handle;
+use Exporter 'import';
+
+our @EXPORT = qw| browser_host clean_tcp_handle|;
+
+################################################################################
+## wait for incoming TCP connections from game clients and other masterservers.
+## respond with secure/validate, contact info and/or server lists.
+## allow other masterservers to synchronize
+################################################################################
+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) = @_;
+
+ # validated? yes = 1 no = 0
+ my $auth = 0;
+
+ # debug -- new connection opened
+ $self->log("tcp","New connection from $a:$p");
+
+ # prep a challenge
+ my $secure = $self->secure_string();
+
+ # handle received data
+ my $h; $h = AnyEvent::Handle->new(
+ fh => $fh,
+ poll => 'r',
+ timeout => 1,
+ on_eof => sub {$self->clean_tcp_handle(@_)},
+ on_error => sub {$self->clean_tcp_handle(@_)},
+ on_read => sub {$self->read_tcp_handle($h, $a, $p, $secure, @_)},
+ );
+
+ # part 1: send \basic\\secure\$key\
+ $h->push_write("\\basic\\\\secure\\$secure\\final\\");
+
+ # keep handle alive longer and store authentication info
+ $self->{browser_clients}->{$h} = [$h, $auth];
+ return;
+ };
+
+ # startup of TCP server complete
+ $self->log("load", "Listening for TCP connections on port $self->{listen_port}.");
+ return $browser;
+}
+
+################################################################################
+## clean handles on timeouts, completed requests and/or errors
+################################################################################
+sub clean_tcp_handle{
+ my ($self, $c) = @_;
+ # clean and close the connection
+ delete ($self->{browser_clients}->{$c});
+ $c->destroy();
+}
+
+1;
diff --git a/lib/MasterServer/TCP/Handler.pm b/lib/MasterServer/TCP/Handler.pm
new file mode 100755
index 0000000..bf6beb9
--- /dev/null
+++ b/lib/MasterServer/TCP/Handler.pm
@@ -0,0 +1,280 @@
+
+package MasterServer::TCP::Handler;
+
+use strict;
+use warnings;
+use AnyEvent::Socket;
+use AnyEvent::Handle;
+use Exporter 'import';
+
+our @EXPORT = qw| read_tcp_handle
+ handle_validate
+ handle_about
+ handle_list
+ handle_sync |;
+
+################################################################################
+## wait for incoming TCP connections from game clients and other masterservers.
+## respond with secure/validate, contact info and/or server lists.
+## allow other masterservers to synchronize
+################################################################################
+sub read_tcp_handle {
+ my ($self, $h, $a, $p, $secure, $c) = @_;
+
+ # clear the buffer
+ my $m = $c->rbuf;
+ $c->rbuf = "";
+
+ # did the client validate already?
+ my $val = $self->{browser_clients}->{$h}[1];
+
+ # in case of errors, save the original message
+ my $rxbuf = $m;
+
+ # allow multiple blocks to add to the response string
+ my $response = "";
+
+ # 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/\\([^\\]+)\\([^\\]+)/$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\
+ $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});
+
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ # improper syntax/protocol -- no valid commands found
+ # respond with an error.
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ if ($m =~ m/!(about|sync|validate|list)/) {
+
+ # 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});
+ } # end if weird query
+ else {
+ $c->push_write($response . "\\final\\") if ($response ne "");
+ }
+
+}
+
+
+################################################################################
+## The master server opens the connection with the \secure\ challenge. The
+## client should respond with basic information about itself and the
+## \validate\ response. In this code block we verify the challenge/response.
+################################################################################
+sub handle_validate {
+ my ($self, $r, $h, $secure, $a, $p) = @_;
+
+ # auth var init
+ my $val = 0;
+
+ # pass or fail the secure challenge
+ if (exists $r->{gamename} && exists $self->{game}->{$r->{gamename}}) {
+ # game exists and we have the key to verify the response
+ $val = $self->validated_request($r->{gamename}, $secure, $r->{enctype}, $r->{validate});
+
+ # 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");
+ }
+
+ # log
+ $self->log("secure","$a:$p validated with $val for $r->{gamename}, $secure, $r->{validate}");
+
+ # return auth status
+ return $val;
+}
+
+
+################################################################################
+## \about\ query.
+## Can contain the values "contact", "build", "Address" or "support", where
+## - contact: return contact information; see config.pl
+## - build: build info, also version.pm
+## - address: address and ports
+## - support: return a list of games currently in the database
+## - undef: all of the above
+##
+## NOTE: client does not need to validate to be allowed to perform this
+## query.
+################################################################################
+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
+ $self->log("about","communicating to $a:$p my contact information.");
+ }
+
+ #
+ # build info
+ #
+ if ($about =~ /^build$/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 = "";
+ for (@{$sg}) {
+ $sgs .= " " if (length $sgs > 0);
+ $sgs .= $_->[0];
+ }
+
+ # print response
+ $response .= "\\support\\$sgs";
+
+ #log/print
+ $self->log("about","telling $a:$p which games are supported.");
+ }
+
+ # return response string
+ return $response;
+}
+
+################################################################################
+## At this point, the client should be validated and ready to request with
+## the \secure\ command and is allowed to ask for the list.
+## The \list\ cmd is NOT compatible with combined queries
+## (like \about\contact\list\)
+################################################################################
+sub handle_list {
+ my ($self, $val, $r, $c, $a, $p) = @_;
+
+ # confirm validation
+ if ($val && exists $r->{gamename}) {
+
+ # prepare the list
+ my $data = "";
+
+ # determine the return format
+ if ($self->{hex_format} =~ m/$r->{gamename}/i or $r->{gamename} =~ /^cmp$/i) {
+ # return addresses as byte format (ip=ABCD port=EF)
+ $data .= $self->compile_list_cmp($r->{gamename});
+ }
+ else {
+ # return addresses as regular \ip\127.0.0.1:7777\ format
+ $data .= $self->compile_list($r->{gamename});
+ }
+
+ # finalize response string
+ $data .= "\\final\\";
+
+ # immediately send to client
+ $c->push_write($data);
+
+ # log successful (debug)
+ $self->log("list","$a:$p successfully retrieved the list for $r->{gamename}.");
+
+ # clean and close the connection
+ $self->clean_tcp_handle($c);
+ }
+
+ # proper syntax/protocol, but incorrect validation. Therefore respond with
+ # an 'empty' list, returning only \final\.
+ else {
+ # return error/empty list
+ $c->push_write("\\echo\\333networks failed to validate your request. Use the correct authorization cipher!\\final\\");
+
+ # log it too
+ $self->log("error","browser $a:$p failed validation for $r->{gamename}");
+
+ # clean and close the connection
+ $self->clean_tcp_handle($c);
+ }
+}
+
+################################################################################
+## Respond to \sync\ requests from other 333networks-based masterservers. After
+## validation, sync behaves in much the same way as \list\,
+################################################################################
+sub handle_sync {
+ my ($self, $val, $r, $c, $a, $p) = @_;
+
+ # alternate part 3: wait for the requested action: \sync\(all|list of games)
+ $self->log("tcp","Sync request from $a:$p found");
+
+ if ($val && exists $r->{sync}) {
+
+ # compile list of addresses
+ my $data = $self->compile_sync($r->{sync});
+ $data .= "\\final\\";
+
+ # send to remote client
+ $c->push_write($data);
+
+ # log successful (debug)
+ $self->log("sync","$a:$p successfully synced.");
+
+ # clean and close the connection
+ $self->clean_tcp_handle($c);
+
+ }
+ # proper syntax/protocol, but incorrect validation. Therefore respond with
+ # an 'empty' list, returning only \final\.
+ else {
+
+ # return error/empty list
+ $c->push_write("\\echo\\333networks failed to validate your request. Use a proper authorization key!\\final\\");
+
+ # log it too
+ $self->log("error","$a:$p failed synchronization.");
+
+ # clean and close the connection
+ $self->clean_tcp_handle($c);
+ }
+}
+
+1;
diff --git a/lib/MasterServer/TCP/ListCompiler.pm b/lib/MasterServer/TCP/ListCompiler.pm
new file mode 100755
index 0000000..56bb256
--- /dev/null
+++ b/lib/MasterServer/TCP/ListCompiler.pm
@@ -0,0 +1,114 @@
+
+package MasterServer::TCP::ListCompiler;
+
+use strict;
+use warnings;
+use Exporter 'import';
+
+our @EXPORT = qw| compile_list compile_list_cmp compile_sync |;
+
+################################################################################
+## compile the list of \ip\ip:port\ addresses and parse them into the
+## plaintext return string.
+################################################################################
+sub compile_list {
+ my ($self, $gamename) = @_;
+
+ # get the list from database
+ my $serverlist = $self->get_game_list($gamename);
+
+ # prepare empty return string
+ my $response_string = "";
+
+ # add address as regular \ip\127.0.0.1:7777\ format
+ for (@{$serverlist}){
+
+ # append \ip\ip:port to string
+ $response_string .= "\\ip\\$_->[0]:$_->[1]";
+ }
+
+ # return the string with data
+ return $response_string;
+}
+
+################################################################################
+## compile the list of binary ip:port addresses and parse them into the
+## ABCDE return string.
+################################################################################
+sub compile_list_cmp {
+ my ($self, $gamename) = @_;
+
+ # get the list from database
+ my $serverlist = $self->get_game_list($gamename);
+
+ # prepare empty return string
+ my $response_string = "";
+
+ # compile a return string
+ 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);
+
+ # print as chr string of 6 bytes long
+ my $bin = ""; $bin .= (chr $A) . (chr $B) . (chr $C) . (chr $D) . (chr $E) . (chr $F);
+
+ # append to list of addresses
+ $response_string .= $bin;
+ }
+
+ # return the string with data
+ return $response_string;
+}
+
+
+################################################################################
+## compile a list of all requested games --or-- if not specified, a list of
+## all games
+################################################################################
+sub compile_sync {
+ my ($self, $sync) = @_;
+
+ # prepare empty return string
+ my $response_string = "";
+ my @games;
+
+ # client requests to sync all games
+ if ($sync eq "all") {
+ # get array of gamenames from db
+ my $sg = $self->get_gamenames();
+ for (@{$sg}) {push @games, $_->[0];}
+ }
+ # only selected games
+ else {
+ # split request into array
+ @games = split " ", $sync;
+ }
+
+ # only get unique values from array
+ @games = map { $_ => 1 } @games;
+
+ # get the list for every requested gamename
+ for my $g (@games) {
+
+ # $g is now a gamename -- check if it's supported. Else ignore.
+ if (exists $self->{game}->{$g}) {
+
+ # get list from database
+ my $list = $self->get_game_list($g);
+
+ # add all games to string separated by spaces
+ my $gamestring = "";
+ foreach $_ (@{$list}) {$gamestring .= "$_->[0]:$_->[1] ";}
+
+ # if it contains at least one entry, add the list to the response list
+ $response_string .= "\\$g\\$gamestring" if (length $gamestring >= 7);
+ }
+ }
+
+ # return \gamename\addresses\gamename2\addresses2 list
+ return $response_string;
+}
+
+1;
diff --git a/lib/MasterServer/TCP/Syncer.pm b/lib/MasterServer/TCP/Syncer.pm
new file mode 100755
index 0000000..dea20a5
--- /dev/null
+++ b/lib/MasterServer/TCP/Syncer.pm
@@ -0,0 +1,166 @@
+
+package MasterServer::TCP::Syncer;
+
+use strict;
+use warnings;
+use AnyEvent;
+use AnyEvent::Handle;
+use Exporter 'import';
+use Data::Dumper 'Dumper';
+
+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++;
+ }
+ );
+}
+
+################################################################################
+## Sends synchronization request to another 333networks based master server and
+## receives the list of games.
+################################################################################
+sub sync_with_master {
+ my ($self, $ms) = @_;
+
+ # list to store all IPs in.
+ my $sync_list = "";
+
+ # connection handle
+ my $handle;
+ $handle = new AnyEvent::Handle(
+ connect => [$ms->{address} => $ms->{port}],
+ timeout => 3,
+ 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_read => sub {
+ # receive and clear buffer
+ my $m = $_[0]->rbuf;
+ $_[0]->rbuf = "";
+
+ # remove string terminator: sometimes trailing slashes are added or
+ # forgotten by sender, so \secure\abcdef is actually \secure\abcdef{\0}
+ chop $m if $m =~ m/secure/;
+
+ # part 1: receive \basic\\secure\$key
+ if ($m =~ m/basic\\\\secure/) {
+
+ # hash $m into %r
+ my %r = ();
+ $m =~ s/\\\\/\\undef\\/;
+ $m =~ s/\n//;
+ $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
+
+ # respond to the validate challenge
+ my $validate = $self->validate_string("333networks", $r{secure}, $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 = "\\sync\\".(($self->{sync_games}[0] == 0) ? "all" : $self->{sync_games}[1])."\\final\\";
+
+ # push the request to remote host
+ $handle->push_write($request);
+
+ # clean up $m for future receivings
+ $m = "";
+
+ } # end secure
+
+ # part 4: receive the entire list in multiple steps
+ $sync_list .= $m;
+ },
+ );
+}
+
+################################################################################
+## Process the list of addresses that was received after querying the UCC applet
+## and store them in the pending list.
+################################################################################
+sub process_sync_list {
+ my ($self, $m, $ms) = @_;
+
+ # replace empty values for the string "undef" and replace line endings from netcatters
+ # parse hash {gamename => list of ips seperated by space}
+ my %r = ();
+ $m =~ s/\\\\/\\undef\\/;
+ $m =~ s/\n//;
+ $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
+
+ # counter
+ my $c = 0;
+
+ # iterate through the gamenames and addresses
+ while ( my ($gn,$addr) = each %r) {
+
+ # only process gamenames that are in our list for supported games (supportedgames.pl)
+ if (defined $gn && exists $self->{game}->{lc $gn}) {
+
+ # database types such as SQLite are slow, therefore use transactions.
+ $self->{dbh}->begin_work;
+
+ # l(ocations, \label\ip:port\) split up in a(ddress) and p(ort)
+ foreach my $l (split(/ /, $addr)) {
+
+ # search for \255.255.255.255:7778\, contains ':'
+ if ($l =~ /:/) {
+ my ($a,$p) = $l =~ /(.*):(.*)/;
+
+ # check if address entry is valid
+ if ($self->valid_address($a,$p)) {
+ # count number of valid addresses
+ $c++;
+
+ # add server
+ $self->syncer_add($a, $p, $gn, $self->secure_string());
+
+ # print address
+ $self->log("add", "syncer added $gn\t$a\t$p");
+ }
+ else {
+ # invalid address, log
+ $self->log("error", "invalid address found while syncing at $ms->{address}: $l!");
+ }
+
+ } # endif ($l =~ /:/)
+ } # end for / /
+
+ # end transaction, commit
+ $self->{dbh}->commit;
+
+ } # end defined $gn
+ } # end while
+
+ # end message
+ $self->log("sync", "received $c addresses after syncing from $ms->{address}");
+}
+
+1;