X-Git-Url: http://dxcluster.net/gitweb/gitweb.cgi?a=blobdiff_plain;f=perl%2Fcluster.pl;h=e8e7cb0568b0f51fef1d93ff7a2aa50b55278452;hb=f6fdd9847c1f46b233be56296cad39ad949d25ae;hp=995500ca934239b4ca18ec6ba580d9ef96f8e3c8;hpb=dc016a0633cdb15d29b9a71fdec2c230fbad201b;p=spider.git diff --git a/perl/cluster.pl b/perl/cluster.pl index 995500ca..e8e7cb05 100755 --- a/perl/cluster.pl +++ b/perl/cluster.pl @@ -23,9 +23,17 @@ BEGIN { unshift @INC, "$root/perl"; # this IS the right way round! unshift @INC, "$root/local"; + # do some validation of the input + die "The directory $root doesn't exist, please RTFM" unless -d $root; + die "$root/local doesn't exist, please RTFM" unless -d "$root/local"; + die "$root/local/DXVars.pm doesn't exist, please RTFM" unless -e "$root/local/DXVars.pm"; + + mkdir "$root/local_cmd", 0777 unless -d "$root/local_cmd"; + + # try to create and lock a lockfile (this isn't atomic but # should do for now - my $lockfn = "$root/perl/cluster.lock"; # lock file name + $lockfn = "$root/local/cluster.lck"; # lock file name if (-e $lockfn) { open(CLLOCK, "$lockfn") or die "Can't open Lockfile ($lockfn) $!"; my $pid = ; @@ -36,10 +44,19 @@ BEGIN { open(CLLOCK, ">$lockfn") or die "Can't open Lockfile ($lockfn) $!"; print CLLOCK "$$\n"; close CLLOCK; + + $is_win = ($^O =~ /^MS/ || $^O =~ /^OS-2/) ? 1 : 0; # is it Windows? + $systime = time; } -use Msg; use DXVars; +use Msg; +use IntMsg; +use Internet; +use Listeners; +use ExtMsg; +use AGWConnect; +use AGWMsg; use DXDebug; use DXLog; use DXLogPrint; @@ -48,23 +65,46 @@ use DXChannel; use DXUser; use DXM; use DXCommandmode; +use DXProtVars; +use DXProtout; use DXProt; use DXMsg; -use DXCluster; use DXCron; use DXConnect; +use DXBearing; +use DXDb; +use DXHash; +use DXDupe; +use Script; use Prefix; +use Spot; use Bands; +use Keps; +use Minimuf; +use Sun; use Geomag; use CmdAlias; use Filter; -use DXDb; use AnnTalk; +use BBS; use WCY; -use DXDupe; use BadWords; +use Timer; +use Route; +use Route::Node; +use Route::User; +use Editable; +use Mrtg; +use USDB; +use UDPMsg; +use QSL; +use RouteDB; +use DXXml; +use DXSql; +use IsoTime; use Data::Dumper; +use IO::File; use Fcntl ':flock'; use POSIX ":sys_wait_h"; @@ -72,119 +112,119 @@ use Local; package main; -#use strict; -#use vars qw(@inqueue $systime $version $starttime $lockfn @outstanding_connects $zombies $root -# $lang $myalias @debug $userfn $clusteraddr $clusterport $mycall $decease ); +use strict; +use vars qw(@inqueue $systime $version $starttime $lockfn @outstanding_connects + $zombies $root @listeners $lang $myalias @debug $userfn $clusteraddr + $clusterport $mycall $decease $is_win $routeroot $me $reqreg $bumpexisting + $allowdxby $dbh $dsn $dbuser $dbpass $do_xml + ); @inqueue = (); # the main input queue, an array of hashes $systime = 0; # the time now (in seconds) -$version = "1.47"; # the version no of the software +$version = "1.52"; # the version no of the software $starttime = 0; # the starting time of the cluster -$lockfn = "cluster.lock"; # lock file name -@outstanding_connects = (); # list of outstanding connects - -# handle disconnections -sub disconnect -{ - my $dxchan = shift; - return if !defined $dxchan; - $dxchan->disconnect(); -} +#@outstanding_connects = (); # list of outstanding connects +@listeners = (); # list of listeners +$reqreg = 0; # 1 = registration required, 2 = deregister people +$bumpexisting = 1; # 1 = allow new connection to disconnect old, 0 - don't allow it +$allowdxby = 0; # 1 = allow "dx by ", 0 - don't allow it + # send a message to call on conn and disconnect sub already_conn { my ($conn, $call, $mess) = @_; - - dbg('chan', "-> D $call $mess\n"); + + $conn->disable_read(1); + dbg("-> D $call $mess\n") if isdbg('chan'); $conn->send_now("D$call|$mess"); - sleep(1); - dbg('chan', "-> Z $call bye\n"); - $conn->send_now("Z$call|bye"); # this will cause 'client' to disconnect - sleep(1); - $conn->disconnect(); + sleep(2); + $conn->disconnect; +} + +sub error_handler +{ + my $dxchan = shift; + $dxchan->{conn}->set_error(undef) if exists $dxchan->{conn}; + $dxchan->disconnect(1); } # handle incoming messages -sub rec +sub new_channel { - my ($conn, $msg, $err) = @_; - my $dxchan = DXChannel->get_by_cnum($conn); # get the dxconnnect object for this message - - if (!defined $msg || (defined $err && $err)) { - if ($dxchan) { - if (defined $err) { - $conn->disconnect; - undef $conn; - $dxchan->conn(undef); - } - $dxchan->disconnect; - } elsif ($conn) { - $conn->disconnect; - } + my ($conn, $msg) = @_; + my ($sort, $call, $line) = DXChannel::decode_input(0, $msg); + return unless defined $sort; + + unless (is_callsign($call)) { + already_conn($conn, $call, DXM::msg($lang, "illcall", $call)); return; } - - # set up the basic channel info - this needs a bit more thought - there is duplication here - if (!defined $dxchan) { - my ($sort, $call, $line) = DXChannel::decode_input(0, $msg); - return unless defined $sort; - - # is there one already connected to me - locally? - my $user = DXUser->get($call); - if (DXChannel->get($call)) { - my $mess = DXM::msg($lang, ($user && $user->is_node) ? 'concluster' : 'conother', $call); - already_conn($conn, $call, $mess); + + # set up the basic channel info + # is there one already connected to me - locally? + my $user = DXUser->get_current($call); + my $dxchan = DXChannel::get($call); + if ($dxchan) { + if ($user && $user->is_node) { + already_conn($conn, $call, DXM::msg($lang, 'concluster', $call, $main::mycall)); return; } - - # is there one already connected elsewhere in the cluster? - if ($user) { - if (($user->is_node || $call eq $myalias) && !DXCluster->get_exact($call)) { - ; - } else { - if (DXCluster->get_exact($call)) { - my $mess = DXM::msg($lang, $user->is_node ? 'concluster' : 'conother', $call); - already_conn($conn, $call, $mess); - return; - } - } - $user->{lang} = $main::lang if !$user->{lang}; # to autoupdate old systems + if ($bumpexisting) { + my $ip = $conn->{peerhost} || 'unknown'; + $dxchan->send_now('D', DXM::msg($lang, 'conbump', $call, $ip)); + LogDbg('DXCommand', "$call bumped off by $ip, disconnected"); + $dxchan->disconnect; } else { - if (DXCluster->get_exact($call)) { - my $mess = DXM::msg($lang, 'conother', $call); - already_conn($conn, $call, $mess); - return; - } - $user = DXUser->new($call); + already_conn($conn, $call, DXM::msg($lang, 'conother', $call, $main::mycall)); + return; } + } - # is he locked out ? - if ($user->lockout) { - Log('DXCommand', "$call is locked out, disconnected"); - $conn->send_now("Z$call|bye"); # this will cause 'client' to disconnect + # is he locked out ? + my $basecall = $call; + $basecall =~ s/-\d+$//; + my $baseuser = DXUser->get_current($basecall); + my $lock = $user->lockout if $user; + if ($baseuser && $baseuser->lockout || $lock) { + if (!$user || !defined $lock || $lock) { + my $host = $conn->{peerhost} || "unknown"; + LogDbg('DXCommand', "$call on $host is locked out, disconnected"); + $conn->disconnect; return; } - - # create the channel - $dxchan = DXCommandmode->new($call, $conn, $user) if $user->is_user; - $dxchan = DXProt->new($call, $conn, $user) if $user->is_node; - $dxchan = BBS->new($call, $conn, $user) if $user->is_bbs; - die "Invalid sort of user on $call = $sort" if !$dxchan; } - # queue the message and the channel object for later processing - if (defined $msg) { - my $self = bless {}, "inqueue"; - $self->{dxchan} = $dxchan; - $self->{data} = $msg; - push @inqueue, $self; + if ($user) { + $user->{lang} = $main::lang if !$user->{lang}; # to autoupdate old systems + } else { + $user = DXUser->new($call); + } + + # create the channel + if ($user->is_node) { + $dxchan = DXProt->new($call, $conn, $user); + } elsif ($user->is_user) { + $dxchan = DXCommandmode->new($call, $conn, $user); + } elsif ($user->is_bbs) { + $dxchan = BBS->new($call, $conn, $user); + } else { + die "Invalid sort of user on $call = $sort"; } + + # check that the conn has a callsign + $conn->conns($call) if $conn->isa('IntMsg'); + + # set callbacks + $conn->set_error(sub {error_handler($dxchan)}); + $conn->set_rproc(sub {my ($conn,$msg) = @_; $dxchan->rec($msg);}); + $dxchan->rec($msg); } + sub login { - return \&rec; + return \&new_channel; } # cease running this program, close down all the connections nicely @@ -192,49 +232,54 @@ sub cease { my $dxchan; - $SIG{'TERM'} = 'IGNORE'; - $SIG{'INT'} = 'IGNORE'; + unless ($is_win) { + $SIG{'TERM'} = 'IGNORE'; + $SIG{'INT'} = 'IGNORE'; + } DXUser::sync; eval { Local::finish(); # end local processing }; - dbg('local', "Local::finish error $@") if $@; + dbg("Local::finish error $@") if $@; # disconnect nodes - foreach $dxchan (DXChannel->get_all()) { - next unless $dxchan->is_node; - disconnect($dxchan) unless $dxchan == $DXProt::me; + foreach $dxchan (DXChannel::get_all_nodes) { + $dxchan->disconnect(2) unless $dxchan == $main::me; } - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); + Msg->event_loop(100, 0.01); # disconnect users - foreach $dxchan (DXChannel->get_all()) { - next if $dxchan->is_node; - disconnect($dxchan) unless $dxchan == $DXProt::me; + foreach $dxchan (DXChannel::get_all_users) { + $dxchan->disconnect; } - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); - Msg->event_loop(1, 0.05); + + # disconnect AGW + AGWMsg::finish(); + + # disconnect UDP customers + UDPMsg::finish(); + + # end everything else + Msg->event_loop(100, 0.01); DXUser::finish(); DXDupe::finish(); # close all databases DXDb::closeall; - - dbg('chan', "DXSpider version $version ended"); - Log('cluster', "DXSpider V$version stopped"); + + # close all listeners + foreach my $l (@listeners) { + $l->close_server; + } + + LogDbg('cluster', "DXSpider V$version, build $build ended"); dbgclose(); Logclose(); + + $dbh->finish if $dbh; + unlink $lockfn; # $SIG{__WARN__} = $SIG{__DIE__} = sub {my $a = shift; cluck($a); }; exit(0); @@ -245,47 +290,15 @@ sub reap { my $cpid; while (($cpid = waitpid(-1, WNOHANG)) > 0) { - dbg('reap', "cpid: $cpid"); - @outstanding_connects = grep {$_->{pid} != $cpid} @outstanding_connects; + dbg("cpid: $cpid") if isdbg('reap'); +# Msg->pid_gone($cpid); $zombies-- if $zombies > 0; } - dbg('reap', "cpid: $cpid"); + dbg("cpid: $cpid") if isdbg('reap'); } # this is where the input queue is dealt with and things are dispatched off to other parts of # the cluster -sub process_inqueue -{ - my $self = shift @inqueue; - return if !$self; - - my $data = $self->{data}; - my $dxchan = $self->{dxchan}; - my $error; - my ($sort, $call, $line) = DXChannel::decode_input($dxchan, $data); - return unless defined $sort; - - # do the really sexy console interface bit! (Who is going to do the TK interface then?) - dbg('chan', "<- $sort $call $line\n") unless $sort eq 'D'; - - # handle A records - my $user = $dxchan->user; - if ($sort eq 'A' || $sort eq 'O') { - $dxchan->start($line, $sort); - } elsif ($sort eq 'I') { - die "\$user not defined for $call" if !defined $user; - # normal input - $dxchan->normal($line); - disconnect($dxchan) if ($dxchan->{state} eq 'bye'); - } elsif ($sort eq 'Z') { - $dxchan->conn(undef); - disconnect($dxchan); - } elsif ($sort eq 'D') { - ; # ignored (an echo) - } else { - print STDERR atime, " Unknown command letter ($sort) received from $call\n"; - } -} sub uptime { @@ -297,6 +310,12 @@ sub uptime my $mins = int $t / 60; return sprintf "%d %02d:%02d", $days, $hours, $mins; } + +sub AGWrestart +{ + AGWMsg::init(\&new_channel); +} + ############################################################# # # The start of the main line of code @@ -306,134 +325,199 @@ sub uptime $starttime = $systime = time; $lang = 'en' unless $lang; +unless ($DB::VERSION) { + $SIG{INT} = $SIG{TERM} = \&cease; +} + # open the debug file, set various FHs to be unbuffered -dbginit(); +dbginit(\&DXCommandmode::broadcast_debug); foreach (@debug) { dbgadd($_); } STDOUT->autoflush(1); -Log('cluster', "DXSpider V$version started"); +# calculate build number +$build += $main::version; +$build = "$build.$branch" if $branch; + +# try to load the database +if (DXSql::init($dsn)) { + $dbh = DXSql->new($dsn); + $dbh = $dbh->connect($dsn, $dbuser, $dbpass) if $dbh; +} + +# try to load XML::Simple +DXXml::init(); # banner -dbg('err', "DXSpider DX Cluster Version $version", "Copyright (c) 1998-2001 Dirk Koopman G1TLH"); +my ($year) = (gmtime)[5]; +$year += 1900; +LogDbg('cluster', "DXSpider V$version, build $build started"); +dbg("Copyright (c) 1998-$year Dirk Koopman G1TLH"); # load Prefixes -dbg('err', "loading prefixes ..."); -Prefix::load(); +dbg("loading prefixes ..."); +dbg(USDB::init()); +my $r = Prefix::init(); +confess $r if $r; # load band data -dbg('err', "loading band data ..."); +dbg("loading band data ..."); Bands::load(); # initialise User file system -dbg('err', "loading user file system ..."); +dbg("loading user file system ..."); DXUser->init($userfn, 1); +# look for the sysop and the alias user and complain if they aren't there +{ + my $ref = DXUser->get($mycall); + die "$mycall missing, run the create_sysop.pl script and please RTFM" unless $ref && $ref->priv == 9; + $ref = DXUser->get($myalias); + die "$myalias missing, run the create_sysop.pl script and please RTFM" unless $ref && $ref->priv == 9; +} + # start listening for incoming messages/connects -dbg('err', "starting listener ..."); -Msg->new_server("$clusteraddr", $clusterport, \&login); +dbg("starting listeners ..."); +my $conn = IntMsg->new_server($clusteraddr, $clusterport, \&login); +$conn->conns("Server $clusteraddr/$clusterport using IntMsg"); +push @listeners, $conn; +dbg("Internal port: $clusteraddr $clusterport using IntMsg"); +foreach my $l (@main::listen) { + no strict 'refs'; + my $pkg = $l->[2] || 'ExtMsg'; + my $login = $l->[3] || 'login'; + + $conn = $pkg->new_server($l->[0], $l->[1], \&{"${pkg}::${login}"}); + $conn->conns("Server $l->[0]/$l->[1] using ${pkg}::${login}"); + push @listeners, $conn; + dbg("External Port: $l->[0] $l->[1] using ${pkg}::${login}"); +} + +dbg("AGW Listener") if $AGWMsg::enable; +AGWrestart(); + +dbg("UDP Listener") if $UDPMsg::enable; +UDPMsg::init(\&new_channel); # load bad words -dbg('err', "load badwords: " . (BadWords::load or "Ok")); +dbg("load badwords: " . (BadWords::load or "Ok")); # prime some signals -unless ($^O =~ /^MS/) { - $SIG{INT} = \&cease; - $SIG{TERM} = \&cease; +unless ($DB::VERSION) { + $SIG{INT} = $SIG{TERM} = sub { $decease = 1 }; +} + +unless ($is_win) { $SIG{HUP} = 'IGNORE'; $SIG{CHLD} = sub { $zombies++ }; - $SIG{PIPE} = sub { dbg('err', "Broken PIPE signal received"); }; - $SIG{IO} = sub { dbg('err', "SIGIO received"); }; + $SIG{PIPE} = sub { dbg("Broken PIPE signal received"); }; + $SIG{IO} = sub { dbg("SIGIO received"); }; $SIG{WINCH} = $SIG{STOP} = $SIG{CONT} = 'IGNORE'; $SIG{KILL} = 'DEFAULT'; # as if it matters.... # catch the rest with a hopeful message for (keys %SIG) { if (!$SIG{$_}) { - # dbg('chan', "Catching SIG $_"); + # dbg("Catching SIG $_") if isdbg('chan'); $SIG{$_} = sub { my $sig = shift; DXDebug::confess("Caught signal $sig"); }; } } } # start dupe system +dbg("Starting Dupe system"); DXDupe::init(); # read in system messages +dbg("Read in Messages"); DXM->init(); # read in command aliases +dbg("Read in Aliases"); CmdAlias->init(); # initialise the Geomagnetic data engine +dbg("Start WWV"); Geomag->init(); +dbg("Start WCY"); WCY->init(); # initial the Spot stuff +dbg("Starting DX Spot system"); Spot->init(); # initialise the protocol engine -dbg('err', "reading in duplicate spot and WWV info ..."); +dbg("Start Protocol Engines ..."); DXProt->init(); # put in a DXCluster node for us here so we can add users and take them away -DXNode->new($DXProt::me, $mycall, 0, 1, $DXProt::myprot_version); +$routeroot = Route::Node->new($mycall, $version*100+5300, Route::here($main::me->here)|Route::conf($main::me->conf)); + +# make sure that there is a routing OUTPUT node default file +#unless (Filter::read_in('route', 'node_default', 0)) { +# my $dxcc = $main::me->dxcc; +# $Route::filterdef->cmd($main::me, 'route', 'accept', "node_default call $mycall" ); +#} # read in any existing message headers and clean out old crap -dbg('err', "reading existing message headers ..."); +dbg("reading existing message headers ..."); DXMsg->init(); DXMsg::clean_old(); # read in any cron jobs -dbg('err', "reading cron jobs ..."); +dbg("reading cron jobs ..."); DXCron->init(); # read in database descriptors -dbg('err', "reading database descriptors ..."); +dbg("reading database descriptors ..."); DXDb::load(); # starting local stuff -dbg('err', "doing local initialisation ..."); +dbg("doing local initialisation ..."); +QSL::init(1); eval { Local::init(); }; -dbg('local', "Local::init error $@") if $@; - -# print various flags -#dbg('err', "seful info - \$^D: $^D \$^W: $^W \$^S: $^S \$^P: $^P"); +dbg("Local::init error $@") if $@; # this, such as it is, is the main loop! -dbg('err', "orft we jolly well go ..."); +dbg("orft we jolly well go ..."); +my $script = new Script "startup"; +$script->run($main::me) if $script; #open(DB::OUT, "|tee /tmp/aa"); for (;;) { # $DB::trace = 1; - Msg->event_loop(1, 0.1); + Msg->event_loop(10, 0.010); my $timenow = time; - process_inqueue(); # read in lines from the input queue and despatch them + + DXChannel::process(); + # $DB::trace = 0; # do timed stuff, ongoing processing happens one a second if ($timenow != $systime) { - reap if $zombies; - $systime = $timenow; + reap() if $zombies; + IsoTime::update($systime = $timenow); DXCron::process(); # do cron jobs DXCommandmode::process(); # process ongoing command mode stuff + DXXml::process(); DXProt::process(); # process ongoing ak1a pcxx stuff DXConnect::process(); DXMsg::process(); DXDb::process(); DXUser::process(); DXDupe::process(); - + AGWMsg::process(); + eval { Local::process(); # do any localised processing }; - dbg('local', "Local::process error $@") if $@; + dbg("Local::process error $@") if $@; } if ($decease) { last if --$decease <= 0;