change node disconnection code to improve node clearouts
[spider.git] / perl / DXProtHandle.pm
index 51585f01facee34ea0e1cabc536132d9c1bafaa6..eacd9225d7ca197a9c0acf4b3accbd9968574d35 100644 (file)
@@ -4,7 +4,7 @@
 #
 # Copyright (c) 1998-2006 Dirk Koopman G1TLH
 #
-# $Id$
+#
 #
 
 package DXProt;
@@ -41,13 +41,17 @@ use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restim
                        $last_hour $last10 %eph  %pings %rcmds $ann_to_talk
                        $pingint $obscount %pc19list $chatdupeage $chatimportfn
                        $investigation_int $pc19_version $myprot_version
-                       %nodehops $baddx $badspotter $badnode $censorpc $rspfcheck
+                       %nodehops $baddx $badspotter $badnode $censorpc
                        $allowzero $decode_dk0wcy $send_opernam @checklist
                        $eph_pc15_restime $pc9x_past_age $pc9x_future_age
+                       $pc10_dupe_age
                   );
 
-$pc9x_past_age = 15*60;                        # maximum age in the past of a px9x
+$pc9x_past_age = 62*60;                        # maximum age in the past of a px9x (a config record might be the only
+                                                               # thing a node might send - once an hour)
 $pc9x_future_age = 5*60;               # maximum age in the future ditto
+$pc10_dupe_age = 45;                   # just something to catch duplicate PC10->PC93 conversions
+
 
 # incoming talk commands
 sub handle_10
@@ -57,8 +61,10 @@ sub handle_10
        my $line = shift;
        my $origin = shift;
 
-       # rsfp check
-       return if $rspfcheck and !$self->rspfcheck(0, $_[6], $_[1]);
+       # this is to catch loops caused by bad software ...
+       if (eph_dup($line, $pc10_dupe_age)) {
+               return;
+       }
 
        # will we allow it at all?
        if ($censorpc) {
@@ -105,42 +111,8 @@ sub handle_10
        RouteDB::update($_[6], $self->{call});
 #      RouteDB::update($to, $_[6]);
 
-       # convert this to a PC93 and process it as such
-       $self->normal(pc93($to, $from, $via, $_[3], $_[6]));
-       return;
-
-       # this is all redundant but kept for now for reference
-
-       # it is here and logged on
-       $dxchan = DXChannel::get($main::myalias) if $to eq $main::mycall;
-       $dxchan = DXChannel::get($to) unless $dxchan;
-       if ($dxchan && $dxchan->is_user) {
-               $_[3] =~ s/\%5E/^/g;
-               $dxchan->talk($from, $to, $via, $_[3]);
-               return;
-       }
-
-       # is it elsewhere, visible on the cluster via the to address?
-       # note: this discards the via unless the to address is on
-       # the via address
-       my ($ref, $vref);
-       if ($ref = Route::get($to)) {
-               $vref = Route::Node::get($via) if $via;
-               $vref = undef unless $vref && grep $to eq $_, $vref->users;
-               $ref->dxchan->talk($from, $to, $vref ? $via : undef, $_[3], $_[6]);
-               return;
-       }
-
-       # can we see an interface to send it down?
-
-       # not visible here, send a message of condolence
-       $vref = undef;
-       $ref = Route::get($from);
-       $vref = $ref = Route::Node::get($_[6]) unless $ref;
-       if ($ref) {
-               $dxchan = $ref->dxchan;
-               $dxchan->talk($main::mycall, $from, $vref ? $vref->call : undef, $dxchan->msg('talknh', $to) );
-       }
+       # convert this to a PC93, coming from mycall with origin set and process it as such
+       $main::me->normal(pc93($to, $from, $via, $_[3], $_[6]));
 }
 
 # DX Spot handling
@@ -159,9 +131,6 @@ sub handle_11
                }
        }
 
