+ if ($_[3] eq $_[2] || $_[3] =~ /^\s*$/) {
+ dbg('PCPROT: invalid value') if isdbg('chanerr');
+ return;
+ }
+
+ # add this station to the user database, if required
+ my $user = DXUser->get_current($call);
+ $user = DXUser->new($call) unless $user;
+
+ if ($_[2] == 1) {
+ $user->name($_[3]);
+ } elsif ($_[2] == 2) {
+ $user->qth($_[3]);
+ } elsif ($_[2] == 3) {
+ if (is_latlong($_[3])) {
+ my ($lat, $long) = DXBearing::stoll($_[3]);
+ $user->lat($lat);
+ $user->long($long);
+ $user->qra(DXBearing::lltoqra($lat, $long));
+ } else {
+ dbg('PCPROT: not a valid lat/long') if isdbg('chanerr');
+ return;
+ }
+ } elsif ($_[2] == 4) {
+ $user->homenode($_[3]);
+ } elsif ($_[2] == 5) {
+ if (is_qra(uc $_[3])) {
+ my ($lat, $long) = DXBearing::qratoll(uc $_[3]);
+ $user->lat($lat);
+ $user->long($long);
+ $user->qra(uc $_[3]);
+ } else {
+ dbg('PCPROT: not a valid QRA locator') if isdbg('chanerr');
+ return;
+ }
+ }
+ $user->lastoper($main::systime); # to cut down on excessive for/opers being generated
+ $user->put;
+
+ unless ($self->{isolate}) {
+ DXChannel::broadcast_nodes($line, $self); # send it to everyone but me
+ }
+
+ # perhaps this IS what we want after all
+ # $self->route_pc41($ref, $call, $_[2], $_[3], $_[4]);
+}
+
+sub handle_42 {goto &handle_28}
+
+
+# database
+sub handle_44 {goto &handle_37}
+sub handle_45 {goto &handle_37}
+sub handle_46 {goto &handle_37}
+sub handle_47 {goto &handle_37}
+sub handle_48 {goto &handle_37}
+
+# message and database
+sub handle_49
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+
+ if (eph_dup($line)) {
+ dbg("PCPROT: Dup PC49 ignored\n") if isdbg('chanerr');
+ return;
+ }
+
+ if ($_[1] eq $main::mycall) {
+ DXMsg::handle_49($self, @_);
+ } else {
+ $self->route($_[1], $line) unless $self->is_clx;
+ }
+}
+
+# keep alive/user list
+sub handle_50
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+
+ my $call = $_[1];
+ my $node = Route::Node::get($call);
+ if ($node) {
+ return unless $node->call eq $self->{call};
+ $node->usercount($_[2]);
+
+ # input filter if required
+ return unless $self->in_filter_route($node);
+
+ $self->route_pc50($node, $_[2], $_[3]) unless eph_dup($line);
+ }
+}
+
+# incoming ping requests/answers
+sub handle_51
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+ my $to = $_[1];
+ my $from = $_[2];
+ my $flag = $_[3];
+
+
+ # is it for us?
+ if ($to eq $main::mycall) {
+ if ($flag == 1) {
+ $self->send(pc51($from, $to, '0'));
+ } else {
+ # it's a reply, look in the ping list for this one
+ my $ref = $pings{$from};
+ if ($ref) {
+ my $tochan = DXChannel->get($from);
+ 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', $from, $s, $ave))
+ } elsif ($dxchan->is_node) {
+ if ($tochan) {
+ my $nopings = $tochan->user->nopings || 2;
+ push @{$tochan->{pingtime}}, $t;
+ shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
+
+ # cope with a missed ping, this means you must set the pingint large enough
+ if ($t > $tochan->{pingint} && $t < 2 * $tochan->{pingint} ) {
+ $t -= $tochan->{pingint};
+ }
+
+ # calc smoothed RTT a la TCP
+ if (@{$tochan->{pingtime}} == 1) {
+ $tochan->{pingave} = $t;
+ } else {
+ $tochan->{pingave} = $tochan->{pingave} + (($t - $tochan->{pingave}) / 6);
+ }
+ $tochan->{nopings} = $nopings; # pump up the timer
+ }
+ }
+ }
+ }
+ }
+ } else {
+ if (eph_dup($line)) {
+ dbg("PCPROT: dup PC51 detected") if isdbg('chanerr');
+ return;
+ }
+ # route down an appropriate thingy
+ $self->route($to, $line);
+ }
+}
+
+# dunno but route it
+sub handle_75
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+ my $call = $_[1];
+ if ($call ne $main::mycall) {
+ $self->route($call, $line);
+ }
+}
+
+# WCY broadcasts
+sub handle_73
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+ my $call = $_[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');
+ return;
+ }
+ @_ = map { unpad($_) } @_;
+ if (WCY::dup($d)) {
+ dbg("PCPROT: Dup WCY Spot ignored\n") if isdbg('chanerr');
+ return;
+ }
+
+ my $wcy = WCY::update($d, @_[2..12]);
+
+ my $rep;
+ eval {
+ $rep = Local::wcy($self, @_[1..12]);
+ };
+ # dbg("Local::wcy error $@") if isdbg('local') if $@;
+ return if $rep;
+
+ # broadcast to the eager world
+ send_wcy_spot($self, $line, $d, @_[2..12]);
+}
+
+# remote commands (incoming)
+sub handle_84
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+ $self->process_rcmd($_[1], $_[2], $_[3], $_[4]);
+}
+
+# remote command replies
+sub handle_85
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+ $self->process_rcmd_reply($_[1], $_[2], $_[3], $_[4]);
+}
+
+# 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!!!!
+#
+
+sub handle_default
+{
+ my $self = shift;
+ my $pcno = shift;
+ my $line = shift;
+
+ if (eph_dup($line)) {
+ dbg("PCPROT: Ephemeral dup, dropped") if isdbg('chanerr');
+ } else {
+ unless ($self->{isolate}) {
+ DXChannel::broadcast_nodes($line, $self); # send it to everyone but me
+ }
+ }
+}
+
+#
+# This is called from inside the main cluster processing loop and is used
+# for despatching commands that are doing some long processing job
+#
+sub process
+{
+ my $t = time;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+ my $pc50s;
+
+ # send out a pc50 on EVERY channel all at once
+ if ($t >= $last_pc50 + $DXProt::pc50_interval) {
+ $pc50s = pc50($main::me, scalar DXChannel::get_all_users);
+ eph_dup($pc50s);
+ $last_pc50 = $t;
+ }
+
+ foreach $dxchan (@dxchan) {
+ next unless $dxchan->is_node();
+ next if $dxchan == $main::me;
+
+ # send the pc50 or PC90
+ $dxchan->send($pc50s) if $pc50s;
+
+ # send a ping out on this channel
+ if ($dxchan->{pingint} && $t >= $dxchan->{pingint} + $dxchan->{lastping}) {
+ if ($dxchan->{nopings} <= 0) {
+ $dxchan->disconnect;
+ } else {
+ addping($main::mycall, $dxchan->call);
+ $dxchan->{nopings} -= 1;
+ $dxchan->{lastping} = $t;
+ }
+ }
+ }
+
+ # every ten seconds
+ if ($t - $last10 >= 10) {
+ # clean out ephemera
+
+ eph_clean();
+
+ $last10 = $t;
+ }
+
+ if ($main::systime - 3600 > $last_hour) {
+ $last_hour = $main::systime;
+ }
+}
+
+#
+# finish up a pc context
+#
+
+#
+# some active measures
+#
+
+
+sub send_dx_spot
+{
+ my $self = shift;
+ my $line = shift;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ # taking into account filtering and so on
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $main::me;
+ next if $dxchan == $self && $self->is_node;
+ $dxchan->dx_spot($line, $self->{isolate}, @_, $self->{call});
+ }
+}
+
+sub dx_spot
+{
+ my $self = shift;
+ my $line = shift;
+ my $isolate = shift;
+ my ($filter, $hops);
+
+ if ($self->{spotsfilter}) {
+ ($filter, $hops) = $self->{spotsfilter}->it(@_);
+ return unless $filter;
+ }
+ send_prot_line($self, $filter, $hops, $isolate, $line);
+}
+
+sub send_prot_line
+{
+ my ($self, $filter, $hops, $isolate, $line) = @_;
+ my $routeit;
+
+ if ($hops) {
+ $routeit = $line;
+ $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
+ } else {
+ $routeit = adjust_hops($self, $line); # adjust its hop count by node name
+ return unless $routeit;
+ }
+ if ($filter) {
+ $self->send($routeit);
+ } else {
+ $self->send($routeit) unless $self->{isolate} || $isolate;
+ }
+}
+
+
+sub send_wwv_spot
+{
+ my $self = shift;
+ my $line = shift;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+ my ($wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
+ my @dxcc = Prefix::extract($_[6]);
+ if (@dxcc > 0) {
+ $wwv_dxcc = $dxcc[1]->dxcc;
+ $wwv_itu = $dxcc[1]->itu;
+ $wwv_cq = $dxcc[1]->cq;
+ }
+ @dxcc = Prefix::extract($_[7]);
+ if (@dxcc > 0) {
+ $org_dxcc = $dxcc[1]->dxcc;
+ $org_itu = $dxcc[1]->itu;
+ $org_cq = $dxcc[1]->cq;
+ }
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ # taking into account filtering and so on
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $main::me;
+ next if $dxchan == $self && $self->is_node;
+ my $routeit;
+ my ($filter, $hops);
+
+ $dxchan->wwv($line, $self->{isolate}, @_, $self->{call}, $wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq);
+ }
+
+}
+
+sub wwv
+{
+ my $self = shift;
+ my $line = shift;
+ my $isolate = shift;
+ my ($filter, $hops);
+
+ if ($self->{wwvfilter}) {
+ ($filter, $hops) = $self->{wwvfilter}->it(@_);
+ return unless $filter;
+ }
+ send_prot_line($self, $filter, $hops, $isolate, $line)
+}
+
+sub send_wcy_spot
+{
+ my $self = shift;
+ my $line = shift;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+ my ($wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
+ my @dxcc = Prefix::extract($_[10]);
+ if (@dxcc > 0) {
+ $wcy_dxcc = $dxcc[1]->dxcc;
+ $wcy_itu = $dxcc[1]->itu;
+ $wcy_cq = $dxcc[1]->cq;
+ }
+ @dxcc = Prefix::extract($_[11]);
+ if (@dxcc > 0) {
+ $org_dxcc = $dxcc[1]->dxcc;
+ $org_itu = $dxcc[1]->itu;
+ $org_cq = $dxcc[1]->cq;
+ }
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ # taking into account filtering and so on
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $main::me;
+ next if $dxchan == $self;
+
+ $dxchan->wcy($line, $self->{isolate}, @_, $self->{call}, $wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq);
+ }
+}
+
+sub wcy
+{
+ my $self = shift;
+ my $line = shift;
+ my $isolate = shift;
+ my ($filter, $hops);
+
+ if ($self->{wcyfilter}) {
+ ($filter, $hops) = $self->{wcyfilter}->it(@_);
+ return unless $filter;
+ }
+ send_prot_line($self, $filter, $hops, $isolate, $line) if $self->is_clx || $self->is_spider || $self->is_dxnet;
+}
+
+# send an announce
+sub send_announce
+{
+ my $self = shift;
+ my $line = shift;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+ my $target;
+ my $to = 'To ';
+ my $text = unpad($_[2]);
+
+ if ($_[3] eq '*') { # sysops
+ $target = "SYSOP";
+ } elsif ($_[3] gt ' ') { # speciality list handling
+ my ($name) = split /\./, $_[3];
+ $target = "$name"; # put the rest in later (if bothered)
+ }
+
+ if ($_[5] eq '1') {
+ $target = "WX";
+ $to = '';
+ }
+ $target = "ALL" if !$target;
+
+
+ # obtain country codes etc
+ my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
+ my ($ann_state, $org_state) = ("", "");
+ my @dxcc = Prefix::extract($_[0]);
+ if (@dxcc > 0) {
+ $ann_dxcc = $dxcc[1]->dxcc;
+ $ann_itu = $dxcc[1]->itu;
+ $ann_cq = $dxcc[1]->cq;
+ $ann_state = $dxcc[1]->state;
+ }
+ @dxcc = Prefix::extract($_[4]);
+ if (@dxcc > 0) {
+ $org_dxcc = $dxcc[1]->dxcc;
+ $org_itu = $dxcc[1]->itu;
+ $org_cq = $dxcc[1]->cq;
+ $org_state = $dxcc[1]->state;
+ }
+
+ if ($self->{inannfilter}) {
+ my ($filter, $hops) =
+ $self->{inannfilter}->it(@_, $self->{call},
+ $ann_dxcc, $ann_itu, $ann_cq,
+ $org_dxcc, $org_itu, $org_cq, $ann_state, $org_state);
+ unless ($filter) {
+ dbg("PCPROT: Rejected by input announce filter") if isdbg('chanerr');
+ return;
+ }
+ }
+
+ if (AnnTalk::dup($_[0], $_[1], $_[2])) {
+ dbg("PCPROT: Duplicate Announce ignored") if isdbg('chanerr');
+ return;
+ }
+
+ Log('ann', $target, $_[0], $text);
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ # taking into account filtering and so on
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $main::me;
+ next if $dxchan == $self && $self->is_node;
+ $dxchan->announce($line, $self->{isolate}, $to, $target, $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq);
+ }
+}
+
+my $msgid = 0;
+
+sub nextchatmsgid
+{
+ $msgid++;
+ $msgid = 1 if $msgid > 999;
+ return $msgid;
+}
+
+# send a chat line
+sub send_chat
+{
+ my $self = shift;
+ my $line = shift;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+ my $target = $_[3];
+ my $text = unpad($_[2]);
+
+ # obtain country codes etc
+ my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
+ my ($ann_state, $org_state) = ("", "");
+ my @dxcc = Prefix::extract($_[0]);
+ if (@dxcc > 0) {
+ $ann_dxcc = $dxcc[1]->dxcc;
+ $ann_itu = $dxcc[1]->itu;
+ $ann_cq = $dxcc[1]->cq;
+ $ann_state = $dxcc[1]->state;
+ }
+ @dxcc = Prefix::extract($_[4]);
+ if (@dxcc > 0) {
+ $org_dxcc = $dxcc[1]->dxcc;
+ $org_itu = $dxcc[1]->itu;
+ $org_cq = $dxcc[1]->cq;
+ $org_state = $dxcc[1]->state;
+ }
+
+ if ($self->{inannfilter}) {
+ my ($filter, $hops) =
+ $self->{inannfilter}->it(@_, $self->{call},
+ $ann_dxcc, $ann_itu, $ann_cq,
+ $org_dxcc, $org_itu, $org_cq, $ann_state, $org_state);
+ unless ($filter) {
+ dbg("PCPROT: Rejected by input announce filter") if isdbg('chanerr');
+ return;
+ }
+ }
+
+ if (AnnTalk::dup($_[0], $_[1], $_[2], $chatdupeage)) {
+ dbg("PCPROT: Duplicate Announce ignored") if isdbg('chanerr');
+ return;
+ }
+
+
+ Log('chat', $target, $_[0], $text);
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ # taking into account filtering and so on
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $main::me;
+ next if $dxchan == $self && $self->is_node;
+ next if $target eq 'LOCAL' && $dxchan->is_node;
+ $dxchan->chat($line, $self->{isolate}, $target, $_[1], $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq);
+ }
+}
+
+sub announce
+{
+ my $self = shift;
+ my $line = shift;
+ my $isolate = shift;
+ my $to = shift;
+ my $target = shift;
+ my $text = shift;
+ my ($filter, $hops);
+
+ if ($self->{annfilter}) {
+ ($filter, $hops) = $self->{annfilter}->it(@_);
+ return unless $filter;
+ }
+ send_prot_line($self, $filter, $hops, $isolate, $line) unless $_[1] eq $main::mycall;
+}
+
+sub chat
+{
+ goto &announce;
+}
+
+
+sub send_local_config
+{
+ my $self = shift;
+ my $node;
+ my @nodes;
+ my @localnodes;
+ my @remotenodes;
+
+ dbg('DXProt::send_local_config') if isdbg('trace');
+
+ # send our nodes
+ if ($self->{isolate}) {
+ @localnodes = ( $main::routeroot );
+ } else {
+ # create a list of all the nodes that are not connected to this connection
+ # and are not themselves isolated, this to make sure that isolated nodes
+ # don't appear outside of this node
+ my @dxchan = grep { $_->call ne $main::mycall && $_ != $self && !$_->{isolate} } DXChannel::get_all_nodes();
+ @localnodes = map { my $r = Route::Node::get($_->{call}); $r ? $r : () } @dxchan if @dxchan;
+ my @intcalls = map { $_->nodes } @localnodes if @localnodes;
+ my $ref = Route::Node::get($self->{call});
+ my @rnodes = $ref->nodes;
+ for my $node (@intcalls) {
+ push @remotenodes, Route::Node::get($node) unless grep $node eq $_, @rnodes;
+ }
+ unshift @localnodes, $main::routeroot;
+ }
+
+
+ $self->send_route(\&pc19, scalar(@localnodes)+scalar(@remotenodes), @localnodes, @remotenodes);
+
+ # get all the users connected on the above nodes and send them out
+ foreach $node (@localnodes, @remotenodes) {
+ if ($node) {
+ my @rout = map {my $r = Route::User::get($_); $r ? ($r) : ()} $node->users;
+ $self->send_route(\&pc16, 1, $node, @rout) if @rout && $self->user->wantsendpc16;
+ } else {
+ dbg("sent a null value") if isdbg('chanerr');
+ }
+ }
+}
+
+#
+# route a message down an appropriate interface for a callsign
+#
+# is called route(to, pcline);
+#
+sub route
+{
+ my ($self, $call, $line) = @_;
+
+ if (ref $self && $call eq $self->{call}) {
+ dbg("PCPROT: Trying to route back to source, dropped") if isdbg('chanerr');
+ return;
+ }
+
+ # always send it down the local interface if available
+ my $dxchan = DXChannel->get($call);
+ unless ($dxchan) {
+ my $cl = Route::get($call);
+ $dxchan = $cl->dxchan if $cl;
+ if (ref $dxchan) {
+ if (ref $self && $dxchan eq $self) {
+ dbg("PCPROT: Trying to route back to source, dropped") if isdbg('chanerr');
+ return;
+ }
+ }
+ }
+ if ($dxchan) {
+ my $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name
+ if ($routeit) {
+ $dxchan->send($routeit) unless $dxchan == $main::me;
+ }
+ } else {
+ dbg("PCPROT: No route available, dropped") if isdbg('chanerr');
+ }
+}
+
+#
+# obtain the hops from the list for this callsign and pc no
+#
+
+sub get_hops
+{
+ my $pcno = shift;
+ my $hops = $DXProt::hopcount{$pcno};
+ $hops = $DXProt::def_hopcount if !$hops;
+ return "H$hops";
+}
+
+#
+# adjust the hop count on a per node basis using the user loadable
+# hop table if available or else decrement an existing one
+#
+
+sub adjust_hops
+{
+ my $self = shift;
+ my $s = shift;
+ my $call = $self->{call};
+ my $hops;
+
+ if (($hops) = $s =~ /\^H(\d+)\^~?$/o) {
+ my ($pcno) = $s =~ /^PC(\d\d)/o;
+ confess "$call called adjust_hops with '$s'" unless $pcno;
+ my $ref = $nodehops{$call} if %nodehops;
+ if ($ref) {
+ my $newhops = $ref->{$pcno};
+ return "" if defined $newhops && $newhops == 0;
+ $newhops = $ref->{default} unless $newhops;
+ return "" if defined $newhops && $newhops == 0;
+ $newhops = $hops if !$newhops;
+ $s =~ s/\^H(\d+)(\^~?)$/\^H$newhops$2/ if $newhops;
+ } else {
+ # simply decrement it
+ $hops--;
+ return "" if !$hops;
+ $s =~ s/\^H(\d+)(\^~?)$/\^H$hops$2/ if $hops;
+ }
+ }
+ return $s;
+}
+
+#
+# load hop tables
+#
+sub load_hops
+{
+ my $self = shift;
+ return $self->msg('lh1') unless -e "$main::data/hop_table.pl";
+ do "$main::data/hop_table.pl";
+ return $@ if $@;
+ return ();
+}
+
+
+# add a ping request to the ping queues
+sub addping
+{
+ my ($from, $to) = @_;
+ my $ref = $pings{$to} || [];
+ my $r = {};
+ $r->{call} = $from;
+ $r->{t} = [ gettimeofday ];
+ route(undef, $to, pc51($to, $main::mycall, 1));
+ push @$ref, $r;
+ $pings{$to} = $ref;
+}
+
+sub process_rcmd
+{
+ my ($self, $tonode, $fromnode, $user, $cmd) = @_;
+ if ($tonode eq $main::mycall) {
+ my $ref = DXUser->get_current($fromnode);
+ my $cref = Route::Node::get($fromnode);
+ Log('rcmd', 'in', $ref->{priv}, $fromnode, $cmd);
+ if ($cmd !~ /^\s*rcmd/i && $cref && $ref && $cref->call eq $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, $cmd));
+ $self->{priv} = $oldpriv;
+ $self->send_rcmd_reply($main::mycall, $fromnode, $user, @in);
+ delete $self->{remotecmd};
+ } else {
+ $self->send_rcmd_reply($main::mycall, $fromnode, $user, "sorry...!");
+ }
+ } else {
+ $self->send_rcmd_reply($main::mycall, $fromnode, $user, "your attempt is logged, Tut tut tut...!");
+ }
+ } else {
+ my $ref = DXUser->get_current($tonode);
+ if ($ref && $ref->is_clx) {
+ $self->route($tonode, pc84($fromnode, $tonode, $user, $cmd));
+ } else {
+ $self->route($tonode, pc34($fromnode, $tonode, $cmd));
+ }
+ }
+}
+
+sub process_rcmd_reply
+{
+ my ($self, $tonode, $fromnode, $user, $line) = @_;
+ if ($tonode eq $main::mycall) {
+ my $s = $rcmds{$fromnode};
+ if ($s) {
+ my $dxchan = DXChannel->get($s->{call});
+ my $ref = $user eq $tonode ? $dxchan : (DXChannel->get($user) || $dxchan);
+ $ref->send($line) if $ref;
+ delete $rcmds{$fromnode} if !$dxchan;
+ } else {
+ # send unsolicited ones to the sysop
+ my $dxchan = DXChannel->get($main::myalias);
+ $dxchan->send($line) if $dxchan;
+ }
+ } else {
+ my $ref = DXUser->get_current($tonode);
+ if ($ref && $ref->is_clx) {
+ $self->route($tonode, pc85($fromnode, $tonode, $user, $line));
+ } else {
+ $self->route($tonode, pc35($fromnode, $tonode, $line));
+ }
+ }
+}
+
+sub send_rcmd_reply
+{
+ my $self = shift;
+ my $tonode = shift;
+ my $fromnode = shift;
+ my $user = shift;
+ while (@_) {
+ my $line = shift;
+ $line =~ s/\s*$//;
+ Log('rcmd', 'out', $fromnode, $line);
+ if ($self->is_clx) {
+ $self->send(pc85($main::mycall, $fromnode, $user, "$main::mycall:$line"));
+ } else {
+ $self->send(pc35($main::mycall, $fromnode, "$main::mycall:$line"));
+ }
+ }
+}
+
+# add a rcmd request to the rcmd queues
+sub addrcmd
+{
+ my ($self, $to, $cmd) = @_;
+
+ my $r = {};
+ $r->{call} = $self->{call};
+ $r->{t} = $main::systime;
+ $r->{cmd} = $cmd;
+ $rcmds{$to} = $r;
+
+ my $ref = Route::Node::get($to);
+ my $dxchan = $ref->dxchan;
+ if ($dxchan && $dxchan->is_clx) {
+ route(undef, $to, pc84($main::mycall, $to, $self->{call}, $cmd));
+ } else {
+ route(undef, $to, pc34($main::mycall, $to, $cmd));
+ }
+}
+
+sub disconnect
+{
+ my $self = shift;
+ my $pc39flag = shift;
+ my $call = $self->call;
+
+ return if $self->{disconnecting}++;
+
+ unless ($pc39flag && $pc39flag == 1) {
+ $self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op")));
+ }
+
+ # get rid of any PC16/17/19
+ eph_del_regex("^PC1[679]*$call");
+
+ # do routing stuff, remove me from routing table
+ my $node = Route::Node::get($call);
+ my @rout;
+ if ($node) {
+ @rout = $node->del($main::routeroot);
+
+ # and all my ephemera as well
+ for (@rout) {
+ my $c = $_->call;
+ eph_del_regex("^PC1[679].*$c");
+ }
+ }
+
+ # remove them from the pc19list as well
+ while (my ($k,$v) = each %pc19list) {
+ my @l = grep {$_->[0] ne $call} @{$pc19list{$k}};
+ if (@l) {
+ $pc19list{$k} = \@l;
+ } else {
+ delete $pc19list{$k};
+ }
+
+ # and the ephemera
+ eph_del_regex("^PC1[679].*$k");
+ }
+
+ # unbusy and stop and outgoing mail
+ my $mref = DXMsg::get_busy($call);
+ $mref->stop_msg($call) if $mref;
+
+ # broadcast to all other nodes that all the nodes connected to via me are gone
+ unless ($pc39flag && $pc39flag == 2) {
+ $self->route_pc21(@rout) if @rout;
+ }
+
+ # remove outstanding pings
+ delete $pings{$call};
+
+ # I was the last node visited
+ $self->user->node($main::mycall);
+
+ # send info to all logged in thingies
+ $self->tell_login('logoutn');
+
+ Log('DXProt', $call . " Disconnected");
+
+ $self->SUPER::disconnect;
+}
+
+
+#
+# send a talk message to this thingy
+#
+sub talk
+{
+ my ($self, $from, $to, $via, $line, $origin) = @_;
+
+ $line =~ s/\^/\\5E/g; # remove any ^ characters
+ $self->send(DXProt::pc10($from, $to, $via, $line, $origin));
+ Log('talk', $to, $from, $via?$via:$self->call, $line) unless $origin && $origin ne $main::mycall;
+}
+
+# send it if it isn't the except list and isn't isolated and still has a hop count
+# taking into account filtering and so on
+sub send_route
+{
+ my $self = shift;
+ my $generate = shift;
+ my $no = shift; # the no of things to filter on
+ my $routeit;
+ my ($filter, $hops);
+ my @rin;
+
+ for (; @_ && $no; $no--) {
+ my $r = shift;
+
+ if (!$self->{isolate} && $self->{routefilter}) {
+ $filter = undef;
+ if ($r) {
+ ($filter, $hops) = $self->{routefilter}->it($self->{call}, $self->{dxcc}, $self->{itu}, $self->{cq}, $r->call, $r->dxcc, $r->itu, $r->cq, $self->{state}, $r->{state});
+ if ($filter) {
+ push @rin, $r;
+ } else {
+ dbg("DXPROT: $self->{call}/" . $r->call . " rejected by output filter") if isdbg('chanerr');
+ }
+ } else {
+ dbg("was sent a null value") if isdbg('chanerr');
+ }
+ } else {
+ push @rin, $r unless $self->{isolate} && $r->call ne $main::mycall;
+ }
+ }
+ if (@rin) {
+ foreach my $line (&$generate(@rin, @_)) {
+ if ($hops) {
+ $routeit = $line;
+ $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
+ } else {
+ $routeit = adjust_hops($self, $line); # adjust its hop count by node name
+ next unless $routeit;
+ }
+ $self->send($routeit);
+ }
+ }
+}
+
+sub broadcast_route
+{
+ my $self = shift;
+ my $generate = shift;
+ my @dxchan = DXChannel::get_all_nodes();
+ my $dxchan;
+ my $line;
+
+ unless ($self->{isolate}) {
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $self;
+ next if $dxchan == $main::me;
+ next unless $dxchan->isa('DXProt');
+ next if ($generate == \&pc16 || $generate==\&pc17) && !$dxchan->user->wantsendpc16;
+
+ $dxchan->send_route($generate, @_);
+ }
+ }
+}
+
+sub route_pc16
+{
+ my $self = shift;
+ return unless $self->user->wantpc16;
+ broadcast_route($self, \&pc16, 1, @_);
+}
+
+sub route_pc17
+{
+ my $self = shift;
+ return unless $self->user->wantpc16;
+ broadcast_route($self, \&pc17, 1, @_);
+}
+
+sub route_pc19
+{
+ my $self = shift;
+ broadcast_route($self, \&pc19, scalar @_, @_);
+}
+
+sub route_pc21
+{
+ my $self = shift;
+ broadcast_route($self, \&pc21, scalar @_, @_);
+}
+
+sub route_pc24
+{
+ my $self = shift;
+ broadcast_route($self, \&pc24, 1, @_);
+}
+
+sub route_pc41
+{
+ my $self = shift;
+ broadcast_route($self, \&pc41, 1, @_);
+}
+
+sub route_pc50
+{
+ my $self = shift;
+ broadcast_route($self, \&pc50, 1, @_);
+}
+
+sub route_pc90
+{
+ my $self = shift;
+ broadcast_route($self, \&pc90, 1, @_);
+}
+
+sub in_filter_route
+{
+ my $self = shift;
+ my $r = shift;
+ my ($filter, $hops) = (1, 1);
+
+ if ($self->{inroutefilter}) {
+ ($filter, $hops) = $self->{inroutefilter}->it($self->{call}, $self->{dxcc}, $self->{itu}, $self->{cq}, $r->call, $r->dxcc, $r->itu, $r->cq, $self->state, $r->state);
+ dbg("PCPROT: $self->{call}/" . $r->call . ' rejected by in_filter_route') if !$filter && isdbg('chanerr');
+ }
+ return $filter;
+}
+
+sub eph_dup
+{
+ my $s = shift;
+ my $t = shift || $eph_restime;
+ my $r;
+
+ # chop the end off
+ $s =~ s/\^H\d\d?\^?\~?$//;
+ $r = 1 if exists $eph{$s}; # pump up the dup if it keeps circulating
+ $eph{$s} = $main::systime + $t;
+ return $r;
+}
+
+sub eph_del_regex
+{
+ my $regex = shift;
+ my ($key, $val);
+ while (($key, $val) = each %eph) {
+ if ($key =~ m{$regex}) {
+ delete $eph{$key};
+ }
+ }
+}
+
+sub eph_clean
+{
+ my ($key, $val);
+
+ while (($key, $val) = each %eph) {
+ if ($main::systime >= $val) {
+ delete $eph{$key};
+ }
+ }
+}
+
+sub eph_list
+{
+ my ($key, $val);
+ my @out;
+
+ while (($key, $val) = each %eph) {
+ push @out, $key, $val;
+ }
+ return @out;