add DXCIDR, fix version no tracking
[spider.git] / perl / DXProtHandle.pm
index 7839e11c55ea868a81a159aa395b28188c50d98c..36360f712a189798fd69bd7c961735a38f4d64a6 100644 (file)
@@ -32,10 +32,10 @@ use DXHash;
 use Route;
 use Route::Node;
 use Script;
-use RouteDB;
-
+use DXCIDR;
 
 use strict;
+use warnings qw(all);
 
 use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restime $eph_pc34_restime
                        $last_hour $last10 %eph  %pings %rcmds $ann_to_talk
@@ -43,19 +43,37 @@ use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restim
                        $investigation_int $pc19_version $myprot_version
                        %nodehops $baddx $badspotter $badnode $censorpc
                        $allowzero $decode_dk0wcy $send_opernam @checklist
-                       $eph_pc15_restime $pc9x_past_age
+                       $eph_pc15_restime $pc9x_past_age $pc9x_dupe_age
                        $pc10_dupe_age $pc92_slug_changes $last_pc92_slug
                        $pc92Ain $pc92Cin $pc92Din $pc92Kin $pc9x_time_tolerance
+                       $pc92filterdef $senderverify
                   );
 
+$pc9x_dupe_age = 60;                   # catch loops of circular (usually) D records
 $pc10_dupe_age = 45;                   # just something to catch duplicate PC10->PC93 conversions
-$pc92_slug_changes = 60;               # slug any changes going outward for this long
+$pc92_slug_changes = 60*1;             # slug any changes going outward for this long
 $last_pc92_slug = 0;                   # the last time we sent out any delayed add or del PC92s
 $pc9x_time_tolerance = 15*60;  # the time on a pc9x is allowed to be out by this amount
 $pc9x_past_age = (122*60)+             # maximum age in the past of a px9x (a config record might be the only
-       $pc9x_time_tolerance;           # thing a node might send - once an hour and we allow an extra hour for luck)
+$pc9x_time_tolerance;           # thing a node might send - once an hour and we allow an extra hour for luck)
                                 # this is actually the partition between "yesterday" and "today" but old.
+$senderverify = 0;                             # 1 - check for forged PC11 or PC61.
+                                # 2 - if forged, dump them.
+
+
+$pc92filterdef = bless ([
+                         # tag, sort, field, priv, special parser
+                         ['call', 'c', 0],
+                         ['by', 'c', 0],
+                         ['dxcc', 'nc', 1],
+                         ['itu', 'ni', 2],
+                         ['zone', 'nz', 3],
+                        ], 'Filter::Cmd');
 
+our %pc11q;
+# this is a place to park an incoming PC11 in the sure and certain hope that
+# a PC61 will be along soon. This has the side benefit that it will delay a
+# a PC11 for one second - assuming that it is not removed by a PC61 version
 
 # incoming talk commands
 sub handle_10