-       # rsfp check
-       #                       return if $rspfcheck and !$self->rspfcheck(1, $_[7], $_[6]);
-
        # is the spotted callsign blank? This should really be trapped earlier but it
        # could break other protocol sentences. Also check for lower case characters.
        if ($_[2] =~ /^\s*$/) {
@@ -319,8 +288,6 @@ sub handle_12
        my $line = shift;
        my $origin = shift;
 
-       #                       return if $rspfcheck and !$self->rspfcheck(1, $_[5], $_[1]);
-
        # announce duplicate checking
        $_[3] =~ s/^\s+//;                      # remove leading blanks
 
@@ -393,7 +360,7 @@ sub handle_15
        my $origin = shift;
 
        if (eph_dup($line, $eph_pc15_restime)) {
-               dbg("PCPROT: Ephemeral dup, dropped") if isdbg('chanerr');
+               return;
        } else {
                unless ($self->{isolate}) {
                        DXChannel::broadcast_nodes($line, $self) if $line =~ /\^H\d+\^?~?$/; # send it to everyone but me
@@ -435,7 +402,6 @@ sub handle_16
        }
 
        if (eph_dup($line)) {
-               dbg("PCPROT: dup PC16 detected") if isdbg('chanerr');
                return;
        }
 
@@ -495,18 +461,17 @@ sub handle_16
                        push @rout, @ans if $h && @ans;
                }
 
-               # send info to all logged in thingies
-               $self->tell_login('loginu', "$ncall: $call") if DXUser->get_current($ncall)->is_local_node;
-               $self->tell_buddies('loginb', $call, $ncall);
-
                # add this station to the user database, if required
-#              $call =~ s/-\d+$//o;    # remove ssid for users
-               my $user = DXUser->get_current($call);
-               $user = DXUser->new($call) if !$user;
+               my $user = DXUser->get_current($ncall);
+               $user = DXUser->new($call) unless $user;
                $user->homenode($parent->call) if !$user->homenode;
                $user->node($parent->call);
                $user->lastin($main::systime) unless DXChannel::get($call);
                $user->put;
+
+               # send info to all logged in thingies
+               $self->tell_login('loginu', "$ncall: $call") if $user->is_local_node;
+               $self->tell_buddies('loginb', $call, $ncall);
        }
        if (@rout) {
                $self->route_pc16($origin, $line, $parent, @rout) if @rout;
@@ -574,11 +539,11 @@ sub handle_17
        $parent->del_user($uref);
 
        # send info to all logged in thingies
-       $self->tell_login('logoutu', "$ncall: $ucall") if DXUser->get_current($ncall)->is_local_node;
+       my $user = DXUser->get_current($ncall);
+       $self->tell_login('logoutu', "$ncall: $ucall") if $user && $user->is_local_node;
        $self->tell_buddies('logoutb', $ucall, $ncall);
 
        if (eph_dup($line)) {
-               dbg("PCPROT: dup PC17 detected") if isdbg('chanerr');
                return;
        }
 
@@ -598,16 +563,15 @@ sub handle_18
        my $parent = Route::Node::get($self->{call});
 
        # record the type and version offered
-       if ($_[1] =~ /DXSpider Version: (\d+\.\d+)/) {
-               $self->{version} = 53 + $1;
-               $self->user->version(53 + $1);
-               $parent->version(0 + $1);
-               dbg("DXSpider version $1");
+       if (my ($version) = $_[1] =~ /DXSpider Version: (\d+\.\d+)/) {
+               $self->{version} = 53 + $version;
+               $self->user->version(53 + $version);
+               $parent->version(0 + $version);
                my ($build) = $_[1] =~ /Build: (\d+(?:\.\d+)?)/;
                $self->{build} = 0 + $build;
                $self->user->build(0 + $build);
                $parent->build(0 + $build);
-               dbg("DXSpider build $build");
+               dbg("DXSpider version $version build $build");
                unless ($self->is_spider) {
                        dbg("Change U " . $self->user->sort . " C $self->{sort} -> S");
                        $self->user->sort('S');
@@ -615,9 +579,15 @@ sub handle_18
                        $self->sort('S');
                }
 #              $self->{handle_xml}++ if DXXml::available() && $_[1] =~ /\bxml/;
-               if (!$self->{isolate} && $_[1] =~ /\bpc9x/) {
-                       $self->{do_pc9x} = 1;
-                       dbg("Do px9x set on $self->{call}");
+               if ($_[1] =~ /\bpc9x/) {
+                       if ($self->{isolate}) {
+                               dbg("pc9x recognised, but $self->{call} is isolated, using old protocol");
+                       } elsif (!$self->user->wantpc9x) {
+                               dbg("pc9x explicitly switched off on $self->{call}, using old protocol");
+                       } else {
+                               $self->{do_pc9x} = 1;
+                               dbg("Do px9x set on $self->{call}");
+                       }
                }
        } else {
                dbg("Unknown software");
@@ -861,7 +831,6 @@ sub handle_21
        }
 
        if (eph_dup($line)) {
-               dbg("PCPROT: dup PC21 detected") if isdbg('chanerr');
                return;
        }
 
@@ -906,10 +875,6 @@ sub handle_23
                }
        }
 
-       # only do a rspf check on PC23 (not 27)
-       if ($pcno == 23) {
-               return if $rspfcheck and !$self->rspfcheck(1, $_[8], $_[7])
-       }
 
        # do some de-duping
        my $d = cltounix($_[1], sprintf("%02d18Z", $_[2]));
@@ -969,7 +934,6 @@ sub handle_24
        return unless $nref || $uref; # if we don't know where they are, it's pointless sending it on
 
        if (eph_dup($line)) {
-               dbg("PCPROT: Dup PC24 ignored\n") if isdbg('chanerr');
                return;
        }
 
@@ -1050,7 +1014,7 @@ sub handle_34
        my $line = shift;
        my $origin = shift;
        if (eph_dup($line, $eph_pc34_restime)) {
-               dbg("PCPROT: dupe PC34, ignored") if isdbg('chanerr');
+               return;
        } else {
                $self->process_rcmd($_[1], $_[2], $_[2], $_[3]);
        }
@@ -1123,7 +1087,6 @@ sub handle_41
 
        my $l = "PC41^$call^$sort";
        if (eph_dup($l, $eph_info_restime)) {
-               dbg("PCPROT: dup PC41, ignored") if isdbg('chanerr');
                return;
        }
 
@@ -1204,7 +1167,6 @@ sub handle_49
        my $origin = shift;
 
        if (eph_dup($line)) {
-               dbg("PCPROT: Dup PC49 ignored\n") if isdbg('chanerr');
                return;
        }
 
@@ -1263,7 +1225,6 @@ sub handle_51
                RouteDB::update($from, $self->{call});
 
                if (eph_dup($line)) {
-                       dbg("PCPROT: dup PC51 detected") if isdbg('chanerr');
                        return;
                }
                # route down an appropriate thingy
@@ -1451,20 +1412,25 @@ sub check_pc9x_t
                if ($parent->call ne $main::mycall) {
                        my $lastid = $parent->lastid->{$pc} || 0;
                        if ($t < $lastid) {
-                               if ($lastid-86400+$t > $pc9x_past_age) {
-                                       dbg("PCPROT: dup id on $t <= $lastid (midnight rollover), ignored") if isdbg('chanerr');
+                               if ($t+86400-$lastid > $pc9x_past_age) {
+                                       dbg("PCPROT: dup id on $t <= $lastid, ignored") if isdbg('chanerr');
                                        return;
                                }
-                       }
-                       if ($lastid >= $t) {
-                               dbg("PCPROT: dup id on $call $lastid >= $t, ignored") if isdbg('chanerr');
+                       } elsif ($t == $lastid) {
+                               dbg("PCPROT: dup id on $t == $lastid, ignored") if isdbg('chanerr');
                                return;
+                       } else {
+                               # $t > $lastid, check that the timestamp offered isn't too far away from 'now'
+                               if ($t+$main::systime_daystart-$main::systime > $pc9x_future_age ) {
+                                       dbg("PCPROT: id $t too far in the future, ignored") if isdbg('chanerr');
+                                       return;
+                               }
                        }
                }
        } elsif ($create) {
                $parent = Route::Node->new($call);
        }
-       $parent->lastid->{$pc} = $t;
+       $parent->lastid->{$pc} = $t if $parent;
 
        return $parent;
 }
