+ my ($self, $line) = @_;
+ my @field = split /\^/, $line;
+ return unless @field;
+
+ pop @field if $field[-1] eq '~';
+
+# print join(',', @field), "\n";
+
+ # ignore any lines that don't start with PC
+ return if !$field[0] =~ /^PC/;
+
+ # process PC frames
+ my ($pcno) = $field[0] =~ /^PC(\d\d)/; # just get the number
+ return unless $pcno;
+ return if $pcno < 10 || $pcno > 99;
+
+ # check for and dump bad protocol messages
+ my $n = check($pcno, @field);
+ if ($n) {
+ dbg('chan', "PCPROT: bad field $n, dumped (" . parray($checklist[$pcno-10]) . ")");
+ return;
+ }
+
+ # local processing 1
+ my $pcr;
+ eval {
+ $pcr = Local::pcprot($self, $pcno, @field);
+ };
+# dbg('local', "Local::pcprot error $@") if $@;
+ return if $pcr;
+
+ SWITCH: {
+ if ($pcno == 10) { # incoming talk
+
+ # will we allow it at all?
+ if ($censorpc) {
+ my @bad;
+ if (@bad = BadWords::check($field[3])) {
+ dbg('chan', "PCPROT: Bad words: @bad, dropped" );
+ return;
+ }
+ }
+
+ # is it for me or one of mine?
+ my ($to, $via, $call, $dxchan);
+ if ($field[5] gt ' ') {
+ $call = $via = $field[2];
+ $to = $field[5];
+ } else {
+ $call = $to = $field[2];
+ }
+ $dxchan = DXChannel->get($call);
+ if ($dxchan && $dxchan->is_user) {
+ $field[3] =~ s/\%5E/^/g;
+ $dxchan->talk($field[1], $to, $via, $field[3]);
+ } else {
+ $self->route($field[2], $line); # relay it on its way
+ }
+ return;
+ }
+
+ if ($pcno == 11 || $pcno == 26) { # dx spot
+
+ # route 'foreign' pc26s
+ if ($pcno == 26) {
+ if ($field[7] ne $main::mycall) {
+ $self->route($field[7], $line);
+ return;
+ }
+ }
+
+ # if this is a 'nodx' node then ignore it
+ 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;
+ }
+
+ # convert the date to a unix date
+ my $d = cltounix($field[3], $field[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('chan', "PCPROT: Spot ignored, invalid date or out of range ($field[3] $field[4])\n");
+ return;
+ }
+
+ # is it '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;
+ }
+ if ($censorpc) {
+ my @bad;
+ if (@bad = BadWords::check($field[5])) {
+ dbg('chan', "PCPROT: Bad words: @bad, dropped" );
+ return;
+ }
+ }
+
+ my @spot = Spot::add($field[1], $field[2], $d, $field[5], $field[6], $field[7]);
+
+ #
+ # @spot at this point contains:-
+ # freq, spotted call, time, text, spotter, spotted cc, spotters cc, orig node
+ # then spotted itu, spotted cq, spotters itu, spotters cq
+ # you should be able to route on any of these
+ #
+
+ # fix up qra locators of known users
+ my $user = DXUser->get_current($spot[4]);
+ if ($user) {
+ my $qra = $user->qra;
+ unless ($qra && DXBearing::is_qra($qra)) {
+ my $lat = $user->lat;
+ my $long = $user->long;
+ if (defined $lat && defined $long) {
+ $user->qra(DXBearing::lltoqra($lat, $long));
+ $user->put;
+ }
+ }
+
+ # send a remote command to a distant cluster if it is visible and there is no
+ # qra locator and we havn't done it for a month.
+
+ unless ($user->qra) {
+ 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)) ) {
+ 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) {
+ 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));
+ }
+ }
+ $user->lastoper($main::systime);
+ $user->put;
+ }
+ }
+ }
+
+ # local processing
+ my $r;
+ eval {
+ $r = Local::spot($self, @spot);
+ };
+# dbg('local', "Local::spot1 error $@") if $@;
+ return if $r;
+
+ # DON'T be silly and send on PC26s!
+ return if $pcno == 26;
+
+ # send out the filtered spots
+ send_dx_spot($self, $line, @spot) if @spot;
+ return;
+ }
+
+ if ($pcno == 12) { # announces
+ # announce duplicate checking
+ $field[3] =~ s/^\s+//; # remove leading blanks
+ if (AnnTalk::dup($field[1], $field[2], $field[3])) {
+ dbg('chan', "PCPROT: Duplicate Announce ignored");
+ return;
+ }
+
+ if ($censorpc) {
+ my @bad;
+ if (@bad = BadWords::check($field[3])) {
+ dbg('chan', "PCPROT: Bad words: @bad, dropped" );
+ return;
+ }
+ }
+
+ if ($field[2] eq '*' || $field[2] eq $main::mycall) {
+
+ # global ann filtering on INPUT
+ if ($self->{inannfilter}) {
+ my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
+ my @dxcc = Prefix::extract($field[1]);
+ if (@dxcc > 0) {
+ $ann_dxcc = $dxcc[1]->dxcc;
+ $ann_itu = $dxcc[1]->itu;
+ $ann_cq = $dxcc[1]->cq();
+ }
+ @dxcc = Prefix::extract($field[5]);
+ if (@dxcc > 0) {
+ $org_dxcc = $dxcc[1]->dxcc;
+ $org_itu = $dxcc[1]->itu;
+ $org_cq = $dxcc[1]->cq();
+ }
+ my ($filter, $hops) = $self->{inannfilter}->it(@field[1..6], $self->{call},
+ $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq);
+ unless ($filter) {
+ dbg('chan', "PCPROT: Rejected by filter");
+ return;
+ }
+ }
+
+ # send it
+ $self->send_announce($line, @field[1..6]);
+ } else {
+ $self->route($field[2], $line);
+ }
+
+ return;
+ }
+
+ if ($pcno == 13) {
+ last SWITCH;
+ }
+ if ($pcno == 14) {
+ last SWITCH;
+ }
+ if ($pcno == 15) {
+ last SWITCH;
+ }
+
+ if ($pcno == 16) { # add a user
+ my $node = DXCluster->get_exact($field[1]);
+ 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) {
+ 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");
+ return;
+ }
+ if (($dxchan = DXChannel->get($field[1])) && $dxchan != $self) {
+ dbg('chan', "PCPROT: $field[1] connected locally");
+ return;
+ }
+ my $i;
+
+ 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);
+
+ # 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->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;
+ }
+
+ 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) {
+ 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");
+ return;
+ }
+ unless ($node->isa('DXNode')) {
+ dbg('chan', "PCPROT: $field[2] is not a node");
+ return;
+ }
+ if ($node->dxchan != $self) {
+ dbg('chan', "PCPROT: $field[2] came in on wrong channel");
+ return;
+ }
+ if ($dxchan = DXChannel->get($field[1])) {
+ dbg('chan', "PCPROT: $field[1] connected locally");
+ return;
+ }
+ my $ref = DXCluster->get_exact($field[1]);
+ if ($ref) {
+ if ($ref->mynode != $node) {
+ dbg('chan', "PCPROT: $field[1] came in from wrong node $field[2]");
+ return;
+ }
+ $ref->del;
+ } else {
+ dbg('chan', "PCPROT: $field[1] not known" );
+ return;
+ }
+ last SWITCH;
+ }
+
+ 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();
+ }
+ $self->send_local_config();
+ $self->send(pc20());
+ return; # we don't pass these on
+ }
+
+ if ($pcno == 19) { # incoming cluster list
+ my $i;
+ my $newline = "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 $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;
+ }
+
+ # check for sane parameters
+ 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);
+
+ # 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;
+
+ # add this station to the user database, if required (don't remove SSID from nodes)
+ my $user = DXUser->get_current($call);
+ if (!$user) {
+ $user = DXUser->new($call);
+ $user->sort('A');
+ $user->priv(1); # I have relented and defaulted nodes
+ $self->{priv} = 1; # to user RCMDs allowed
+ $user->homenode($call);
+ $user->node($call);
+ }
+ $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;
+ }
+
+ if ($pcno == 20) { # send local configuration
+ $self->send_local_config();
+ $self->send(pc22());
+ $self->state('normal');
+ return;
+ }
+
+ if ($pcno == 21) { # delete a cluster from the list
+ my $call = uc $field[1];
+ 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");
+ return;
+ }
+ } else {
+ dbg('chan', "PCPROT: I WILL _NOT_ be disconnected!");
+ return;
+ }
+ last SWITCH;
+ }
+
+ if ($pcno == 22) {
+ $self->state('normal');
+ return;
+ }
+
+ if ($pcno == 23 || $pcno == 27) { # WWV info
+
+ # route 'foreign' pc27s
+ if ($pcno == 27) {
+ if ($field[8] ne $main::mycall) {
+ $self->route($field[8], $line);
+ return;
+ }
+ }
+
+ # do some de-duping
+ my $d = cltounix($field[1], sprintf("%02d18Z", $field[2]));
+ my $sfi = unpad($field[3]);
+ my $k = unpad($field[4]);
+ my $i = unpad($field[5]);
+ my ($r) = $field[6] =~ /R=(\d+)/;
+ $r = 0 unless $r;
+ if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $field[2] < 0 || $field[2] > 23) {
+ dbg('chan', "PCPROT: WWV Date ($field[1] $field[2]) out of range");
+ return;
+ }
+ if (Geomag::dup($d,$sfi,$k,$i,$field[6])) {
+ dbg('chan', "PCPROT: Dup WWV Spot ignored\n");
+ return;
+ }
+ $field[7] =~ s/-\d+$//o; # remove spotter's ssid
+
+ my $wwv = Geomag::update($d, $field[2], $sfi, $k, $i, @field[6..8], $r);
+
+ my $rep;
+ eval {
+ $rep = Local::wwv($self, $field[1], $field[2], $sfi, $k, $i, @field[6..8], $r);
+ };
+# dbg('local', "Local::wwv2 error $@") if $@;
+ 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, $field[2], $sfi, $k, $i, @field[6..8]);
+ return;
+ }
+
+ if ($pcno == 24) { # set here status
+ my $call = uc $field[1];
+ my $ref = DXCluster->get_exact($call);
+ $ref->here($field[2]) if $ref;
+ last SWITCH;
+ }
+
+ if ($pcno == 25) { # merge request
+ if ($field[1] ne $main::mycall) {
+ $self->route($field[1], $line);
+ return;
+ }
+ if ($field[2] eq $main::mycall) {
+ dbg('chan', "PCPROT: Trying to merge to myself, ignored");
+ return;
+ }
+
+ Log('DXProt', "Merge request for $field[3] spots and $field[4] WWV from $field[1]");
+
+ # spots
+ if ($field[3] > 0) {
+ my @in = reverse Spot::search(1, undef, undef, 0, $field[3]);
+ my $in;
+ foreach $in (@in) {
+ $self->send(pc26(@{$in}[0..4], $field[2]));
+ }
+ }
+
+ # wwv
+ if ($field[4] > 0) {
+ my @in = reverse Geomag::search(0, $field[4], time, 1);
+ my $in;
+ foreach $in (@in) {
+ $self->send(pc27(@{$in}[0..5], $field[2]));
+ }
+ }
+ return;
+ }
+
+ if (($pcno >= 28 && $pcno <= 33) || $pcno == 40 || $pcno == 42 || $pcno == 49) { # mail/file handling
+ if ($pcno == 49 || $field[1] eq $main::mycall) {
+ DXMsg::process($self, $line);
+ } else {
+ $self->route($field[1], $line) unless $self->is_clx;
+ }
+ return;
+ }
+
+ 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]);
+ Log('rcmd', 'in', $ref->{priv}, $field[2], $field[3]);
+ unless (!$cref || !$ref || $cref->mynode->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};
+ $self->{priv} = $ref->{priv}; # assume the user's privilege level
+ my @in = (DXCommandmode::run_cmd($self, $field[3]));
+ $self->{priv} = $oldpriv;
+ for (@in) {
+ s/\s*$//og;
+ $self->send(pc35($main::mycall, $field[2], "$main::mycall:$_"));
+ Log('rcmd', 'out', $field[2], $_);
+ }
+ delete $self->{remotecmd};
+ } else {
+ $self->send(pc35($main::mycall, $field[2], "$main::mycall:sorry...!"));
+ }
+ } else {
+ $self->send(pc35($main::mycall, $field[2], "$main::mycall:your attempt is logged, Tut tut tut...!"));
+ }
+ } else {
+ my $ref = DXUser->get_current($field[1]);
+ if ($ref && $ref->is_clx) {
+ $self->route($field[1], pc84($field[2], $field[1], $field[2], $field[3]));
+ } else {
+ $self->route($field[1], $line);
+ }
+ }
+ return;
+ }
+
+ if ($pcno == 35) { # remote command replies
+ if ($field[1] eq $main::mycall) {
+ my $s = $rcmds{$field[2]};
+ if ($s) {
+ my $dxchan = DXChannel->get($s->{call});
+ $dxchan->send($field[3]) if $dxchan;
+ delete $rcmds{$field[2]} if !$dxchan;
+ } else {
+ # send unsolicited ones to the sysop
+ my $dxchan = DXChannel->get($main::myalias);
+ $dxchan->send($field[3]) if $dxchan;
+ }
+ } else {
+ my $ref = DXUser->get_current($field[1]);
+ if ($ref && $ref->is_clx) {
+ $self->route($field[1], pc85($field[2], $field[1], $field[2], $field[3]));
+ } else {
+ $self->route($field[1], $line);
+ }
+ }
+ return;
+ }
+
+ # for pc 37 see 44 onwards
+
+ if ($pcno == 38) { # node connected list from neighbour
+ return;
+ }
+
+ if ($pcno == 39) { # incoming disconnect
+ if ($field[1] eq $self->{call}) {
+ $self->disconnect(1);
+ } else {
+ dbg('chan', "PCPROT: came in on wrong channel");
+ }
+ return;
+ }
+
+ if ($pcno == 41) { # user info
+ # add this station to the user database, if required
+ my $user = DXUser->get_current($field[1]);
+ $user = DXUser->new($field[1]) if !$user;
+
+ if ($field[2] == 1) {
+ $user->name($field[3]);
+ } elsif ($field[2] == 2) {
+ $user->qth($field[3]);
+ } elsif ($field[2] == 3) {
+ my ($lat, $long) = DXBearing::stoll($field[3]);
+ $user->lat($lat);
+ $user->long($long);
+ $user->qra(DXBearing::lltoqra($lat, $long)) unless $user->qra && DXBearing::is_qra($user->qra);
+ } elsif ($field[2] == 4) {
+ $user->homenode($field[3]);
+ }
+ $user->lastoper($main::systime); # to cut down on excessive for/opers being generated
+ $user->put;
+ last SWITCH;
+ }
+ if ($pcno == 43) {
+ last SWITCH;
+ }
+ if ($pcno == 37 || $pcno == 44 || $pcno == 45 || $pcno == 46 || $pcno == 47 || $pcno == 48) {
+ DXDb::process($self, $line);
+ return;
+ }
+
+ if ($pcno == 50) { # keep alive/user list
+ my $node = DXCluster->get_exact($field[1]);
+ if ($node) {
+ return unless $node->isa('DXNode');
+ return unless $node->dxchan == $self;
+ $node->update_users($field[2]);
+ }
+ last SWITCH;
+ }
+
+ if ($pcno == 51) { # incoming ping requests/answers
+
+ # is it for us?
+ if ($field[1] eq $main::mycall) {
+ my $flag = $field[3];
+ if ($flag == 1) {
+ $self->send(pc51($field[2], $field[1], '0'));
+ } else {
+ # it's a reply, look in the ping list for this one
+ my $ref = $pings{$field[2]};
+ if ($ref) {
+ my $tochan = DXChannel->get($field[2]);
+ while (@$ref) {
+ my $r = shift @$ref;
+ my $dxchan = DXChannel->get($r->{call});
+ next unless $dxchan;
+ my $t = tv_interval($r->{t}, [ gettimeofday ]);
+ if ($dxchan->is_user) {
+ my $s = sprintf "%.2f", $t;
+ my $ave = sprintf "%.2f", $tochan ? ($tochan->{pingave} || $t) : $t;
+ $dxchan->send($dxchan->msg('pingi', $field[2], $s, $ave))
+ } elsif ($dxchan->is_node) {
+ if ($tochan) {
+ $tochan->{nopings} = $tochan->user->nopings || 2; # pump up the timer
+ push @{$tochan->{pingtime}}, $t;
+ shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
+ my $st;
+ for (@{$tochan->{pingtime}}) {
+ $st += $_;
+ }
+ $tochan->{pingave} = $st / @{$tochan->{pingtime}};
+ }
+ }
+ }
+ }
+ }
+ } else {
+ # route down an appropriate thingy
+ $self->route($field[1], $line);
+ }
+ return;
+ }
+
+ if ($pcno == 75) { # dunno but route it
+ if ($field[1] ne $main::mycall) {
+ $self->route($field[1], $line);
+ }
+ return;
+ }
+
+ if ($pcno == 73) { # WCY broadcasts
+
+ # do some de-duping
+ my $d = cltounix($field[1], sprintf("%02d18Z", $field[2]));
+ if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $field[2] < 0 || $field[2] > 23) {
+ dbg('chan', "PCPROT: WCY Date ($field[1] $field[2]) out of range");
+ return;
+ }
+ @field = map { unpad($_) } @field;
+ if (WCY::dup($d,@field[3..7])) {
+ dbg('chan', "PCPROT: Dup WCY Spot ignored\n");
+ return;
+ }
+
+ my $wcy = WCY::update($d, @field[2..12]);
+
+ my $rep;
+ eval {
+ $rep = Local::wwv($self, @field[1..12]);
+ };
+ # dbg('local', "Local::wcy error $@") if $@;
+ return if $rep;
+
+ # broadcast to the eager world
+ send_wcy_spot($self, $line, $d, @field[2..12]);
+ return;
+ }
+
+ 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]);
+ 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!
+ 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};
+ $self->{priv} = $ref->{priv}; # assume the user's privilege level
+ my @in = (DXCommandmode::run_cmd($self, $field[4]));
+ $self->{priv} = $oldpriv;
+ for (@in) {
+ s/\s*$//og;
+ $self->send(pc85($main::mycall, $field[2], $field[3], "$main::mycall:$_"));
+ Log('rcmd', 'out', $field[2], $_);
+ }
+ delete $self->{remotecmd};
+ } else {
+ $self->send(pc85($main::mycall, $field[2], $field[3], "$main::mycall:sorry...!"));
+ }
+ } else {
+ $self->send(pc85($main::mycall, $field[2], $field[3],"$main::mycall:your attempt is logged, Tut tut tut...!"));
+ }
+ } else {
+ my $ref = DXUser->get_current($field[1]);
+ if ($ref && $ref->is_clx) {
+ $self->route($field[1], $line);
+ } else {
+ $self->route($field[1], pc34($field[2], $field[1], $field[4]));
+ }
+ }
+ return;
+ }
+
+ if ($pcno == 85) { # remote command replies
+ if ($field[1] eq $main::mycall) {
+ my $dxchan = DXChannel->get($field[3]);
+ if ($dxchan) {
+ $dxchan->send($field[4]);
+ } else {
+ my $s = $rcmds{$field[2]};
+ if ($s) {
+ $dxchan = DXChannel->get($s->{call});
+ $dxchan->send($field[4]) if $dxchan;
+ delete $rcmds{$field[2]} if !$dxchan;
+ } else {
+ # send unsolicited ones to the sysop
+ my $dxchan = DXChannel->get($main::myalias);
+ $dxchan->send($field[4]) if $dxchan;
+ }
+ }
+ } else {
+ my $ref = DXUser->get_current($field[1]);
+ if ($ref && $ref->is_clx) {
+ $self->route($field[1], $line);
+ } else {
+ $self->route($field[1], pc35($field[2], $field[1], $field[4]));
+ }
+ }
+ return;
+ }
+ }
+
+ # if get here then rebroadcast the thing with its Hop count decremented (if
+ # there is one). If it has a hop count and it decrements to zero then don't
+ # rebroadcast it.
+ #
+ # NOTE - don't arrive here UNLESS YOU WANT this lump of protocol to be
+ # REBROADCAST!!!!
+ #
+
+ unless ($self->{isolate}) {
+ broadcast_ak1a($line, $self); # send it to everyone but me
+ }