@@ -64,6 +82,7 @@ sub handle_10
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        # this is to catch loops caused by bad software ...
        if (eph_dup($line, $pc10_dupe_age)) {
@@ -73,7 +92,7 @@ sub handle_10
        # will we allow it at all?
        if ($censorpc) {
                my @bad;
-               if (@bad = BadWords::check($_[3])) {
+               if (@bad = BadWords::check($pc->[3])) {
                        dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
                        return;
                }
@@ -81,16 +100,16 @@ sub handle_10
 
        # is it for me or one of mine?
        my ($from, $to, $via, $call, $dxchan);
-       $from = $_[1];
-       if ($_[5] gt ' ') {
-               $via = $_[2];
-               $to = $_[5];
+       $from = $pc->[1];
+       if ($pc->[5] gt ' ') {
+               $via = $pc->[2];
+               $to = $pc->[5];
        } else {
-               $to = $_[2];
+               $to = $pc->[2];
        }
 
        # if this is a 'nodx' node then ignore it
-       if ($badnode->in($_[6]) || ($via && $badnode->in($via))) {
+       if ($badnode->in($pc->[6]) || ($via && $badnode->in($via))) {
                dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
                return;
        }
@@ -105,20 +124,20 @@ sub handle_10
 
        # if we are converting announces to talk is it a dup?
        if ($ann_to_talk) {
-               if (AnnTalk::is_talk_candidate($from, $_[3]) && AnnTalk::dup($from, $to, $_[3])) {
+               if (AnnTalk::is_talk_candidate($from, $pc->[3]) && AnnTalk::dup($from, $to, $pc->[3])) {
                        dbg("PCPROT: Dupe talk from announce, dropped") if isdbg('chanerr');
                        return;
                }
        }
 
-       # remember a route to this node and also the node on which this user is
-       RouteDB::update($_[6], $self->{call});
-#      RouteDB::update($to, $_[6]);
-
        # 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]));
+       $main::me->normal(pc93($to, $from, $via, $pc->[3], $pc->[6]));
 }
 
+my $last;
+my $pc11_saved;
+my $pc11_saved_time;
+
 # DX Spot handling
 sub handle_11
 {
@@ -126,77 +145,89 @@ sub handle_11
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
+       my $recurse = shift || 0;
 
        # route 'foreign' pc26s
        if ($pcno == 26) {
-               if ($_[7] ne $main::mycall) {
-                       $self->route($_[7], $line);
+               if ($pc->[7] ne $main::mycall) {
+                       $self->route($pc->[7], $line);
                        return;
                }
        }
 
-#      my ($hops) = $_[8] =~ /^H(\d+)/;
+       dbg("INPUT PC$pcno $line origin $origin recurse: $recurse") if isdbg("pc11"); 
+
+#      my ($hops) = $pc->[8] =~ /^H(\d+)/;
 
        # 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*$/) {
+       if ($pc->[2] =~ /^\s*$/) {
                dbg("PCPROT: blank callsign, dropped") if isdbg('chanerr');
                return;
        }
-       if ($_[2] =~ /[a-z]/) {
+       if ($pc->[2] =~ /[a-z]/) {
                dbg("PCPROT: lowercase characters, dropped") if isdbg('chanerr');
                return;
        }
 
 
        # if this is a 'nodx' node then ignore it
-       if ($badnode->in($_[7])) {
-               dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
+       if ($badnode->in($pc->[7])) {
+               dbg("PCPROT: Bad Node $pc->[7], dropped") if isdbg('chanerr');
                return;
        }
 
-       # if this is a 'bad spotter' user then ignore it
-       my $nossid = $_[6];
+       # if this is a 'bad spotter' or an unknown user then ignore it. BUT if it's got an IP address then allow it through
+       my $nossid = $pc->[6];
        $nossid =~ s/-\d+$//;
        if ($badspotter->in($nossid)) {
-               dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
+               dbg("PCPROT: Bad Spotter $pc->[6], dropped") if isdbg('chanerr');
                return;
        }
 
+       # check IP addresses
+       if ($pc->[8] && is_ipaddr($pc->[8])) {
+               my $ip = $pc->[8];
+               $ip =~ s/,/:/g;
+               $ip =~ s/^::ffff://;
+               if (DXCIDR::find($ip)) {
+                       
+               }
+       }
+
        # convert the date to a unix date
-       my $d = cltounix($_[3], $_[4]);
+       my $d = cltounix($pc->[3], $pc->[4]);
        # bang out (and don't pass on) if date is invalid or the spot is too old (or too young)
-       if (!$d || ($pcno == 11 && ($d < $main::systime - $pc11_max_age || $d > $main::systime + 900))) {
-               dbg("PCPROT: Spot ignored, invalid date or out of range ($_[3] $_[4])\n") if isdbg('chanerr');
+       if (!$d || (($pcno == 11 || $pcno == 61) && ($d < $main::systime - $pc11_max_age || $d > $main::systime + 900))) {
+               dbg("PCPROT: Spot ignored, invalid date or out of range ($pc->[3] $pc->[4])\n") if isdbg('chanerr');
                return;
        }
 
        # is it 'baddx'
-       if ($baddx->in($_[2]) || BadWords::check($_[2]) || $_[2] =~ /COCK/) {
+       if ($baddx->in($pc->[2]) || BadWords::check($pc->[2])) {
                dbg("PCPROT: Bad DX spot, ignored") if isdbg('chanerr');
                return;
        }
 
        # do some de-duping
-       $_[5] =~ s/^\s+//;                      # take any leading blanks off
-       $_[2] = unpad($_[2]);           # take off leading and trailing blanks from spotted callsign
-       if ($_[2] =~ /BUST\w*$/) {
+       $pc->[5] =~ s/^\s+//;                   # take any leading blanks off
+       $pc->[2] = unpad($pc->[2]);             # take off leading and trailing blanks from spotted callsign
+       if ($pc->[2] =~ /BUST\w*$/) {
                dbg("PCPROT: useless 'BUSTED' spot") if isdbg('chanerr');
                return;
        }
        if ($censorpc) {
                my @bad;
-               if (@bad = BadWords::check($_[5])) {
+               if (@bad = BadWords::check($pc->[5])) {
                        dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
                        return;
                }
        }
+       
 
-       # remember a route
-#      RouteDB::update($_[7], $self->{call});
-#      RouteDB::update($_[6], $_[7]);
+       my @spot = Spot::prepare($pc->[1], $pc->[2], $d, $pc->[5], $nossid, $pc->[7], $pc->[8]);
 
-       my @spot = Spot::prepare($_[1], $_[2], $d, $_[5], $nossid, $_[7]);
        # global spot filtering on INPUT
        if ($self->{inspotsfilter}) {
                my ($filter, $hops) = $self->{inspotsfilter}->it(@spot);
@@ -206,17 +237,110 @@ sub handle_11
                }
        }
 
+       # this is where we decide to delay PC11s in the hope that a PC61 will be along soon.
+       
+       my $key = join '|', @spot[0..2,4,7]; # not including text
+       unless ($recurse) {
+               if ($pcno == 61) {
+                       if ($pc11_saved) {
+                               if ($key eq $pc11_saved->[0]) {
+                                       dbg("saved PC11 spot $key dumped, better pc61 received") if isdbg("pc11");
+                                       undef $pc11_saved;
+                               }
+                       } 
+               }
+               if ($pcno == 11) {
+                       if ($pc11_saved) {
+                               if ($key eq $pc11_saved->[0]) {
+                                       dbg("saved PC11 spot $key, dupe pc11 received and dumped") if isdbg("pc11");
+                                       return;         # because it's a dup
+                               }
+                       }
+
+                       # can we promote this to a PC61?
+                       my $r = Route::User::get($spot[4]); # find spotter
+                       if ($r && $r->ip) {                     # do we have an ip addres
+                               $pcno = 61;                                             # now turn this into a PC61
+                               $spot[14] = $r->ip;
+                               dbg("PC11 spot $key promoted to pc61 ip $spot[14]") if isdbg("pc11");
+                               undef $pc11_saved;
+                       }
+               }
+
+               if ($pc11_saved && $key ne $pc11_saved) {
+                       dbg("saved PC11 spot $pc11_saved->[0] ne new key $key, recursing") if isdbg("pc11");
+                       shift @$pc11_saved;     # saved key
+                       my $self = shift @$pc11_saved;
+                       my @saved = @$pc11_saved;
+                       undef $pc11_saved;
+                       $self->handle_11(@saved, 1);
+               }
+
+               # if we are still a PC11, save it for a better offer
+               if ($pcno == 11) {
+                       $pc11_saved = [$key, $self, $pcno, $line, $origin, $pc];
+                       $pc11_saved_time = $main::systime;
+                       dbg("saved new PC11 spot $key for a better offer") if isdbg("pc11");
+                       return;
+               } else {
+                       dbg("PC61 spot $key passed onward") if isdbg("pc11");
+               }
+       }
+
+       
        # this goes after the input filtering, but before the add
        # so that if it is input filtered, it isn't added to the dup
        # list. This allows it to come in from a "legitimate" source
-       if (Spot::dup(@spot[0..4,5])) {
-               dbg("PCPROT: Duplicate Spot ignored\n") if isdbg('chanerr');
+       if (Spot::dup(@spot[0..4,7])) {
+               dbg("PCPROT: Duplicate Spot $pc->[0] $key ignored\n") if isdbg('chanerr') || isdbg('dupespot');
                return;
        }
+       
+       # here we verify the spotter is currently connected to the node it says it is one. AKA email sender verify
+       # but without the explicit probe to the node. We are relying on "historical" information, but it very likely
+       # to be current once we have seen the first PC92C from that node.
+       #
+       # As for spots generated from non-PC92 nodes, we'll see after about  do_pc9x3h20m...
+       #
+       if ($senderverify) {
+               my $nroute = Route::Node::get($pc->[7]);
+               my $uroute = Route::Node::get($pc->[6]);
+               my $local = DXChannel::get($pc->[7]);
+               
+               if ($nroute && ($nroute->last_PC92C || ($local && !$local->do_pc9x))) {
+                       my $s = '';
+                       my $ip = $pcno == 61 ?  $pc->[8] : '';
+#                      $s .= "User $pc->[6] not logged in, " unless $uroute;
+                       $s .= "User $pc->[6] not on node $pc->[7], " unless $nroute->is_user($pc->[6]);
+#                      $s .= "Node $pc->[7] at '$ip' not on Node's IP " . $nroute->ip if $ip && $nroute && $nroute->ip && $nroute->ip ne $ip;
+                       if ($s) {
+                               my $action = $senderverify > 1 ? ", DUMPED" : '';
+                               $s =~ s/, $//;
+                               dbg("PCProt: Suspicious Spot $pc->[2] on $pc->[1] by $pc->[6]($ip)\@$pc->[7] $s$action");
+                               return unless $senderverify < 2;
+                       }
+               }
+       }
 
+       # If is a new PC11, store it, releasing the one that is there (if any),
+       # if a PC61 comes along then dump the stored PC11
+       # If there is a different PC11 stored, release that one and store this PC11 instead,
+       
        # add it
        Spot::add(@spot);
 
+       my $ip = '';
+       $ip ||= $spot[14] if exists $spot[14];
+       if (isdbg('progress')) {
+               my $sip = $ip ? sprintf "($ip)" : '' unless $ip =~ m|[\(\)\*]|;
+               $sip ||= '';
+               my $d = ztime($spot[2]);
+               my $s = "SPOT: $spot[1] on $spot[0] \@ $d by $spot[4]$sip\@$spot[7]";
+               $s .= $spot[3] ? " '$spot[3]'" : q{ ''};
+               $s .=  " route: $origin";
+               dbg($s);
+       }
+       
        #
        # @spot at this point contains:-
        # freq, spotted call, time, text, spotter, spotted cc, spotters cc, orig node
@@ -225,7 +349,7 @@ sub handle_11
        #
 
        # fix up qra locators of known users
-       my $user = DXUser->get_current($spot[4]);
+       my $user = DXUser::get_current($spot[4]);
        if ($user) {
                my $qra = $user->qra;
                unless ($qra && is_qra($qra)) {
@@ -253,8 +377,8 @@ sub handle_11
                                } else {
                                        route(undef, $to, pc34($main::mycall, $to, $cmd));
                                }
-                               if ($to ne $_[7]) {
-                                       $to = $_[7];
+                               if ($to ne $origin) {
+                                       $to = $origin;
                                        $node = Route::Node::get($to);
                                        if ($node) {
                                                $dxchan = $node->dxchan;
@@ -272,12 +396,13 @@ sub handle_11
        }
 
        # local processing
-       my $r;
-       eval {
-               $r = Local::spot($self, @spot);
-       };
-       #                       dbg("Local::spot1 error $@") if isdbg('local') if $@;
-       return if $r;
+       if (defined &Local::spot) {
+               my $r;
+               eval {
+                       $r = Local::spot($self, @spot);
+               };
+               return if $r;
+       }
 
        # DON'T be silly and send on PC26s!
        return if $pcno == 26;
@@ -286,6 +411,19 @@ sub handle_11
        send_dx_spot($self, $line, @spot) if @spot;
 }
 
+# used to kick outstanding PC11 if required
+sub pc11_process
+{
+       if ($pc11_saved && $main::systime > $pc11_saved_time) {
+               dbg("saved PC11 spot $pc11_saved->[0] timed out waiting, recursing") if isdbg("pc11");
+               shift @$pc11_saved;     # saved key
+               my $self = shift @$pc11_saved;
+               my @saved = @$pc11_saved;
+               undef $pc11_saved;
+               $self->handle_11(@saved, 1);
+       }
+}
+
 # announces
 sub handle_12
 {
@@ -293,46 +431,49 @@ sub handle_12
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        # announce duplicate checking
-       $_[3] =~ s/^\s+//;                      # remove leading blanks
+       $pc->[3] =~ s/^\s+//;                   # remove leading blanks
 
        if ($censorpc) {
                my @bad;
-               if (@bad = BadWords::check($_[3])) {
+               if (@bad = BadWords::check($pc->[3])) {
                        dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
                        return;
                }
        }
 
        # if this is a 'nodx' node then ignore it
-       if ($badnode->in($_[5])) {
+       if ($badnode->in($pc->[5])) {
                dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
                return;
        }
 
        # if this is a 'bad spotter' user then ignore it
-       my $nossid = $_[1];
+       my $nossid = $pc->[1];
        $nossid =~ s/-\d+$//;
        if ($badspotter->in($nossid)) {
                dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
                return;
        }
 
+       # ignore PC12s from origins that use PCxx protocol
+       my $oref = Route::get($origin);
+       if ($oref->do_pc9x) {
+               dbg("PCPROT: PC12 rxed from PC9x node, ignored") if isdbg('chanerr');
+               return;
+       }
 
        my $dxchan;
 
-       if ((($dxchan = DXChannel::get($_[2])) && $dxchan->is_user) || $_[4] =~ /^[\#\w.]+$/){
-               $self->send_chat(0, $line, @_[1..6]);
-       } elsif ($_[2] eq '*' || $_[2] eq $main::mycall) {
-
-               # remember a route
-#              RouteDB::update($_[5], $self->{call});
-#              RouteDB::update($_[1], $_[5]);
+       if ((($dxchan = DXChannel::get($pc->[2])) && $dxchan->is_user) || $pc->[4] =~ /^[\#\w.]+$/){
+               $self->send_chat(0, $line, @$pc[1..6]);
+       } elsif ($pc->[2] eq '*' || $pc->[2] eq $main::mycall) {
 
                # ignore something that looks like a chat line coming in with sysop
                # flag - this is a kludge...
-               if ($_[3] =~ /^\#\d+ / && $_[4] eq '*') {
+               if ($pc->[3] =~ /^\#\d+ / && $pc->[4] eq '*') {
                        dbg('PCPROT: Probable chat rewrite, dropped') if isdbg('chanerr');
                        return;
                }
@@ -340,21 +481,30 @@ sub handle_12
                # here's a bit of fun, convert incoming ann with a callsign in the first word
                # or one saying 'to <call>' to a talk if we can route to the recipient
                if ($ann_to_talk) {
-                       my $call = AnnTalk::is_talk_candidate($_[1], $_[3]);
+                       my $call = AnnTalk::is_talk_candidate($pc->[1], $pc->[3]);
                        if ($call) {
                                my $ref = Route::get($call);
                                if ($ref) {
                                        $dxchan = $ref->dxchan;
-                                       $dxchan->talk($_[1], $call, undef, $_[3], $_[5]) if $dxchan != $self;
+                                       $dxchan->talk($pc->[1], $call, undef, $pc->[3], $pc->[5]) if $dxchan != $self;
                                        return;
                                }
                        }
                }
 
                # send it
-               $self->send_announce(0, $line, @_[1..6]);
+               $self->send_announce(0, $line, @$pc[1..6]);
        } else {
-               $self->route($_[2], $line);
+               $self->route($pc->[2], $line);
+       }
+
+       # local processing
+       if (defined &Local::ann) {
+               my $r;
+               eval {
+                       $r = Local::ann($self, $line, @$pc[1..6]);
+               };
+               return if $r;
        }
 }
 
@@ -364,6 +514,7 @@ sub handle_15
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        if (eph_dup($line, $eph_pc15_restime)) {
                return;
@@ -381,10 +532,11 @@ sub handle_16
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        # general checks
        my $dxchan;
-       my $ncall = $_[1];
+       my $ncall = $pc->[1];
        my $newline = "PC16^";
 
        # dos I want users from this channel?
@@ -401,7 +553,6 @@ sub handle_16
 
        my $h;
        $h = 1 if DXChannel::get($ncall);
-       RouteDB::update($ncall, $self->{call}, $h);
        if ($h && $self->{call} ne $ncall) {
                dbg("PCPROT: trying to update a local node, ignored") if isdbg('chanerr');
                return;
@@ -441,8 +592,8 @@ sub handle_16
 
        my $i;
        my @rout;
-       for ($i = 2; $i < $#_; $i++) {
-               my ($call, $conf, $here) = $_[$i] =~ /^(\S+) (\S) (\d)/o;
+       for ($i = 2; $i < $#$pc; $i++) {
+               my ($call, $conf, $here) = $pc->[$i] =~ /^(\S+) (\S) (\d)/o;
                next unless $call && $conf && defined $here && is_callsign($call);
                next if $call eq $main::mycall;
 
@@ -452,7 +603,7 @@ sub handle_16
 
                # reject this if we think it is a node already
                my $r = Route::Node::get($call);
-               my $u = DXUser->get_current($call) unless $r;
+               my $u = DXUser::get_current($call) unless $r;
                if ($r || ($u && $u->is_node)) {
                        dbg("PCPROT: $call is a node") if isdbg('chanerr');
                        next;
@@ -474,7 +625,7 @@ sub handle_16
                }
 
                # add this station to the user database, if required
-               my $user = DXUser->get_current($ncall);
+               my $user = DXUser::get_current($ncall);
                $user = DXUser->new($call) unless $user;
                $user->homenode($parent->call) if !$user->homenode;
                $user->node($parent->call);
@@ -498,9 +649,11 @@ sub handle_17
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
+
        my $dxchan;
-       my $ncall = $_[2];
-       my $ucall = $_[1];
+       my $ncall = $pc->[2];
+       my $ucall = $pc->[1];
 
        eph_del_regex("^PC16\\^$ncall.*$ucall");
 
@@ -521,8 +674,6 @@ sub handle_17
                return;
        }
 
-       RouteDB::delete($ncall, $self->{call});
-
        my $uref = Route::User::get($ucall);
        unless ($uref) {
                dbg("PCPROT: Route::User $ucall not in config") if isdbg('chanerr');
@@ -557,7 +708,7 @@ sub handle_17
        $parent->del_user($uref);
 
        # send info to all logged in thingies
-       my $user = DXUser->get_current($ncall);
+       my $user = DXUser::get_current($ncall);
        $self->tell_login('logoutu', "$ncall: $ucall") if $user && $user->is_local_node;
        $self->tell_buddies('logoutb', $ucall, $ncall);
 
@@ -576,42 +727,44 @@ sub handle_18
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
+
        $self->state('init');
 
        my $parent = Route::Node::get($self->{call});
 
        # record the type and version offered
-       if (my ($version) = $_[1] =~ /DXSpider Version: (\d+\.\d+)/) {
+       if (my ($version) = $pc->[1] =~ /DXSpider Version: (\d+\.\d+)/) {
                $self->{version} = 53 + $version;
                $self->user->version(53 + $version);
                $parent->version(0 + $version);
-               my ($build) = $_[1] =~ /Build: (\d+(?:\.\d+)?)/;
+               my ($build) = $pc->[1] =~ /Build: (\d+(?:\.\d+)?)/;
                $self->{build} = 0 + $build;
                $self->user->build(0 + $build);
                $parent->build(0 + $build);
-               dbg("DXSpider version $version build $build");
+               dbg("$self->{call} = DXSpider version $version build $build");
                unless ($self->is_spider) {
                        dbg("Change U " . $self->user->sort . " C $self->{sort} -> S");
                        $self->user->sort('S');
                        $self->user->put;
                        $self->sort('S');
                }
-#              $self->{handle_xml}++ if DXXml::available() && $_[1] =~ /\bxml/;
+#              $self->{handle_xml}++ if DXXml::available() && $pc->[1] =~ /\bxml/;
        } else {
-               dbg("Unknown software");
+               dbg("$self->{call} = Unknown software ($pc->[1] $pc->[2])");
                $self->version(50.0);
-               $self->version($_[2] / 100) if $_[2] && $_[2] =~ /^\d+$/;
+               $self->version($pc->[2] / 100) if $pc->[2] && $pc->[2] =~ /^\d+$/;
                $self->user->version($self->version);
        }
 
-       if ($_[1] =~ /\bpc9x/) {
+       if ($pc->[1] =~ /\bpc9x/) {
                if ($self->{isolate}) {
-                       dbg("pc9x recognised, but $self->{call} is isolated, using old protocol");
+                       dbg("$self->{call} pc9x recognised, but node is isolated, using old protocol");
                } elsif (!$self->user->wantpc9x) {
-                       dbg("pc9x explicitly switched off on $self->{call}, using old protocol");
+                       dbg("$self->{call} pc9x explicitly switched off, using old protocol");
                } else {
                        $self->{do_pc9x} = 1;
-                       dbg("Do px9x set on $self->{call}");
+                       dbg("$self->{call} Set do PC9x");
                }
        }
 
@@ -622,20 +775,38 @@ sub handle_18
        $self->send(pc20());
 }
 
-sub check_add_node
+sub check_add_user
 {
        my $call = shift;
+       my $type = shift;
+       my $homenode = shift;
 
        # add this station to the user database, if required (don't remove SSID from nodes)
-       my $user = DXUser->get_current($call);
-       if (!$user) {
+       my $user = DXUser::get_current($call);
+       unless ($user) {
                $user = DXUser->new($call);
-               $user->priv(1);         # I have relented and defaulted nodes
-               $user->lockout(1);
-               $user->homenode($call);
-               $user->node($call);
+               $user->sort($type || 'U');
+               if ($user->is_node) {
+                       $user->priv(1);         # I have relented and defaulted nodes
+                       $user->lockout(1) if $user->is_node;
+               } else {
+                       $user->homenode($homenode) if $homenode;
+                       $user->node($homenode);
+                       $user->priv(0);
+               }
+               $user->lastin($main::systime); # this make it last longer than just this invocation
+               $user->put;                             # just to make sure it gets written away!!!
+               dbg("DXProt: PC92 new user record for $call created");
+       }
+
+       # this is to fix a problem I introduced some build ago by using this function for users
+       # whereas it was only being used for nodes.
+       if ($user->is_user && $user->lockout && ($user->priv // 0) == 1) {
+               $user->priv(0);
+               $user->lockout(0);
+               dbg("DXProt: PC92 user record for $call depriv'd and unlocked");
+               $user->put;
        }
-       $user->sort('A') unless $user->is_node;
        return $user;
 }
 
@@ -646,6 +817,7 @@ sub handle_19
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        my $i;
        my $newline = "PC19^";
@@ -675,11 +847,11 @@ sub handle_19
        # From now on we are only going to believe PC92 data and locally connected
        # non-pc92 nodes.
        #
-       for ($i = 1; $i < $#_-1; $i += 4) {
-               my $here = $_[$i];
-               my $call = uc $_[$i+1];
-               my $conf = $_[$i+2];
-               my $ver = $_[$i+3];
+       for ($i = 1; $i < $#$pc-1; $i += 4) {
+               my $here = $pc->[$i];
+               my $call = uc $pc->[$i+1];
+               my $conf = $pc->[$i+2];
+               my $ver = $pc->[$i+3];
                next unless defined $here && defined $conf && is_callsign($call);
 
                eph_del_regex("^PC(?:21\\^$call|17\\^[^\\^]+\\^$call)");
@@ -689,7 +861,7 @@ sub handle_19
                next unless $ver && $ver =~ /^\d+$/;
                next if $ver < 5000;    # only works with version 5 software
                next if length $call < 3; # min 3 letter callsigns
-               next if $call eq $main::mycall;
+               next if $call eq $main::mycall || $call eq $main::myalias;
 
                # check that this PC19 isn't trying to alter the wrong dxchan
                $h = 0;
@@ -709,14 +881,13 @@ sub handle_19
                        next;
                }
 
-               my $user = check_add_node($call);
+               my $user = check_add_user($call, 'A');
 
 #              if (eph_dup($genline)) {
 #                      dbg("PCPROT: dup PC19 for $call detected") if isdbg('chanerr');
 #                      next;
 #              }
 
-               RouteDB::update($call, $self->{call}, $dxchan ? 1 : undef);
 
                unless ($h) {
                        if ($parent->via_pc92) {
@@ -785,6 +956,7 @@ sub handle_20
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        if ($self->{do_pc9x} && $self->{state} ne 'init92') {
                $self->send("Reseting to oldstyle routing because login call not sent in any pc92");
@@ -807,7 +979,9 @@ sub handle_21
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       my $call = uc $_[1];
+       my $pc = shift;
+
+       my $call = uc $pc->[1];
 
        eph_del_regex("^PC1[679].*$call");
 
@@ -822,8 +996,6 @@ sub handle_21
        # we don't need any isolation code here, because we will never
        # act on a PC21 with self->call in it.
 
-       RouteDB::delete($call, $self->{call});
-
        my $parent = Route::Node::get($self->{call});
        unless ($parent) {
                dbg("PCPROT: my parent $self->{call} has disappeared");
@@ -833,7 +1005,7 @@ sub handle_21
 
        my @rout;
 
-       if ($call ne $main::mycall) { # don't allow malicious buggers to disconnect me!
+       if ($call ne $main::mycall && $call ne $main::myalias) { # don't allow malicious buggers to disconnect me!
                my $node = Route::Node::get($call);
                if ($node) {
 
@@ -844,7 +1016,7 @@ sub handle_21
 
                        my $dxchan = DXChannel::get($call);
                        if ($dxchan && $dxchan != $self) {
-                               dbg("PCPROT: PC21 from $self->{call} trying to alter locally connected $call, ignored!") if isdbg('chanerr');
+                               dbg("PCPROT: PC21 from $self->{call} trying to alter locally connected $call, ignored!") if isdbg('chan');
                                return;
                        }
 
@@ -856,7 +1028,7 @@ sub handle_21
                        push @rout, $call if $dxchan && @rout == 0;
                }
        } else {
-               dbg("PCPROT: I WILL _NOT_ be disconnected!") if isdbg('chanerr');
+               dbg("PCPROT: I WILL _NOT_ be disconnected!") if isdbg('chan');
                return;
        }
 
@@ -877,6 +1049,7 @@ sub handle_22
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        if ($self->{do_pc9x}) {
                if ($self->{state} ne 'init92') {
@@ -896,58 +1069,61 @@ sub handle_23
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        # route foreign' pc27s
        if ($pcno == 27) {
-               if ($_[8] ne $main::mycall) {
-                       $self->route($_[8], $line);
+               if ($pc->[8] ne $main::mycall) {
+                       $self->route($pc->[8], $line);
                        return;
                }
        }
 
 
        # do some de-duping
-       my $d = cltounix($_[1], sprintf("%02d18Z", $_[2]));
-       my $sfi = unpad($_[3]);
-       my $k = unpad($_[4]);
-       my $i = unpad($_[5]);
-       my ($r) = $_[6] =~ /R=(\d+)/;
+       my $d = cltounix($pc->[1], sprintf("%02d18Z", $pc->[2]));
+       my $sfi = unpad($pc->[3]);
+       my $k = unpad($pc->[4]);
+       my $i = unpad($pc->[5]);
+       my ($r) = $pc->[6] =~ /R=(\d+)/;
        $r = 0 unless $r;
-       if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) {
-               dbg("PCPROT: WWV Date ($_[1] $_[2]) out of range") if isdbg('chanerr');
+       if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $pc->[2] < 0 || $pc->[2] > 23) {
+               dbg("PCPROT: WWV Date ($pc->[1] $pc->[2]) out of range") if isdbg('chanerr');
                return;
        }
 
        # global wwv filtering on INPUT
-       my @dxcc = ((Prefix::cty_data($_[7]))[0..2], (Prefix::cty_data($_[8]))[0..2]);
+       my @dxcc = ((Prefix::cty_data($pc->[7]))[0..2], (Prefix::cty_data($pc->[8]))[0..2]);
        if ($self->{inwwvfilter}) {
-               my ($filter, $hops) = $self->{inwwvfilter}->it(@_[7,8], $origin, @dxcc);
+               my ($filter, $hops) = $self->{inwwvfilter}->it(@$pc[7,8], $origin, @dxcc);
                unless ($filter) {
                        dbg("PCPROT: Rejected by input wwv filter") if isdbg('chanerr');
                        return;
                }
        }
-       $_[7] =~ s/-\d+$//o;            # remove spotter's ssid
-       if (Geomag::dup($d,$sfi,$k,$i,$_[6],$_[7])) {
+       $pc->[7] =~ s/-\d+$//o;         # remove spotter's ssid
+       if (Geomag::dup($d,$sfi,$k,$i,$pc->[6],$pc->[7])) {
                dbg("PCPROT: Dup WWV Spot ignored\n") if isdbg('chanerr');
                return;
        }
 
        # note this only takes the first one it gets
-       Geomag::update($d, $_[2], $sfi, $k, $i, @_[6..8], $r);
+       Geomag::update($d, $pc->[2], $sfi, $k, $i, @$pc[6..8], $r);
+       dbg("WWV: <$pc->[2]>, sfi=$sfi k=$k info=$i '$pc->[6]' $pc->[7]\@$pc->[8] $r route: $origin") if isdbg('progress');
 
-       my $rep;
-       eval {
-               $rep = Local::wwv($self, $_[1], $_[2], $sfi, $k, $i, @_[6..8], $r);
-       };
-       #                       dbg("Local::wwv2 error $@") if isdbg('local') if $@;
-       return if $rep;
+       if (defined &Local::wwv) {
+               my $rep;
+               eval {
+                       $rep = Local::wwv($self, $pc->[1], $pc->[2], $sfi, $k, $i, @$pc[6..8], $r);
+               };
+               return if $rep;
+       }
 
        # DON'T be silly and send on PC27s!
        return if $pcno == 27;
 
        # broadcast to the eager world
-       send_wwv_spot($self, $line, $d, $_[2], $sfi, $k, $i, @_[6..8]);
+       send_wwv_spot($self, $line, $d, $pc->[2], $sfi, $k, $i, @$pc[6..8]);
 }
 
 # set here status
@@ -957,7 +1133,9 @@ sub handle_24
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       my $call = uc $_[1];
+       my $pc = shift;
+
+       my $call = uc $pc->[1];
        my ($nref, $uref);
        $nref = Route::Node::get($call);
        $uref = Route::User::get($call);
@@ -967,12 +1145,12 @@ sub handle_24
                return;
        }
 
-       $nref->here($_[2]) if $nref;
-       $uref->here($_[2]) if $uref;
+       $nref->here($pc->[2]) if $nref;
+       $uref->here($pc->[2]) if $uref;
        my $ref = $nref || $uref;
        return unless $self->in_filter_route($ref);
 
-       $self->route_pc24($origin, $line, $ref, $_[3]);
+       $self->route_pc24($origin, $line, $ref, $pc->[3]);
 }
 
 # merge request
@@ -982,32 +1160,34 @@ sub handle_25
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       if ($_[1] ne $main::mycall) {
-               $self->route($_[1], $line);
+       my $pc = shift;
+
+       if ($pc->[1] ne $main::mycall) {
+               $self->route($pc->[1], $line);
                return;
        }
-       if ($_[2] eq $main::mycall) {
-               dbg("PCPROT: Trying to merge to myself, ignored") if isdbg('chanerr');
+       if ($pc->[2] eq $main::mycall) {
+               dbg("PCPROT: Trying to merge to myself, ignored") if isdbg('chan');
                return;
        }
 
-       Log('DXProt', "Merge request for $_[3] spots and $_[4] WWV from $_[2]");
+       Log('DXProt', "Merge request for $pc->[3] spots and $pc->[4] WWV from $pc->[2]");
 
        # spots
-       if ($_[3] > 0) {
-               my @in = reverse Spot::search(1, undef, undef, 0, $_[3]);
+       if ($pc->[3] > 0) {
+               my @in = reverse Spot::search(1, undef, undef, 0, $pc->[3]);
                my $in;
                foreach $in (@in) {
-                       $self->send(pc26(@{$in}[0..4], $_[2]));
+                       $self->send(pc26(@{$in}[0..4], $pc->[2]));
                }
        }
 
        # wwv
-       if ($_[4] > 0) {
-               my @in = reverse Geomag::search(0, $_[4], time, 1);
+       if ($pc->[4] > 0) {
+               my @in = reverse Geomag::search(0, $pc->[4], time, 1);
                my $in;
                foreach $in (@in) {
-                       $self->send(pc27(@{$in}[0..5], $_[2]));
+                       $self->send(pc27(@{$in}[0..5], $pc->[2]));
                }
        }
 }
@@ -1022,12 +1202,14 @@ sub handle_28
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       if ($_[1] eq $main::mycall) {
+       my $pc = shift;
+
+       if ($pc->[1] eq $main::mycall) {
                no strict 'refs';
                my $sub = "DXMsg::handle_$pcno";
-               &$sub($self, @_);
+               &$sub($self, @$pc);
        } else {
-               $self->route($_[1], $line) unless $self->is_clx;
+               $self->route($pc->[1], $line) unless $self->is_clx;
        }
 }
 
@@ -1043,10 +1225,12 @@ sub handle_34
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
+
        if (eph_dup($line, $eph_pc34_restime)) {
                return;
        } else {
-               $self->process_rcmd($_[1], $_[2], $_[2], $_[3]);
+               $self->process_rcmd($pc->[1], $pc->[2], $pc->[2], $pc->[3]);
        }
 }
 
@@ -1057,8 +1241,10 @@ sub handle_35
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       eph_del_regex("^PC35\\^$_[2]\\^$_[1]\\^");
-       $self->process_rcmd_reply($_[1], $_[2], $_[1], $_[3]);
+       my $pc = shift;
+
+       eph_del_regex("^PC35\\^$pc->[2]\\^$pc->[1]\\^");
+       $self->process_rcmd_reply($pc->[1], $pc->[2], $pc->[1], $pc->[3]);
 }
 
 sub handle_36 {goto &handle_34}
@@ -1070,12 +1256,14 @@ sub handle_37
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       if ($_[1] eq $main::mycall) {
+       my $pc = shift;
+
+       if ($pc->[1] eq $main::mycall) {
                no strict 'refs';
                my $sub = "DXDb::handle_$pcno";
-               &$sub($self, @_);
+               &$sub($self, @$pc);
        } else {
-               $self->route($_[1], $line) unless $self->is_clx;
+               $self->route($pc->[1], $line) unless $self->is_clx;
        }
 }
 
@@ -1086,6 +1274,7 @@ sub handle_38
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 }
 
 # incoming disconnect
@@ -1095,7 +1284,9 @@ sub handle_39
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       if ($_[1] eq $self->{call}) {
+       my $pc = shift;
+
+       if ($pc->[1] eq $self->{call}) {
                $self->disconnect(1);
        } else {
                dbg("PCPROT: came in on wrong channel") if isdbg('chanerr');
@@ -1111,9 +1302,11 @@ sub handle_41
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       my $call = $_[1];
-       my $sort = $_[2];
-       my $val = $_[3];
+       my $pc = shift;
+
+       my $call = $pc->[1];
+       my $sort = $pc->[2];
+       my $val = $pc->[3];
 
        my $l = "PC41^$call^$sort";
        if (eph_dup($l, $eph_info_restime)) {
@@ -1129,16 +1322,23 @@ sub handle_41
                return;
        }
 
+       if ($call eq $main::mycall || $call eq $main::myalias) {
+               dbg "DXPROT: PC41 trying to update $call from outside via $origin, ignored";
+               return;
+       }
+       my $chan = DXChannel::get($call);
+       if ($chan) {
+               dbg "DXPROT: PC41 trying to update online $call from outside via $origin, ignored";
+               return;
+       }
+
        # add this station to the user database, if required
-       my $user = DXUser->get_current($call);
+       my $user = DXUser::get_current($call);
        $user = DXUser->new($call) unless $user;
 
        if ($sort == 1) {
                if (($val =~ /spotter/i || $val =~ /self/i) && $user->name && $user->name ne $val) {
                        dbg("PCPROT: invalid name") if isdbg('chanerr');
-                       if ($main::mycall eq 'GB7DJK' || $main::mycall eq 'GB7BAA' || $main::mycall eq 'WR3D') {
-                               DXChannel::broadcast_nodes(pc41($_[1], 1, $user->name)); # send it to everyone including me
-                       }
                        return;
                }
                $user->name($val);
@@ -1175,7 +1375,7 @@ sub handle_41
        }
 
        #  perhaps this IS what we want after all
-       #                       $self->route_pc41($ref, $call, $sort, $val, $_[4]);
+       #                       $self->route_pc41($ref, $call, $sort, $val, $pc->[4]);
 }
 
 sub handle_42 {goto &handle_28}
@@ -1195,15 +1395,16 @@ sub handle_49
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        if (eph_dup($line)) {
                return;
        }
 
-       if ($_[1] eq $main::mycall) {
-               DXMsg::handle_49($self, @_);
+       if ($pc->[1] eq $main::mycall) {
+               DXMsg::handle_49($self, @$pc);
        } else {
-               $self->route($_[1], $line) unless $self->is_clx;
+               $self->route($pc->[1], $line) unless $self->is_clx;
        }
 }
 
@@ -1214,18 +1415,18 @@ sub handle_50
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        return if (eph_dup($line));
 
-       my $call = $_[1];
-
-       RouteDB::update($call, $self->{call});
+       my $call = $pc->[1];
 
        my $node = Route::Node::get($call);
        if ($node) {
                return unless $node->call eq $self->{call};
-               $node->usercount($_[2]) unless $node->users;
+               $node->usercount($pc->[2]) unless $node->users;
                $node->reset_obs;
+               $node->PC92C_dxchan($self->call, $pc->[-1]);
 
                # input filter if required
 #              return unless $self->in_filter_route($node);
@@ -1233,7 +1434,7 @@ sub handle_50
                unless ($self->{isolate}) {
                        DXChannel::broadcast_nodes($line, $self); # send it to everyone but me
                }
-#              $self->route_pc50($origin, $line, $node, $_[2], $_[3]) unless eph_dup($line);
+#              $self->route_pc50($origin, $line, $node, $pc->[2], $pc->[3]) unless eph_dup($line);
        }
 }
 
@@ -1244,10 +1445,16 @@ sub handle_51
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       my $to = $_[1];
-       my $from = $_[2];
-       my $flag = $_[3];
+       my $pc = shift;
 
+       my $to = $pc->[1];
+       my $from = $pc->[2];
+       my $flag = $pc->[3];
+
+       if ($to eq $main::myalias) {
+               dbg("DXPROT: Ping addressed to \$myalias ($main::myalias), ignored") if isdbg('chan');
+               return;
+       }
 
        # is it for us?
        if ($to eq $main::mycall) {
@@ -1257,9 +1464,6 @@ sub handle_51
                        DXXml::Ping::handle_ping_reply($self, $from);
                }
        } else {
-
-               RouteDB::update($from, $self->{call});
-
                if (eph_dup($line)) {
                        return;
                }
@@ -1277,7 +1481,9 @@ sub handle_75
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       my $call = $_[1];
+       my $pc = shift;
+
+       my $call = $pc->[1];
        if ($call ne $main::mycall) {
                $self->route($call, $line);
        }
@@ -1290,31 +1496,35 @@ sub handle_73
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       my $call = $_[1];
+       my $pc = shift;
+
+       my $call = $pc->[1];
 
        # do some de-duping
-       my $d = cltounix($call, sprintf("%02d18Z", $_[2]));
-       if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) {
-               dbg("PCPROT: WCY Date ($call $_[2]) out of range") if isdbg('chanerr');
+       my $d = cltounix($call, sprintf("%02d18Z", $pc->[2]));
+       if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $pc->[2] < 0 || $pc->[2] > 23) {
+               dbg("PCPROT: WCY Date ($call $pc->[2]) out of range") if isdbg('chanerr');
                return;
        }
-       @_ = map { unpad($_) } @_;
+       $pc = [ map { unpad($_) } @$pc ];
        if (WCY::dup($d)) {
                dbg("PCPROT: Dup WCY Spot ignored\n") if isdbg('chanerr');
                return;
        }
 
-       my $wcy = WCY::update($d, @_[2..12]);
+       my $wcy = WCY::update($d, @$pc[2..12]);
+       dbg("WCY: <$pc->[2]> K=$pc->[5] expK=$pc->[6] A=$pc->[4] R=$pc->[7] SFI=$pc->[3] SA=$pc->[8] GMF=$pc->[9] Au=$pc->[10] $pc->[11]\@$pc->[12] route: $origin") if isdbg('progress');
 
-       my $rep;
-       eval {
-               $rep = Local::wcy($self, @_[1..12]);
-       };
-       # dbg("Local::wcy error $@") if isdbg('local') if $@;
-       return if $rep;
+       if (defined &Local::wcy) {
+               my $rep;
+               eval {
+                       $rep = Local::wcy($self, @$pc[1..12]);
+               };
+               return if $rep;
+       }
 
        # broadcast to the eager world
-       send_wcy_spot($self, $line, $d, @_[2..12]);
+       send_wcy_spot($self, $line, $d, @$pc[2..12]);
 }
 
 # remote commands (incoming)
@@ -1324,7 +1534,9 @@ sub handle_84
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       $self->process_rcmd($_[1], $_[2], $_[3], $_[4]);
+       my $pc = shift;
+
+       $self->process_rcmd($pc->[1], $pc->[2], $pc->[3], $pc->[4]);
 }
 
 # remote command replies
@@ -1334,7 +1546,9 @@ sub handle_85
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
-       $self->process_rcmd_reply($_[1], $_[2], $_[3], $_[4]);
+       my $pc = shift;
+
+       $self->process_rcmd_reply($pc->[1], $pc->[2], $pc->[3], $pc->[4]);
 }
 
 # decode a pc92 call: flag call : version : build
@@ -1354,7 +1568,23 @@ sub _decode_pc92_call
        my $is_node = $flag & 4;
        my $is_extnode = $flag & 2;
        my $here = $flag & 1;
-       return ($call, $is_node, $is_extnode, $here, $part[1], $part[2]);
+       my $version = $part[1] || 0;
+       my $build = $part[2] || 0;
+       my $ip = $part[3] || '';
+       
+       if ($version =~ /[,.]/) {
+               $ip = $version;
+               $version = 0;
+       }
+       $version =~ s/\D+//g;
+       $build =~ s/^0\.//;
+       $build =~ s/\D+//g;
+       if ($ip) {
+               $ip =~ s/,/:/g;
+               $ip =~ s/^::ffff://i;
+       }
+       dbg("$icall = '" . join("', '", $call, $is_node, $is_extnode, $here, $version, $build, $ip) . "'") if isdbg('pc92');
+       return ($call, $is_node, $is_extnode, $here, $version, $build, $ip);
 }
 
 # decode a pc92 call: flag call : version : build
@@ -1365,7 +1595,7 @@ sub _encode_pc92_call
        # plain call or value
        return $ref unless ref $ref;
 
-       my $ext = shift;
+       my $ext = shift || 0;
        my $flag = 0;
        my $call = $ref->call;
        my $extra = '';
@@ -1374,14 +1604,17 @@ sub _encode_pc92_call
                $flag |= 4;
                my $dxchan = DXChannel::get($call);
                $flag |= 2 if $call ne $main::mycall && $dxchan && !$dxchan->{do_pc9x};
-               if ($ext) {
-                       if ($ref->version) {
-                               my $version = $ref->version || 1.0;
-                               $version =  $version * 100 + 5300 if $version < 50;
-                               $extra .= ":" . $version;
-                       }
+               if (($ext & 1) && $ref->version) {
+                       my $version = $ref->version || 1.0;
+                       $version =  $version * 100 + 5300 if $version < 50;
+                       $extra .= ":" . $version;
                }
        }
+       if (($ext & 2) && $ref->ip) {
+               my $ip = $ref->ip;
+               $ip =~ s/:/,/g;
+               $extra .= ':' . $ip;
+       }
        return "$flag$call$extra";
 }
 
@@ -1392,22 +1625,77 @@ sub _add_thingy
 {
        my $parent = shift;
        my $s = shift;
-       my ($call, $is_node, $is_extnode, $here, $version, $build) = @$s;
+       my $dxchan = shift;
+       my $hops = shift;
+
+       my ($call, $is_node, $is_extnode, $here, $version, $build, $ip) = @$s;
        my @rout;
 
+       # remove spurious IPV6 prefix on IPV4 addresses
+       $build ||= 0;
+       $version ||= 0;
+
        if ($call) {
-               if ($is_node) {
-                       dbg("ROUTE: added node $call to " . $parent->call) if isdbg('routelow');
-                       @rout = $parent->add($call, $version, Route::here($here));
-               } else {
-                       dbg("ROUTE: added user $call to " . $parent->call) if isdbg('routelow');
-                       @rout = $parent->add_user($call, Route::here($here));
-               }
-               if ($pc92_slug_changes && $parent == $main::routeroot) {
-                       $things_add{$call} = Route::get($call);
-                       delete $things_del{$call};
+               my $ncall = $parent->call;
+               if ($ncall ne $call) {
+                       my $user;
+                       my $r;
+
+                       # normalise call, delete any unnormalised calls in the users file.
+                       # then ignore this thingy
+                       my $normcall = normalise_call($call);
+                       if ($normcall ne $call) {
+                               next if DXChannel::get($call);
+                               $user = DXUser::get($call);
+                               dbg("DXProt::_add_thingy call $call normalised to $normcall, deleting spurious user $call");
+                               $user->del if $user;
+                           $call = $normcall; # this is safe because a route add will ignore duplicates
+                       }
+                       
+                       if ($is_node) {
+                               dbg("ROUTE: added node $call to $ncall") if isdbg('routelow');
+                               $user = check_add_user($call, 'A');
+                               @rout = $parent->add($call, $version, Route::here($here), $ip);
+                               $r = Route::Node::get($call);
+                               $r->PC92C_dxchan($dxchan->call, $hops) if $r;
+                               if ($version && is_numeric($version) && !$r->K && !$user->K) {
+                                       my $old = $user->sort;
+                                       if ($user->is_ak1a && (($version >= 5455 &&  $build > 0) || ($version >= 3000 && $version <= 3500)) ) {
+                                               $user->sort('S');
+                                               dbg("PCProt::_add_thingy node $call v: $version b: $build sort ($old) updated to " . $user->sort);
+                                       } elsif ($user->is_spider && ($version < 3000 || ($version > 4000 && $version < 5455))) {
+                                               unless ($version == 5000 && $build == 0) {
+                                                       $user->sort('A');
+                                                       $build //= 0;
+                                                       dbg("PCProt::_add_thingy node $call v: $version b: $build sort ($old) downgraded to " . $user->sort);
+                                               }
+                                       }
+                               }
+                               $r->version($user->version) if $user->version;
+                               $r->build($user->build) if $user->build;
+                               $r->K(1) if $user->K;
+                       } else {
+                               dbg("ROUTE: added user $call to $ncall") if isdbg('routelow');
+                               $user = check_add_user($call, 'U', $parent->call);
+                               @rout = $parent->add_user($call, Route::here($here), $ip);
+                               $dxchan->tell_buddies('loginb', $call, $ncall) if $dxchan;
+                               $r = Route::User::get($call);
+                       }
+                       if ($ip) {
+                               $r->ip($ip);
+                               Log('DXProt', "PC92A $call -> $ip on $ncall");
+                       }
+                       if ($pc92_slug_changes && $parent == $main::routeroot) {
+                               $things_add{$call} = Route::get($call);
+                               delete $things_del{$call};
+                       }
+                       $user->close($main::systime, $ip) if $user;             # this just updates lastseen and the connlist list containing the IP address
+               } else {                                
+                       dbgprintring(10) if isdbg('nologchan');
+                       dbg("DXProt::add_thingy: Trying to add parent $call to itself $ncall, ignored");
                }
        }
+       
        return @rout;
 }
 
@@ -1415,6 +1703,7 @@ sub _del_thingy
 {
        my $parent = shift;
        my $s = shift;
+       my $dxchan = shift;
        my ($call, $is_node, $is_extnode, $here, $version, $build) = @$s;
        my @rout;
        if ($call) {
@@ -1424,9 +1713,12 @@ sub _del_thingy
                        dbg("ROUTE: deleting node $call from " . $parent->call) if isdbg('routelow');
                        @rout = $ref->del($parent) if $ref;
                } else {
-                       $ref = Route::User::get($call);
                        dbg("ROUTE: deleting user $call from " . $parent->call) if isdbg('routelow');
-                       @rout = $parent->del_user($ref) if $ref;
+                       $ref = Route::User::get($call);
+                       if ($ref) {
+                               $dxchan->tell_buddies('logoutb', $call, $parent->call) if $dxchan;
+                               @rout = $parent->del_user($ref);
+                       }
                }
                if ($pc92_slug_changes && $parent == $main::routeroot) {
                        $things_del{$call} = $ref unless exists $things_add{$call};
@@ -1516,19 +1808,27 @@ sub check_pc9x_t
                                        # and old dupes with $t = 234, $lastid = 256 (which give answers 249 and
                                        # 86378 respectively in the calculation below).
                                        #
-                                       if (($t-$lastid)%86400 > $pc9x_past_age) {
+                                       if ($t+86400-$lastid > $pc9x_past_age) {
                                                dbg("PCPROT: dup id on $t <= lastid $lastid, ignored") if isdbg('chanerr') || isdbg('pc92dedupe');
                                                return undef;
                                        }
                                } elsif ($t == $lastid) {
                                        dbg("PCPROT: dup id on $t == lastid $lastid, ignored") if isdbg('chanerr') || isdbg('pc92dedupe');
                                        return undef;
+                               } else {
+                                       # check that if we have a low number in lastid that yesterday's numbers
+                                       # (likely in the 85000+ area) don't override them, thus causing flip flopping
+                                       if ($lastid+86400-$t < $pc9x_past_age) {
+                                               dbg("PCPROT: dup id on $t in yesterday, lastid $lastid, ignored") if isdbg('chanerr') || isdbg('pc92dedupe');
+                                               return undef;
+                                       }
                                }
                        }
                }
        } elsif ($create) {
                $parent = Route::Node->new($call);
        } else {
+               dbg("PCPROT: $call does not exist, ignored") if isdbg('pc92dedupe');
                return undef;
        }
        if (isdbg('pc92dedupe')) {
@@ -1548,6 +1848,7 @@ sub pc92_handle_first_slot
        my $slot = shift;
        my $parent = shift;
        my $t = shift;
+       my $hops = shift;
        my $oparent = $parent;
 
        my @radd;
@@ -1555,7 +1856,11 @@ sub pc92_handle_first_slot
        my ($call, $is_node, $is_extnode, $here, $version, $build) = @$slot;
        if ($call && $is_node) {
                if ($call eq $main::mycall) {
-                       dbg("PCPROT: $call looped back onto $main::mycall, ignored") if isdbg('chanerr');
+                       LogDbg('err', "PCPROT: $self->{call} : $call looped back onto \$main::mycall ($main::mycall), ignored");
+                       return;
+               }
+               if ($call eq $main::myalias) {
+                       LogDbg('err', "PCPROT: $self->{call} : $call looped back onto \$main::myalias ($main::myalias), ignored");
                        return;
                }
                # this is only accepted from my "self".
@@ -1572,7 +1877,7 @@ sub pc92_handle_first_slot
                        # from the true parent node for this external before we get one for the this node
                        unless ($parent = Route::Node::get($call)) {
                                if ($is_extnode && $oparent) {
-                                       @radd = _add_thingy($oparent, $slot);
+                                       @radd = _add_thingy($oparent, $slot, $self, $hops);
                                        $parent = $radd[0];
                                } else {
                                        dbg("PCPROT: no previous C or A for this external node received, ignored") if isdbg('chanerr');
@@ -1581,7 +1886,7 @@ sub pc92_handle_first_slot
                        }
                        $parent = check_pc9x_t($call, $t, 92) || return;
                        $parent->via_pc92(1);
-                       $parent->PC92C_dxchan($self->{call});
+                       $parent->PC92C_dxchan($self->{call}, $hops);
                }
        } else {
                dbg("PCPROT: must be \$mycall or external node as first entry, ignored") if isdbg('chanerr');
@@ -1589,8 +1894,8 @@ sub pc92_handle_first_slot
        }
        $parent->here(Route::here($here));
        $parent->version($version || $pc19_version) if $version;
-       $parent->build($build) if $build;
-       $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+    $parent->build($build) if $build;
+       $parent->PC92C_dxchan($self->{call}, $hops) unless $self->{call} eq $parent->call;
        return ($parent, @radd);
 }
 
@@ -1601,29 +1906,41 @@ sub handle_92
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        my (@radd, @rdel);
 
-       my $pcall = $_[1];
-       my $t = $_[2];
-       my $sort = $_[3];
+       my $pcall = $pc->[1];
+       my $t = $pc->[2];
+       my $sort = $pc->[3];
+       my $hops = $pc->[-1];
+
+       # this catches loops of A/Ds
+#      if (eph_dup($line, $pc9x_dupe_age)) {
+#              return;
+#      }
 
        if ($pcall eq $main::mycall) {
-               dbg("PCPROT: looped back, ignored") if isdbg('chanerr');
+               LogDbg('err', "PCPROT: looped back, ignored");
+               return;
+       }
+
+       if ($pcall eq $main::myalias) {
+               LogDbg('err', "PCPROT: looped back to \$myalias ($main::myalias), misconfiguration ignored");
                return;
        }
 
        if ($pcall eq $self->{call} && $self->{state} eq 'init') {
                if ($self->{isolate}) {
-                       dbg("PC9x received, but $pcall is isolated, ignored");
+                       dbg("DXPROT: PC9x received, but $pcall is isolated, ignored");
                        return;
                } elsif (!$self->user->wantpc9x) {
-                       dbg("PC9x explicitly switched off on $pcall, ignored");
+                       dbg("DXPROT: PC9x explicitly switched off on $pcall, ignored");
                        return;
                } else {
                        $self->state('init92');
                        $self->{do_pc9x} = 1;
-                       dbg("Do pc9x set on $pcall");
+                       dbg("DXPROT: Do pc9x set on $pcall");
                }
        }
        unless ($self->{do_pc9x}) {
@@ -1631,7 +1948,9 @@ sub handle_92
                return;
        }
 
-       my $parent = check_pc9x_t($pcall, $t, 92, 1) || return;
+       # don't create routing entries for D records that don't already exist
+       # this is what causes all those PC92 loops!
+       my $parent = check_pc9x_t($pcall, $t, 92, $sort ne 'D') || return;
        my $oparent = $parent;
 
        $parent->do_pc9x(1);
@@ -1643,8 +1962,8 @@ sub handle_92
                # here is where the consequences of the 'find' command
                # are dealt with
 
-               my $from = $_[4];
-               my $target = $_[5];
+               my $from = $pc->[4];
+               my $target = $pc->[5];
 
                if ($sort eq 'F') {
                        my $flag;
@@ -1663,7 +1982,7 @@ sub handle_92
                        }
                } elsif ($sort eq 'R') {
                        if (my $dxchan = DXChannel::get($from)) {
-                               handle_pc92_find_reply($dxchan, $pcall, $from, $target, @_[6,7]);
+                               handle_pc92_find_reply($dxchan, $pcall, $from, $target, @$pc[6,7]);
                        } else {
                                my $ref = Route::get($from);
                                if ($ref) {
@@ -1671,35 +1990,66 @@ sub handle_92
                                        if (@dxchan) {
                                                $_->send($line) for @dxchan;
                                        } else {
-                                               dbg("PCPROT: no return route, ignored") if isdbg('chanerr')
+                                               dbg("PCPROT: $self->{call} : type R no return route, ignored") if isdbg('chanerr') || isdbg('route');
                                        }
                                } else {
-                                       dbg("PCPROT: no return route, ignored") if isdbg('chanerr')
+                                       dbg("PCPROT: $self->{call} : type R no return route, ignored") if isdbg('chanerr') || isdbg('route');
                                }
                        }
                        return;
                }
 
        } elsif ($sort eq 'K') {
-               $pc92Kin += length $line if $sort eq 'K';
+               $pc92Kin += length $line;
 
                # remember the last channel we arrived on
-               $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+               $parent->PC92C_dxchan($self->{call}, $hops) unless $self->{call} eq $parent->call;
 
-               my @ent = _decode_pc92_call($_[4]);
+               my @ent = _decode_pc92_call($pc->[4]);
 
                if (@ent) {
                        my $add;
 
-                       ($parent, $add) = $self->pc92_handle_first_slot(\@ent, $parent, $t);
+                       ($parent, $add) = $self->pc92_handle_first_slot(\@ent, $parent, $t, $hops);
                        return unless $parent; # dupe
-
+                       
                        push @radd, $add if $add;
                        $parent->reset_obs;
-                       $parent->version($ent[4]) if $ent[4];
-                       $parent->build($ent[5]) if $ent[5];
-
-                       dbg("ROUTE: reset obscount on $parent->{call} now " . $parent->obscount) if isdbg('obscount');
+                       my $call = $parent->call;
+                       my $version = $ent[4] || 0;
+                       my $build = $ent[5] ||  0;
+                       $build =~ s/^0\.//;
+                       my $oldbuild = $parent->build || 0;
+                       my $oldversion = $parent->version || 0;
+                       my $user = check_add_user($parent->call, 'S');
+                       my $oldsort = $user->sort // '';
+
+                       dbg("PCProt PC92 K v: $version ov: $oldversion b: $build ob: $oldbuild pk: " . ($parent->K || '0') . " uk: " . ($user->K || 0)) if isdbg('pc92k');
+                               
+                       if (is_numeric($version) || is_numeric($build)) {
+                               my $changed = 0;
+                               if (($oldversion ne $version || $build ne $oldbuild)) {
+                                       dbg("PCProt PC92 K node $call updated version: $version (was $oldversion) build: $build (was $oldbuild)");
+                                       $user->version($parent->version($version));
+                                       $user->build($parent->build($build));
+                                       ++$changed;
+                               }
+                               if ($oldsort ne 'S') {
+                                       dbg("PCProt PC92 K node $call updated sort: $sort (was $oldsort)");
+                                       $user->sort('S');
+                                       ++$changed;
+                               }
+                               unless ($user->K) {
+                                       dbg("PCProt PC92 K node $call updated - marked as PC92 K user");
+                                       $user->K(1);
+                                       ++$changed;
+                               }
+                               $user->put if $changed;
+                               $parent->K(1); # mark this as come in on a K
+                       } else {
+                               dbg("DXProt PC92 K version call $call: invalid version: '$version' or build: '$version', ignored");
+                       }
+                       dbg("ROUTE: reset obscount on $call now " . $parent->obscount) if isdbg('obscount');
                }
        } elsif ($sort eq 'A' || $sort eq 'D' || $sort eq 'C') {
 
@@ -1708,16 +2058,16 @@ sub handle_92
                $pc92Din += length $line if $sort eq 'D';
 
                # remember the last channel we arrived on
-               $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+               $parent->PC92C_dxchan($self->{call}, $hops) unless $self->{call} eq $parent->call;
 
                # this is the main route section
                # here is where all the routes are created and destroyed
 
                # cope with missing duplicate node calls in the first slot
-               my $me = $_[4] || '';
+               my $me = $pc->[4] || '';
                $me ||= _encode_pc92_call($parent) unless $me ;
 
-               my @ent = map {my @a = _decode_pc92_call($_); @a ? \@a : ()} grep {$_ && /^[0-7]/} $me, @_[5 .. $#_];
+               my @ent = map {my @a = _decode_pc92_call($_); @a ? \@a : ()} grep {$_ && /^[0-7]/} $me, @$pc[5 .. $#$pc];
 
                if (@ent) {
 
@@ -1727,31 +2077,37 @@ sub handle_92
                        # that needs to be done.
                        my $add;
 
-                       ($parent, $add) = $self->pc92_handle_first_slot($ent[0], $parent, $t);
+                       ($parent, $add) = $self->pc92_handle_first_slot($ent[0], $parent, $t, $hops);
                        return unless $parent; # dupe
 
                        shift @ent;
                        push @radd, $add if $add;
                }
 
-               # do a pass through removing any references to either locally connected nodes or mycall
+               # do a pass through removing any references to either mycall
                my @nent;
                for (@ent) {
+                       my $dxc;
                        next unless $_ && @$_;
-                       if ($_->[0] eq $main::mycall || DXChannel::get($_->[0])) {
-                               dbg("PCPROT: $_->[0] refers to locally connected node, ignored") if isdbg('chanerr');
+                       if ($_->[0] eq $main::mycall) {
+                               dbg("PCPROT: $self->{call} : type $sort $_->[0] refers to me, ignored") if isdbg('route');
+                               next;
+                       }
+                       if ($_->[0] eq $main::myalias && $_->[1] || $_->[0] eq $main::mycall && $_->[1] == 0) {
+                               LogDbg('err',"PCPROT: $self->{call} : type $sort $_->[0] trying to change type to " . $_->[1]?"Node":"User" . ", ignored");
                                next;
                        }
+                       
                        push @nent, $_;
                }
 
                if ($sort eq 'A') {
                        for (@nent) {
-                               push @radd, _add_thingy($parent, $_);
+                               push @radd, _add_thingy($parent, $_, $self, $hops);
                        }
                } elsif ($sort eq 'D') {
                        for (@nent) {
-                               push @rdel, _del_thingy($parent, $_);
+                               push @rdel, _del_thingy($parent, $_, $self);
                        }
                } elsif ($sort eq 'C') {
                        my (@nodes, @users);
@@ -1770,7 +2126,7 @@ sub handle_92
                                                push @users, $r->[0];
                                        }
                                } else {
-                                       dbg("PCPROT: pc92 call entry '$_' not decoded, ignored") if isdbg('chanerr');
+                                       dbg("PCPROT: $self->{call} :  pc92 call entry '$_' not decoded, ignored") if isdbg('chanerr') || isdbg('route');
                                }
                        }
 
@@ -1780,15 +2136,15 @@ sub handle_92
                        foreach my $r (@nent) {
                                my $call = $r->[0];
                                if ($call) {
-                                       push @radd,_add_thingy($parent, $r) if grep $call eq $_, (@$nnodes, @$nusers);
+                                       push @radd,_add_thingy($parent, $r, $self, $hops) if grep $call eq $_, (@$nnodes, @$nusers);
                                }
                        }
                        # del users here
                        foreach my $r (@$dnodes) {
-                               push @rdel,_del_thingy($parent, [$r, 1]);
+                               push @rdel,_del_thingy($parent, [$r, 1], $self);
                        }
                        foreach my $r (@$dusers) {
-                               push @rdel,_del_thingy($parent, [$r, 0]);
+                               push @rdel,_del_thingy($parent, [$r, 0], $self);
                        }
 
                        # remember this last PC92C for rebroadcast on demand
@@ -1815,6 +2171,42 @@ sub handle_92
        $self->broadcast_route_pc9x($pcall, undef, $line, 0);
 }
 
+# get all the routes for a thing, bearing in mind that the thing (e.g. a user)
+# might be on two or more nodes at the same time or that there may be more than
+# one equal distance neighbour to a node.
+#
+# What this means that if sh/route g1tlh shows that he is on (say) two nodes, then
+# a Route::findroutes is done on each of those two nodes, the best route(s) taken from
+# each and then combined to give a set of dxchans to send the PC9x record down
+#
+sub find_pc9x_routes
+{
+       my $to = shift;
+       my $ref = Route::get($to);
+       my @parent;
+       my %cand;
+
+       if ($ref->isa('Route::User')) {
+               my $dxchan = DXChannel::get($to);
+               push @parent, $to if $dxchan;
+               push @parent, $ref->parents;
+       } else {
+               @parent = $to;
+       }
+       foreach my $p (@parent) {
+               my $lasthops;
+               my @routes = Route::findroutes($p);
+               foreach my $r (@routes) {
+                       $lasthops = $r->[0] unless defined $lasthops;
+                       if ($r->[0] == $lasthops) {
+                               $cand{$r->[1]->call} = $r->[1];
+                       } else {
+                               last;
+                       }
+               }
+       }
+       return values %cand;
+}
 
 sub handle_93
 {
@@ -1822,10 +2214,11 @@ sub handle_93
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
 #      $self->{do_pc9x} ||= 1;
 
-       my $pcall = $_[1];                      # this is now checked earlier
+       my $pcall = $pc->[1];                   # this is now checked earlier
 
        # remember that we are converting PC10->PC93 and self will be $main::me if it
        # comes from us
@@ -1834,21 +2227,27 @@ sub handle_93
                return;
        }
 
-       my $t = $_[2];
+       my $t = $pc->[2];
        my $parent = check_pc9x_t($pcall, $t, 93, 1) || return;
 
-       my $to = uc $_[3];
-       my $from = uc $_[4];
-       my $via = uc $_[5];
-       my $text = $_[6];
-       my $onode = uc $_[7];
-       $onode = $pcall if @_ <= 8;
+       my $to = uc $pc->[3];
+       my $from = uc $pc->[4];
+       my $via = uc $pc->[5];
+       my $text = $pc->[6];
+       my $onode = uc $pc->[7];
+       $onode = $pcall if @$pc <= 8;
 
        # this is catch loops caused by bad software ...
        if (eph_dup("PC93|$from|$text|$onode", $pc10_dupe_age)) {
                return;
        }
 
+       if (isdbg('progress')) {
+               my $vs = $via ne '*' ? " via $via" : ''; 
+               my $s = "ANNTALK: $from\@$onode$vs -> $to '$text' route: $origin";
+               dbg($s);
+       }
+       
        # will we allow it at all?
        if ($censorpc) {
                my @bad;
@@ -1866,39 +2265,37 @@ sub handle_93
                return;
        }
 
+       # ignore PC93 coming in from outside this node with a target of local
+       if ($to eq 'LOCAL' && $self != $main::me) {
+               dbg("PCPROT: incoming LOCAL chat not from local node, ignored") if isdbg('chanerr');
+               return;
+       }
+
        # if it is routeable then then treat it like a talk
        my $ref = Route::get($to);
        if ($ref) {
-               # local talks
                my $dxchan;
-               $dxchan = DXChannel::get($main::myalias) if $to eq $main::mycall;
-               $dxchan = DXChannel::get($to) unless $dxchan;
-               # check it...
-               if ($dxchan) {
+
+               # convert to PC10 or local talks where appropriate
+               # PC93 capable nodes of the same hop count all get a copy
+               # if there is a PC10 node then it will get a copy and that
+               # will be it. Hopefully such a node will not figure highly
+               # in the route list, unless it is local, 'cos it don't issue PC92s!
+               # note that both local and PC93s at the same time are possible if the
+               # user on more than one node.
+               my @routes = find_pc9x_routes($to);
+               my $lasthops;
+               foreach $dxchan (@routes) {
                        if (ref $dxchan && $dxchan->isa('DXChannel')) {
-                               if ($dxchan->is_user) {
+                               if ($dxchan->{do_pc9x}) {
+                                       $dxchan->send($line);
+                               } else {
                                        $dxchan->talk($from, $to, $via, $text, $onode);
-                                       return;
                                }
                        } else {
-                               dbg("ERROR: $to -> $dxchan is not a DXChannel! (local talk)");
+                               dbg("ERROR: $to -> $dxchan is not a DXChannel! (convert to pc10)");
                        }
                }
-
-               # convert to PC10 talks where appropriate
-               # 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;
 
        } elsif ($to eq '*' || $to eq 'SYSOP' || $to eq 'WX') {
@@ -1910,10 +2307,12 @@ sub handle_93
                $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';
        } elsif (!is_callsign($to) && $text =~ /^#\d+ /) {
-               # chat messages to non-pc9x nodes
-               $self->send_chat(1, pc12($from, $text, undef, $to, undef, $pcall), $from, '*', $text, $to, $pcall, '0');
+               # chat messages really only locally connected users
+               $self->send_chat(1, $line, $from, '*', $text, $to, $pcall, '0');
        }
-       $self->broadcast_route_pc9x($pcall, undef, $line, 0);
+
+       # broadcast this chat sentence everywhere unless it is targetted to 'LOCAL'
+       $self->broadcast_route_pc9x($pcall, undef, $line, 0) unless $to eq 'LOCAL' || $via eq 'LOCAL';
 }
 
 # if get here then rebroadcast the thing with its Hop count decremented (if
@@ -1930,15 +2329,16 @@ sub handle_default
        my $pcno = shift;
        my $line = shift;
        my $origin = shift;
+       my $pc = shift;
 
        unless (eph_dup($line)) {
                if ($pcno >= 90) {
-                       my $pcall = $_[1];
+                       my $pcall = $pc->[1];
                        unless (is_callsign($pcall)) {
-                               dbg("PCPROT: invalid callsign string '$_[1]', ignored") if isdbg('chanerr');
+                               dbg("PCPROT: invalid callsign string '$pc->[1]', ignored") if isdbg('chanerr');
                                return;
                        }
-                       my $t = $_[2];
+                       my $t = $pc->[2];
                        my $parent = check_pc9x_t($pcall, $t, $pcno, 1) || return;
                        $self->broadcast_route_pc9x($pcall, undef, $line, 0);
                } else {