@@ -1493,9 +1459,17 @@ sub handle_92
        }
 
        if ($pcall eq $self->{call} && $self->{state} eq 'init') {
-               $self->state('init92');
-               $self->{do_pc9x} = 1;
-               dbg("Do pc9x set on $pcall");
+               if ($self->{isolate}) {
+                       dbg("PC9x received, but $pcall is isolated, ignored");
+                       return;
+               } elsif (!$self->user->wantpc9x) {
+                       dbg("PC9x explicitly switched off on $pcall, ignored");
+                       return;
+               } else {
+                       $self->state('init92');
+                       $self->{do_pc9x} = 1;
+                       dbg("Do pc9x set on $pcall");
+               }
        }
        unless ($self->{do_pc9x}) {
                dbg("PCPROT: PC9x come in from non-PC9x node, ignored") if isdbg('chanerr');
@@ -1552,6 +1526,9 @@ sub handle_92
                }
        } elsif ($sort eq 'A' || $sort eq 'D' || $sort eq 'C') {
 
+               # remember the last channel we arrived on
+               $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+
                # this is the main route section
                # here is where all the routes are created and destroyed
 
@@ -1575,20 +1552,28 @@ sub handle_92
                                                dbg("PCPROT: $call looped back onto $main::mycall, ignored") if isdbg('chanerr');
                                                return;
                                        }
