make sure the version no is updated correctly
[spider.git] / perl / DXProt.pm
index c7cd41b77cec153ca0b6a83c5aefddafab9dee2b..3f4575aa3907ca18ac361c364d5978eaff05a2e9 100644 (file)
@@ -15,7 +15,6 @@ use DXUtil;
 use DXChannel;
 use DXUser;
 use DXM;
-use DXCluster;
 use DXProtVars;
 use DXCommandmode;
 use DXLog;
@@ -30,11 +29,14 @@ use Geomag;
 use WCY;
 use Time::HiRes qw(gettimeofday tv_interval);
 use BadWords;
+use DXHash;
+use Route;
+use Route::Node;
 
 use strict;
 use vars qw($me $pc11_max_age $pc23_max_age
                        $last_hour %pings %rcmds
-                       %nodehops @baddx $baddxfn $censorpc
+                       %nodehops $baddx $badspotter $badnode $censorpc
                        $allowzero $decode_dk0wcy $send_opernam @checklist);
 
 $me = undef;                                   # the channel id for this cluster
@@ -45,10 +47,11 @@ $last_hour = time;                          # last time I did an hourly periodic update
 %pings = ();                    # outstanding ping requests outbound
 %rcmds = ();                    # outstanding rcmd requests outbound
 %nodehops = ();                 # node specific hop control
-@baddx = ();                    # list of illegal spotted callsigns
-$censorpc = 0;                                 # Do a BadWords::check on text fields and reject things
-
-$baddxfn = "$main::data/baddx.pl";
+$censorpc = 1;                                 # Do a BadWords::check on text fields and reject things
+                                                               # loads of 'bad things'
+$baddx = new DXHash "baddx";
+$badspotter = new DXHash "badspotter";
+$badnode = new DXHash "badnode";
 
 @checklist = 
 (
@@ -177,10 +180,8 @@ sub init
        do "$main::data/hop_table.pl" if -e "$main::data/hop_table.pl";
        confess $@ if $@;
        $me->{sort} = 'S';    # S for spider
-
-       # load the baddx file
-       do "$baddxfn" if -e "$baddxfn";
-       print "$@\n" if $@;
+       $me->{priv} = 9;
+#      $Route::Node::me->adddxchan($me);
 }
 
 #
@@ -190,6 +191,12 @@ sub init
 sub new 
 {
        my $self = DXChannel::alloc(@_);
+
+       # add this node to the table, the values get filled in later
+       my $pkg = shift;
+       my $call = shift;
+       $main::routeroot->add($call, '0000', Route::here(1)) if $call ne $main::mycall;
+
        return $self;
 }
 
@@ -216,6 +223,7 @@ sub start
        $self->{wwvfilter} = Filter::read_in('wwv', $call, 0) || Filter::read_in('wwv', 'node_default', 0);
        $self->{wcyfilter} = Filter::read_in('wcy', $call, 0) || Filter::read_in('wcy', 'node_default', 0);
        $self->{annfilter} = Filter::read_in('ann', $call, 0) || Filter::read_in('ann', 'node_default', 0) ;
+       $self->{routefilter} = Filter::read_in('route', $call, 0) || Filter::read_in('route', 'node_default', 0) ;
 
 
        # get the INPUT filters (these only pertain to Clusters)
@@ -223,6 +231,7 @@ sub start
        $self->{inwwvfilter} = Filter::read_in('wwv', $call, 1) || Filter::read_in('wwv', 'node_default', 1);
        $self->{inwcyfilter} = Filter::read_in('wcy', $call, 1) || Filter::read_in('wcy', 'node_default', 1);
        $self->{inannfilter} = Filter::read_in('ann', $call, 1) || Filter::read_in('ann', 'node_default', 1);
+       $self->{inroutefilter} = Filter::read_in('route', $call, 1) || Filter::read_in('route', 'node_default', 1);
        
        # set unbuffered and no echo
        $self->send_now('B',"0");
@@ -238,16 +247,13 @@ sub start
 
        # send initialisation string
        unless ($self->{outbound}) {
-#              $self->send(pc38()) if DXNode->get_all();
                $self->send(pc18());
                $self->{lastping} = $main::systime;
        } else {
-               # remove from outstanding connects queue
-               @main::outstanding_connects = grep {$_->{call} ne $call} @main::outstanding_connects;
-               $self->{lastping} = $main::systime + $self->pingint / 2;
+               $self->{lastping} = $main::systime + ($self->pingint / 2);
        }
        $self->state('init');
-       $self->pc50_t(time);
+       $self->{pc50_t} = $main::systime;
 
        # send info to all logged in thingies
        $self->tell_login('loginn');
@@ -332,8 +338,14 @@ sub normal
                        }
                        
                        # if this is a 'nodx' node then ignore it