+                                       # this is only accepted from my "self".
+                                       # this also kills configs from PC92 nodes with external PC19 nodes that are also
+                                       # locally connected. Local nodes always take precedence.
+                                       if (DXChannel::get($call) && $call ne $self->{call}) {
+                                               dbg("PCPROT: locally connected node $call from other another node $self->{call}, ignored") if isdbg('chanerr');
+                                               return;
+                                       }
                                        if ($is_extnode) {
-                                               # this is only accepted from my "self"
-                                               if (DXChannel::get($call) && $call ne $self->{call}) {
-                                                       dbg("PCPROT: locally connected node config for $call from other another node $self->{call}, ignored") if isdbg('chanerr');
-                                                       return;
-                                               }
                                                # reparent to external node (note that we must have received a 'C' or 'A' record
                                                # from the true parent node for this external before we get one for the this node
                                                unless ($parent = Route::Node::get($call)) {
-                                                       dbg("PCPROT: no previous C or A for this external node received, ignored") if isdbg('chanerr');
-                                                       return;
+                                                       if ($is_extnode && $oparent) {
+                                                               @radd =  _add_thingy($oparent, $ent[0]);
+                                                               $parent = $radd[0];
+                                                       } else {
+                                                               dbg("PCPROT: no previous C or A for this external node received, ignored") if isdbg('chanerr');
+                                                               return;
+                                                       }
                                                }
                                                $parent = check_pc9x_t($call, $t, 92) || return;
                                                $parent->via_pc92(1);
+                                               $parent->PC92C_dxchan($self->{call});
                                        }
                                } else {
                                        dbg("PCPROT: must be mycall or external node as first entry, ignored") if isdbg('chanerr');
@@ -1597,6 +1582,7 @@ sub handle_92
                                $parent->here(Route::here($here));
                                $parent->version($version) if $version && $version > $parent->version;
                                $parent->build($build) if $build && $build > $parent->build;
+                               $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
                                shift @ent;
                        }
                }
@@ -1625,10 +1611,10 @@ sub handle_92
 
                        # we only reset obscounts on config records
                        $oparent->reset_obs;