-                       if (grep $field[7] =~ /^$_/,  @DXProt::nodx_node) {
-                               dbg('chan', "PCPROT: Bad DXNode, dropped");
+                       if ($badnode->in($field[7])) {
+                               dbg('chan', "PCPROT: Bad Node, dropped");
+                               return;
+                       }
+                       
+                       # if this is a 'bad spotter' user then ignore it
+                       if ($badspotter->in($field[6])) {
+                               dbg('chan', "PCPROT: Bad Spotter, dropped");
                                return;
                        }
                        
@@ -346,13 +358,18 @@ sub normal
                        }
 
                        # is it 'baddx'
-                       if (grep $field[2] eq $_, @baddx) {
+                       if ($baddx->in($field[2])) {
                                dbg('chan', "PCPROT: Bad DX spot, ignored");
                                return;
                        }
                        
                        # do some de-duping
                        $field[5] =~ s/^\s+//;      # take any leading blanks off
+                       $field[2] = unpad($field[2]);   # take off leading and trailing blanks from spotted callsign
+                       if ($field[2] =~ /BUST\w*$/) {
+                               dbg('chan', "PCPROT: useless 'BUSTED' spot");
+                               return;
+                       }
                        if (Spot::dup($field[1], $field[2], $d, $field[5])) {
                                dbg('chan', "PCPROT: Duplicate Spot ignored\n");
                                return;
@@ -364,8 +381,19 @@ sub normal
                                        return;
                                }
                        }
+
+                       my @spot = Spot::prepare($field[1], $field[2], $d, $field[5], $field[6], $field[7]);
+                       # global spot filtering on INPUT
+                       if ($self->{inspotsfilter}) {
+                               my ($filter, $hops) = $self->{inspotsfilter}->it(@spot);
+                               unless ($filter) {
+                                       dbg('chan', "PCPROT: Rejected by filter");
+                                       return;
+                               }
+                       }
                        
-                       my @spot = Spot::add($field[1], $field[2], $d, $field[5], $field[6], $field[7]);
+                       # add it 
+                       Spot::add(@spot);
 
             #
                        # @spot at this point contains:-
@@ -394,21 +422,25 @@ sub normal
                                        my $node;
                                        my $to = $user->homenode;
                                        my $last = $user->lastoper || 0;
-                                       if ($send_opernam && $main::systime > $last + $DXUser::lastoperinterval && $to && ($node = DXCluster->get_exact($to)) ) {
+                                       if ($send_opernam && $main::systime > $last + $DXUser::lastoperinterval && $to && ($node = Route::Node::get($to)) ) {
                                                my $cmd = "forward/opernam $spot[4]";
                                                # send the rcmd but we aren't interested in the replies...
-                                               if ($node && $node->dxchan && $node->dxchan->is_clx) {
+                                               my $dxchan = $node->dxchan;
+                                               if ($dxchan && $dxchan->is_clx) {
                                                        route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
                                                } else {
                                                        route(undef, $to, pc34($main::mycall, $to, $cmd));
                                                }
                                                if ($to ne $field[7]) {
                                                        $to = $field[7];
-                                                       $node = DXCluster->get_exact($to);
-                                                       if ($node && $node->dxchan && $node->dxchan->is_clx) {
-                                                               route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
-                                                       } else {
-                                                               route(undef, $to, pc34($main::mycall, $to, $cmd));
+                                                       $node = Route::Node::get($to);
+                                                       if ($node) {
+                                                               $dxchan = $node->dxchan;
+                                                               if ($dxchan && $dxchan->is_clx) {
+                                                                       route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
+                                                               } else {
+                                                                       route(undef, $to, pc34($main::mycall, $to, $cmd));
+                                                               }
                                                        }
                                                }
                                                $user->lastoper($main::systime);
@@ -494,128 +526,88 @@ sub normal
                }
                
                if ($pcno == 16) {              # add a user
-                       my $node = DXCluster->get_exact($field[1]); 
+
+                       # general checks
                        my $dxchan;
-                       if (!$node && ($dxchan = DXChannel->get($field[1]))) {
-                               # add it to the node table if it isn't present and it's
-                               # connected locally
-                               $node = DXNode->new($dxchan, $field[1], 0, 1, 5400);
-                               dbg('chan', "PCPROT: $field[1] no PC19 yet, autovivified as node");
-#                              broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate};
-                               
-                       }
-                       if ($field[1] eq $main::mycall || $field[2] eq $main::mycall) {
+                       my $ncall = $field[1];
+                       my $newline = "PC16^";
+                       
+                       if ($ncall eq $main::mycall) {
                                dbg('chan', "PCPROT: trying to alter config on this node from outside!");
                                return;
                        }
-                       if ($field[2] eq $main::myalias && DXChannel->get($field[1])) {
-                               dbg('chan', "PCPROT: trying to connect sysop from outside!");
-                               return;
-                       }
-                       unless ($node) {
-                               dbg('chan', "PCPROT: Node $field[1] not in config");
-                               return;
-                       }
-                       unless ($node->isa('DXNode')) {
-                               dbg('chan', "PCPROT: $field[1] is not a node");
-                               return;
-                       }
-                       if ($node->dxchan != $self) {
-                               dbg('chan', "PCPROT: $field[1] came in on wrong channel");
+                       $dxchan = DXChannel->get($ncall);
+                       if ($dxchan && $dxchan ne $self) {
+                               dbg('chan', "PCPROT: PC16 from $self->{call} trying to alter locally connected $ncall, ignored!");
                                return;
                        }
-                       if (($dxchan = DXChannel->get($field[1])) && $dxchan != $self) {
-                               dbg('chan', "PCPROT: $field[1] connected locally");
+                       my $parent = Route::Node::get($ncall); 
+                       unless ($parent) {
+                               dbg('chan', "PCPROT: Node $ncall not in config");
                                return;
                        }
                        my $i;
-                                               
+                       my @rout;
                        for ($i = 2; $i < $#field; $i++) {
-                               my ($call, $confmode, $here) = $field[$i] =~ /^(\S+) (\S) (\d)/o;
-                               next unless $call && $confmode && defined $here && is_callsign($call);
-                               my $ref = DXCluster->get_exact($call); 
-                               if ($ref) {
-                                       if ($ref->isa('DXNode')) {
-                                               dbg('chan', "PCPROT: $call is a node");
-                                               next;
-                                       }
-                                       my $rcall = $ref->mynode->call;
-                                       dbg('chan', "PCPROT: already have $call on $rcall");
-                                       next;
-                               }
-                               
-                               $confmode = $confmode eq '*';
-                               DXNodeuser->new($self, $node, $call, $confmode, $here);
+                               my ($call, $conf, $here) = $field[$i] =~ /^(\S+) (\S) (\d)/o;
+                               next unless $call && $conf && defined $here && is_callsign($call);
+                               $conf = $conf eq '*';
+
+                               push @rout, $parent->add_user($call, Route::here($here)|Route::conf($conf));
                                
                                # 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;
-                               $user->homenode($node->call) if !$user->homenode;
-                               $user->node($node->call);
+                               $user->homenode($parent->call) if !$user->homenode;
+                               $user->node($parent->call);
                                $user->lastin($main::systime) unless DXChannel->get($call);
                                $user->put;
                        }
+
                        
                        # queue up any messages (look for privates only)
                        DXMsg::queue_msg(1) if $self->state eq 'normal';     
-                       last SWITCH;
+
+                       dbg('route', "B/C PC16 on $ncall for: " . join(',', map{$_->call} @rout)) if @rout;
+                       $self->route_pc16($parent, @rout) if @rout;
+                       return;
                }
                
                if ($pcno == 17) {              # remove a user
-                       my $node = DXCluster->get_exact($field[2]);
                        my $dxchan;
-                       if (!$node && ($dxchan = DXChannel->get($field[2]))) {
-                               # add it to the node table if it isn't present and it's
-                               # connected locally
-                               $node = DXNode->new($dxchan, $field[2], 0, 1, 5400);
-                               dbg('chan', "PCPROT: $field[2] no PC19 yet, autovivified as node");
-#                              broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate};
-                       }
-                       if ($field[1] eq $main::mycall || $field[2] eq $main::mycall) {
+                       my $ncall = $field[2];
+                       if ($ncall eq $main::mycall) {
                                dbg('chan', "PCPROT: trying to alter config on this node from outside!");
                                return;
                        }
-                       if ($field[1] eq $main::myalias && DXChannel->get($field[1])) {
-                               dbg('chan', "PCPROT: trying to disconnect sysop from outside!");
-                               return;
-                       }
-                       unless ($node) {
-                               dbg('chan', "PCPROT: Node $field[2] not in config");
+                       $dxchan = DXChannel->get($ncall);
+                       if ($dxchan && $dxchan ne $self) {
+                               dbg('chan', "PCPROT: PC17 from $self->{call} trying to alter locally connected $ncall, ignored!");
                                return;
                        }
-                       unless ($node->isa('DXNode')) {
-                               dbg('chan', "PCPROT: $field[2] is not a node");
+                       my $parent = Route::Node::get($ncall);
+                       unless ($parent) {
+                               dbg('chan', "PCPROT: Route::Node $ncall not in config");
                                return;
                        }
-                       if ($node->dxchan != $self) {
-                               dbg('chan', "PCPROT: $field[2] came in on wrong channel");
-                               return;
-                       }
-                       if (($dxchan = DXChannel->get($field[2])) && $dxchan != $self) {
-                               dbg('chan', "PCPROT: $field[2] connected locally");
-                               return;
-                       }
-                       my $ref = DXCluster->get_exact($field[1]);
-                       if ($ref) {
-                               $ref->del;
-                       } else {
-                               dbg('chan', "PCPROT: $field[1] not known" );
-                               return;
-                       }
-                       last SWITCH;
+                       my @rout = $parent->del_user($field[1]);
+                       dbg('route', "B/C PC17 on $ncall for: $field[1]");
+                       $self->route_pc17($parent, @rout) if @rout;
+                       return;
                }
                
                if ($pcno == 18) {              # link request
                        $self->state('init');   
 
                        # first clear out any nodes on this dxchannel
-                       my @gonenodes = map { $_->dxchan == $self ? $_ : () } DXNode::get_all();
-                       foreach my $node (@gonenodes) {
-                               next if $node->dxchan == $DXProt::me;
-                               broadcast_ak1a(pc21($node->call, 'Gone, re-init') , $self) unless $self->{isolate}; 
-                               $node->del();
+                       my $parent = Route::Node::get($self->{call});
+                       my @rout;
+                       for ($parent->nodes) {
+                               my $r = Route::Node::get($_);
+                               push @rout, $r->del_node if $r;
                        }
+                       $self->route_pc21(@rout, $parent);
                        $self->send_local_config();
                        $self->send(pc20());
                        return;             # we don't pass these on
@@ -624,39 +616,43 @@ sub normal
                if ($pcno == 19) {              # incoming cluster list
                        my $i;
                        my $newline = "PC19^";
+
+                       # new routing list
+                       my @rout;
+                       my $parent = Route::Node::get($self->{call});
+
+                       # parse the PC19
                        for ($i = 1; $i < $#field-1; $i += 4) {
                                my $here = $field[$i];
                                my $call = uc $field[$i+1];
-                               my $confmode = $field[$i+2];
+                               my $conf = $field[$i+2];
                                my $ver = $field[$i+3];
-                               next unless defined $here && defined $confmode && is_callsign($call);
-
-                               $ver = 5400 if !$ver && $allowzero;
-                               
-                               # now check the call over
-                               my $node = DXCluster->get_exact($call);
-                               if ($node) {
-                                       my $dxchan;
-                                       if (($dxchan = DXChannel->get($call)) && $dxchan != $self) {
-                                               dbg('chan', "PCPROT: $call connected locally");
-                                       }
-                                   if ($node->dxchan != $self) {
-                                               dbg('chan', "PCPROT: $call come in on wrong channel");
-                                               next;
-                                       }
-                                       my $rcall = $node->mynode->call;
-                                       dbg('chan', "PCPROT: already have $call on $rcall");
-                                       next;
-                               }
-                               
+                               next unless defined $here && defined $conf && is_callsign($call);
                                # check for sane parameters
+                               $ver = 5000 if $ver eq '0000';
                                next if $ver < 5000; # only works with version 5 software
                                next if length $call < 3; # min 3 letter callsigns
 
-                               # add it to the nodes table and outgoing line
-                               $newline .= "$here^$call^$confmode^$ver^";
-                               DXNode->new($self, $call, $confmode, $here, $ver);
-                               
+                               # update it if required
+                               my $r;
+                               if ($parent->call eq $call && !$parent->version) {
+                                       $parent->version($ver);
+                                       $parent->flags(Route::here($here)|Route::conf($conf));
+                                       push @rout, $parent;
+                               } elsif ($parent->call ne $call) {
+                                       next if $call eq $main::mycall || $call eq $self->{call};
+
+                                       my $r = $parent->add($call, $ver, Route::here($here)|Route::conf($conf));
+                                       push @rout, $r if $r;
+                               } else {
+                                       $r = Route::Node::get($call);
+                                       if ($r && (!$r->version || $r->version eq '0000')) {
+                                               $r->version($ver);
+                                               $r->flags(Route::here($here)|Route::conf($conf));
+                                               push @rout, $r;
+                                       }
+                               }
+
                                # unbusy and stop and outgoing mail (ie if somehow we receive another PC19 without a disconnect)
                                my $mref = DXMsg::get_busy($call);
                                $mref->stop_msg($call) if $mref;
@@ -674,13 +670,11 @@ sub normal
                                $user->lastin($main::systime) unless DXChannel->get($call);
                                $user->put;
                        }
-                       
-                       return if $newline eq "PC19^";
 
-                       # add hop count 
-                       $newline .=  get_hops(19) . "^";
-                       $line = $newline;
-                       last SWITCH;
+                       dbg('route', "B/C PC19 for: " . join(',', map{$_->call} @rout)) if @rout;
+                       
+                       $self->route_pc19(@rout) if @rout;
+                       return;
                }
                
                if ($pcno == 20) {              # send local configuration
@@ -692,36 +686,29 @@ sub normal
                
                if ($pcno == 21) {              # delete a cluster from the list
                        my $call = uc $field[1];
+                       my @rout;
+                       my $parent = Route::Node::get($self->{call});
+                       
                        if ($call ne $main::mycall) { # don't allow malicious buggers to disconnect me!
-                               my $node = DXCluster->get_exact($call);
-                               if ($node) {
-                                       unless ($node->isa('DXNode')) {
-                                               dbg('chan', "PCPROT: $call is not a node");
-                                               return;
-                                       }
-                                       if ($call eq $self->{call}) {
-                                               dbg('chan', "PCPROT: Trying to disconnect myself with PC21");
-                                               return;
-                                       } 
-                                       if ($node->dxchan != $self) {
-                                               dbg('chan', "PCPROT: $call come in on wrong channel");
-                                               return;
-                                       }
-                                       my $dxchan;
-                                       if ($dxchan = DXChannel->get($call)) {
-                                               dbg('chan', "PCPROT: $call connected locally");
-                                               return;
-                                       }
-                                       $node->del();
-                               } else {
-                                       dbg('chan', "PCPROT: $call not in table, dropped");
+                               if ($call eq $self->{call}) {
+                                       dbg('chan', "PCPROT: Trying to disconnect myself with PC21");
                                        return;
                                }
+
+                               # routing objects
+                               if ($parent) {
+                                       push @rout, $parent->del_node($call);
+                               } else {
+                                       dbg('chan', "PCPROT: Route::Node $call not in config");
+                               }
                        } else {
                                dbg('chan', "PCPROT: I WILL _NOT_ be disconnected!");
                                return;
                        }
-                       last SWITCH;
+                       dbg('route', "B/C PC21 for: " . join(',', (map{$_->call} @rout))) if @rout;
+                       
+                       $self->route_pc21(@rout) if @rout;
+                       return;
                }
                
                if ($pcno == 22) {
@@ -775,7 +762,9 @@ sub normal
                
                if ($pcno == 24) {              # set here status
                        my $call = uc $field[1];
-                       my $ref = DXCluster->get_exact($call);
+                       my $ref = Route::Node::get($call);
+                       $ref->here($field[2]) if $ref;
+                       $ref = Route::User::get($call);
                        $ref->here($field[2]) if $ref;
                        last SWITCH;
                }
@@ -824,9 +813,9 @@ sub normal
                if ($pcno == 34 || $pcno == 36) { # remote commands (incoming)
                        if ($field[1] eq $main::mycall) {
                                my $ref = DXUser->get_current($field[2]);
-                               my $cref = DXCluster->get($field[2]);
+                               my $cref = Route::Node::get($field[2]);
                                Log('rcmd', 'in', $ref->{priv}, $field[2], $field[3]);
-                               unless (!$cref || !$ref || $cref->mynode->call ne $ref->homenode) {    # not allowed to relay RCMDS!
+                               unless (!$cref || !$ref || $cref->call ne $ref->homenode) {    # not allowed to relay RCMDS!
                                        if ($ref->{priv}) {     # you have to have SOME privilege, the commands have further filtering
                                                $self->{remotecmd} = 1; # for the benefit of any command that needs to know
                                                my $oldpriv = $self->{priv};
@@ -848,7 +837,7 @@ sub normal
                        } else {
                                my $ref = DXUser->get_current($field[1]);
                                if ($ref && $ref->is_clx) {
-                                       route($field[1], pc84($field[2], $field[1], $field[2], $field[3]));
+                                       $self->route($field[1], pc84($field[2], $field[1], $field[2], $field[3]));
                                } else {
                                        $self->route($field[1], $line);
                                }
@@ -871,7 +860,7 @@ sub normal
                        } else {
                                my $ref = DXUser->get_current($field[1]);
                                if ($ref && $ref->is_clx) {
-                                       route($field[1], pc85($field[2], $field[1], $field[2], $field[3]));
+                                       $self->route($field[1], pc85($field[2], $field[1], $field[2], $field[3]));
                                } else {
                                        $self->route($field[1], $line);
                                }
@@ -924,11 +913,10 @@ sub normal
                }
                
                if ($pcno == 50) {              # keep alive/user list
-                       my $node = DXCluster->get_exact($field[1]);
+                       my $node = Route::Node::get($field[1]);
                        if ($node) {
-                               return unless $node->isa('DXNode');
-                               return unless $node->dxchan == $self;
-                               $node->update_users($field[2]);
+                               return unless $node->call eq $self->{call};
+                               $node->usercount($field[2]);
                        }
                        last SWITCH;
                }
@@ -956,7 +944,7 @@ sub normal
                                                                $dxchan->send($dxchan->msg('pingi', $field[2], $s, $ave))
                                                        } elsif ($dxchan->is_node) {
                                                                if ($tochan) {
-                                                                       $tochan->{nopings} = 2; # pump up the timer
+                                                                       $tochan->{nopings} = $tochan->user->nopings || 2; # pump up the timer
                                                                        push @{$tochan->{pingtime}}, $t;
                                                                        shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
                                                                        my $st;
@@ -1014,9 +1002,9 @@ sub normal
                if ($pcno == 84) { # remote commands (incoming)
                        if ($field[1] eq $main::mycall) {
                                my $ref = DXUser->get_current($field[2]);
-                               my $cref = DXCluster->get($field[2]);
+                               my $cref = Route::Node::get($field[2]);
                                Log('rcmd', 'in', $ref->{priv}, $field[2], $field[4]);
-                               unless ($field[4] =~ /rcmd/i || !$cref || !$ref || $cref->mynode->call ne $ref->homenode) {    # not allowed to relay RCMDS!
+                               unless ($field[4] =~ /rcmd/i || !$cref || !$ref || $cref->call ne $ref->homenode) {    # not allowed to relay RCMDS!
                                        if ($ref->{priv}) {     # you have to have SOME privilege, the commands have further filtering
                                                $self->{remotecmd} = 1; # for the benefit of any command that needs to know
                                                my $oldpriv = $self->{priv};
@@ -1040,7 +1028,7 @@ sub normal
                                if ($ref && $ref->is_clx) {
                                        $self->route($field[1], $line);
                                } else {
-                                       route($field[1], pc34($field[2], $field[1], $field[4]));
+                                       $self->route($field[1], pc34($field[2], $field[1], $field[4]));
                                }
                        }
                        return;
@@ -1068,7 +1056,7 @@ sub normal
                                if ($ref && $ref->is_clx) {
                                        $self->route($field[1], $line);
                                } else {
-                                       route($field[1], pc35($field[2], $field[1], $field[4]));
+                                       $self->route($field[1], pc35($field[2], $field[1], $field[4]));
                                }
                        }
                        return;
@@ -1103,9 +1091,10 @@ sub process
                next if $dxchan == $me;
                
                # send a pc50 out on this channel
-               if ($t >= $dxchan->pc50_t + $DXProt::pc50_interval) {
+               $dxchan->{pc50_t} = $main::systime unless exists $dxchan->{pc50_t};
+               if ($t >= $dxchan->{pc50_t} + $DXProt::pc50_interval) {
                        $dxchan->send(pc50(scalar DXChannel::get_all_users));
-                       $dxchan->pc50_t($t);
+                       $dxchan->{pc50_t} = $t;
                } 
 
                # send a ping out on this channel
@@ -1134,46 +1123,11 @@ sub process
 #
 # finish up a pc context
 #
-sub finish
-{
-       my $self = shift;
-       my $call = $self->call;
-       my $conn = shift;
-       my $ref = DXCluster->get_exact($call);
-       
-       # unbusy and stop and outgoing mail
-       my $mref = DXMsg::get_busy($call);
-       $mref->stop_msg($call) if $mref;
-       
-       # broadcast to all other nodes that all the nodes connected to via me are gone
-       my @gonenodes = map { $_->dxchan == $self ? $_ : () } DXNode::get_all();
-       my $node;
-       
-       foreach $node (@gonenodes) {
-               next if $node->call eq $call;
-               broadcast_ak1a(pc21($node->call, 'Gone') , $self) unless $self->{isolate}; 
-               $node->del();
-       }
-
-       # remove outstanding pings
-       delete $pings{$call};
-       
-       # now broadcast to all other ak1a nodes that I have gone
-       broadcast_ak1a(pc21($call, 'Gone.'), $self) unless $self->{isolate};
-
-       # I was the last node visited
-    $self->user->node($main::mycall);
-
-       # send info to all logged in thingies
-       $self->tell_login('logoutn');
-
-       Log('DXProt', $call . " Disconnected");
-       $ref->del() if $ref;
-}
 
 #
 # some active measures
 #
+
 sub send_dx_spot
 {
        my $self = shift;
@@ -1184,6 +1138,7 @@ sub send_dx_spot
        # send it if it isn't the except list and isn't isolated and still has a hop count
        # taking into account filtering and so on
        foreach $dxchan (@dxchan) {
+               next if $dxchan == $me;
                my $routeit;
                my ($filter, $hops);
 
@@ -1242,6 +1197,8 @@ sub send_wwv_spot
        # send it if it isn't the except list and isn't isolated and still has a hop count
        # taking into account filtering and so on
        foreach $dxchan (@dxchan) {
+               next if $dxchan == $self;
+               next if $dxchan == $me;
                my $routeit;
                my ($filter, $hops);
 
@@ -1250,7 +1207,6 @@ sub send_wwv_spot
                         next unless $filter;
                }
                if ($dxchan->is_node) {
-                       next if $dxchan == $self;
                        if ($hops) {
                                $routeit = $line;
                                $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
@@ -1299,6 +1255,7 @@ sub send_wcy_spot
        # send it if it isn't the except list and isn't isolated and still has a hop count
        # taking into account filtering and so on
        foreach $dxchan (@dxchan) {
+               next if $dxchan == $me;
                my $routeit;
                my ($filter, $hops);
 
@@ -1307,7 +1264,6 @@ sub send_wcy_spot
                         next unless $filter;
                }
                if ($dxchan->is_clx || $dxchan->is_spider || $dxchan->is_dxnet) {
-                       next if $dxchan == $self;
                        if ($hops) {
                                $routeit = $line;
                                $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
@@ -1376,6 +1332,8 @@ sub send_announce
        # send it if it isn't the except list and isn't isolated and still has a hop count
        # taking into account filtering and so on
        foreach $dxchan (@dxchan) {
+               next if $dxchan == $self;
+               next if $dxchan == $me;
                my $routeit;
                my ($filter, $hops);
 
@@ -1384,7 +1342,6 @@ sub send_announce
                        next unless $filter;
                } 
                if ($dxchan->is_node && $_[1] ne $main::mycall) {  # i.e not specifically routed to me
-                       next if $dxchan == $self;
                        if ($hops) {
                                $routeit = $line;
                                $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
@@ -1422,37 +1379,32 @@ sub send_local_config
        my @nodes;
        my @localnodes;
        my @remotenodes;
-               
+
+       dbg('trace', 'DXProt::send_local_config');
+       
        # send our nodes
        if ($self->{isolate}) {
-               @localnodes = (DXCluster->get_exact($main::mycall));
+               @localnodes = ( $main::routeroot );
        } else {
                # create a list of all the nodes that are not connected to this connection
                # and are not themselves isolated, this to make sure that isolated nodes
         # don't appear outside of this node
-               @nodes = DXNode::get_all();
-               @nodes = grep { $_->{call} ne $main::mycall } @nodes;
-               @nodes = grep { $_->dxchan != $self } @nodes if @nodes;
-               @nodes = grep { !$_->dxchan->{isolate} } @nodes if @nodes;
-               @localnodes = grep { $_->dxchan->{call} eq $_->{call} } @nodes if @nodes;
-               unshift @localnodes, DXCluster->get_exact($main::mycall);
-               @remotenodes = grep { $_->dxchan->{call} ne $_->{call} } @nodes if @nodes;
-       }
-
-       my @s = $me->pc19(@localnodes, @remotenodes);
-       for (@s) {
-               my $routeit = adjust_hops($self, $_);
-               $self->send($routeit) if $routeit;
+               my @dxchan = grep { $_->call ne $main::mycall && $_->call ne $self->{call} } DXChannel::get_all_nodes();
+               @localnodes = map { Route::Node::get($_->{call}) or die "connot find node $_->{call}" } @dxchan if @dxchan;
+               my @intcalls = map { $_->nodes } @localnodes if @localnodes;
+               my $ref = Route::Node::get($self->{call});
+               my @rnodes = $ref->nodes;
+               for my $n (@intcalls) {
+                       push @remotenodes, Route::Node::get($n) unless grep $n eq $_, @rnodes;
+               }
+               unshift @localnodes, $main::routeroot;
        }
        
+       send_route($self, \&pc19, scalar(@localnodes)+scalar(@remotenodes), @localnodes, @remotenodes);
+       
        # get all the users connected on the above nodes and send them out
        foreach $n (@localnodes, @remotenodes) {
-               my @users = values %{$n->list};
-               my @s = pc16($n, @users);
-               for (@s) {
-                       my $routeit = adjust_hops($self, $_);
-                       $self->send($routeit) if $routeit;
-               }
+               send_route($self, \&pc16, 1, $n, map {my $r = Route::User::get($_); $r ? ($r) : ()} $n->users);
        }
 }
 
@@ -1473,14 +1425,22 @@ sub route
        # always send it down the local interface if available
        my $dxchan = DXChannel->get($call);
        unless ($dxchan) {
-               my $cl = DXCluster->get_exact($call);
+               my $cl = Route::Node::get($call);
                $dxchan = $cl->dxchan if $cl;
+               if (ref $dxchan) {
+                       if (ref $self && $dxchan eq $self) {
+                               dbg('chan', "PCPROT: Trying to route back to source, dropped");
+                               return;
+                       }
+               }
        }
        if ($dxchan) {
                my $routeit = adjust_hops($dxchan, $line);   # adjust its hop count by node name
                if ($routeit) {
                        $dxchan->send($routeit);
                }
+       } else {
+               dbg('chan', "PCPROT: No route available, dropped");
        }
 }
 
@@ -1496,6 +1456,8 @@ sub broadcast_ak1a
        # send it if it isn't the except list and isn't isolated and still has a hop count
        foreach $dxchan (@dxchan) {
                next if grep $dxchan == $_, @except;
+               next if $dxchan == $me;
+               
                my $routeit = adjust_hops($dxchan, $s);      # adjust its hop count by node name
                $dxchan->send($routeit) unless $dxchan->{isolate} || !$routeit;
        }
@@ -1513,6 +1475,8 @@ sub broadcast_all_ak1a
        # send it if it isn't the except list and isn't isolated and still has a hop count
        foreach $dxchan (@dxchan) {
                next if grep $dxchan == $_, @except;
+               next if $dxchan == $me;
+
                my $routeit = adjust_hops($dxchan, $s);      # adjust its hop count by node name
                $dxchan->send($routeit);
        }
@@ -1547,6 +1511,7 @@ sub broadcast_list
        
        foreach $dxchan (@_) {
                my $filter = 1;
+               next if $dxchan == $me;
                
                if ($sort eq 'dx') {
                    next unless $dxchan->{dx};
@@ -1651,8 +1616,9 @@ sub addrcmd
        $r->{cmd} = $cmd;
        $rcmds{$to} = $r;
 
-       my $ref = DXCluster->get_exact($to);
-    if ($ref && $ref->dxchan && $ref->dxchan->is_clx) {
+       my $ref = Route::Node::get($to);
+       my $dxchan = $ref->dxchan;
+    if ($dxchan && $dxchan->is_clx) {
                route(undef, $to, pc84($main::mycall, $to, $self->{call}, $cmd));
        } else {
                route(undef, $to, pc34($main::mycall, $to, $cmd));
@@ -1662,12 +1628,39 @@ sub addrcmd
 sub disconnect
 {
        my $self = shift;
-       my $nopc39 = shift;
+       my $pc39flag = shift;
+       my $call = $self->call;
 
-       if ($self->{conn} && !$nopc39) {
+       unless ($pc39flag && $pc39flag == 1) {
                $self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op")));
        }
 
+       # do routing stuff
+#      my $node = Route::Node::get($self->{call});
+#      my @rout = $node->del_nodes if $node;
+       my @rout = $main::routeroot->del_node($call);
+       dbg('route', "B/C PC21 (from PC39) for: " . join(',', (map{ $_->call } @rout))) if @rout;
+       
+       # unbusy and stop and outgoing mail
+       my $mref = DXMsg::get_busy($call);
+       $mref->stop_msg($call) if $mref;
+       
+       # broadcast to all other nodes that all the nodes connected to via me are gone
+       unless ($pc39flag && $pc39flag == 2) {
+               $self->route_pc21(@rout) if @rout;
+       }
+
+       # remove outstanding pings
+       delete $pings{$call};
+       
+       # I was the last node visited
+    $self->user->node($main::mycall);
+
+       # send info to all logged in thingies
+       $self->tell_login('logoutn');
+
+       Log('DXProt', $call . " Disconnected");
+
        $self->SUPER::disconnect;
 }
 
@@ -1683,5 +1676,77 @@ sub talk
        $self->send(DXProt::pc10($from, $to, $via, $line));
        Log('talk', $self->call, $from, $via?$via:$main::mycall, $line);
 }
+
+# send it if it isn't the except list and isn't isolated and still has a hop count
+# taking into account filtering and so on
+sub send_route
+{
+       my $self = shift;
+       my $generate = shift;
+       my $no = shift;     # the no of things to filter on 
+       my $routeit;
+       my ($filter, $hops);
+       my @rin;
+       
+       if ($self->{routefilter}) {
+               for (; @_ && $no; $no--) {
+                       my $r = shift;
+                       ($filter, $hops) = $self->{routefilter}->it($self->{call}, $self->{dxcc}, $self->{itu}, $self->{cq}, $r->call, $r->dxcc, $r->itu, $r->cq);
+                       push @rin, $r if $filter;
+               }
+       }
+       if (@rin) {
+               foreach my $line (&$generate(@rin, @_)) {
+                       if ($hops) {
+                               $routeit = $line;
+                               $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
+                       } else {
+                               $routeit = adjust_hops($self, $line);  # adjust its hop count by node name
+                               next unless $routeit;
+                       }
+                       $self->send($routeit);
+               }
+       }
+}
+
+sub broadcast_route
+{
+       my $self = shift;
+       my $generate = shift;
+       my @dxchan = DXChannel::get_all_nodes();
+       my $dxchan;
+       my $line;
+       
+       foreach $dxchan (@dxchan) {
+               next if $dxchan == $self;
+               next if $dxchan == $me;
+               $dxchan->send_route($generate, @_);
+       }
+}
+
+sub route_pc16
+{
+       my $self = shift;
+       broadcast_route($self, \&pc16, 1, @_);
+}
+
+sub route_pc17
+{
+       my $self = shift;
+       broadcast_route($self, \&pc17, 1, @_);
+}
+
+sub route_pc19
+{
+       my $self = shift;
+       broadcast_route($self, \&pc19, scalar @_, @_);
+}
+
+sub route_pc21
+{
+       my $self = shift;
+       broadcast_route($self, \&pc21, scalar @_, @_);
+}
+
 1;
 __END__