-                       dbg("ROUTE: reset obscount on $pcall now " . $oparent->obscount) if isdbg('route');
+                       dbg("ROUTE: reset obscount on $pcall now " . $oparent->obscount) if isdbg('obscount');
                        if ($oparent != $parent) {
                                $parent->reset_obs;
-                               dbg("ROUTE: reset obscount on $parent->{call} now " . $parent->obscount) if isdbg('route');
+                               dbg("ROUTE: reset obscount on $parent->{call} now " . $parent->obscount) if isdbg('obscount');
                        }
 
                        #
@@ -1701,6 +1687,13 @@ sub handle_93
                dbg("PCPROT: invalid callsign string '$_[1]', ignored") if isdbg('chanerr');
                return;
        }
+
+       # remember that we are converting PC10->PC93
+       unless ($self->{do_pc9x}) {
+               dbg("PCPROT: PC9x come in from non-PC9x node, ignored") if isdbg('chanerr');
+               return;
+       }
+
        my $t = $_[2];
        my $parent = check_pc9x_t($pcall, $t, 93, 1) || return;
 
@@ -1711,6 +1704,11 @@ sub handle_93
        my $onode = $_[7];
        $onode = $pcall if @_ <= 8;
 
+       # this is catch loops caused by bad software ...
+       if (eph_dup("PC93|$from|$text|$onode", $pc10_dupe_age)) {
+               return;
+       }
+
        # will we allow it at all?
        if ($censorpc) {
                my @bad;
@@ -1733,21 +1731,33 @@ sub handle_93
                my $dxchan;
                $dxchan = DXChannel::get($main::myalias) if $to eq $main::mycall;
                $dxchan = DXChannel::get($to) unless $dxchan;
-               if ($dxchan && $dxchan->is_user) {
-                       $dxchan->talk($from, $to, $via, $text, $onode);
-                       return;
+               # check it...
+               if ($dxchan) {
+                       if (ref $dxchan && $dxchan->isa('DXChannel')) {
+                               if ($dxchan->is_user) {
+                                       $dxchan->talk($from, $to, $via, $text, $onode);
+                                       return;
+                               }
+                       } else {
+                               dbg("ERROR: $to -> $dxchan is not a DXChannel! (local talk)");
+                       }
                }
 
                # convert to PC10 talks where appropriate
                my $ref = Route::get($to);
                if ($ref) {
-                       my @dxchan = $ref->alldxchan;
-                       for $dxchan (@dxchan) {
+                       # just go for the "best" one for now (rather than broadcast)
+                       $dxchan = $ref->dxchan;
+
+                       # check it...
+                       if (ref $dxchan && $dxchan->isa('DXChannel')) {
                                if ($dxchan->{do_pc9x}) {
                                        $dxchan->send($line);
                                } else {
                                        $dxchan->talk($from, $to, $via, $text, $onode);
                                }
+                       } else {
+                               dbg("ERROR: $to -> $dxchan is not a DXChannel! (convert to pc10)");
                        }
                        return;
                }
@@ -1759,7 +1769,7 @@ sub handle_93
                my $wx = uc $to eq 'WX' ? '1' : '0';
                my $local = $via eq 'LOCAL' ? '*' : $via;
 
-               $self->send_announce(1, pc12($from, $text, $local, $via, $sysop, $wx, $pcall), $from, $local, $text, $sysop, $pcall, $wx, $via eq 'LOCAL' ? $via : undef);
+               $self->send_announce(1, pc12($from, $text, $local, $sysop, $wx, $pcall), $from, $local, $text, $sysop, $pcall, $wx, $via eq 'LOCAL' ? $via : undef);
                return if $via eq 'LOCAL';
        } else {
                # chat messages to non-pc9x nodes
@@ -1783,9 +1793,7 @@ sub handle_default
        my $line = shift;
        my $origin = shift;
 
-       if (eph_dup($line)) {
-               dbg("PCPROT: Ephemeral dup, dropped") if isdbg('chanerr');
-       } else {
+       unless (eph_dup($line)) {
                if ($pcno >= 90) {
                        my $pcall = $_[1];
                        unless (is_callsign($pcall)) {