X-Git-Url: http://gb7djk.dxcluster.net/gitweb/gitweb.cgi?a=blobdiff_plain;f=perl%2FDXProt.pm;h=8687a4943d446c920152a73b9657ae7a0ce5c1fc;hb=9128a77e1390d3ad788470aaf73a7ce6f9c64c78;hp=710a3dbf84129a4b0405fc92597c8ff2cd7e2161;hpb=638d9efe6fe3d3c4eec08d5e985fce4dd760423b;p=spider.git diff --git a/perl/DXProt.pm b/perl/DXProt.pm index 710a3dbf..8687a494 100644 --- a/perl/DXProt.pm +++ b/perl/DXProt.pm @@ -25,35 +25,149 @@ use DXDebug; use Filter; use Local; use DXDb; +use AnnTalk; +use Geomag; +use WCY; use Time::HiRes qw(gettimeofday tv_interval); - -use Carp; +use BadWords; +use DXHash; use strict; -use vars qw($me $pc11_max_age $pc23_max_age $pc11_dup_age $pc23_dup_age - %spotdup %wwvdup $last_hour %pings %rcmds - %nodehops @baddx $baddxfn $pc12_dup_age - %anndup $allowzero $pc12_dup_lth $decode_dk0wcy); +use vars qw($me $pc11_max_age $pc23_max_age + $last_hour %pings %rcmds + %nodehops $baddx $badspotter $badnode $censorpc + $allowzero $decode_dk0wcy $send_opernam @checklist); $me = undef; # the channel id for this cluster -$decode_dk0wcy = undef; # if set use this callsign to decode announces from the EU WWV data beacon $pc11_max_age = 1*3600; # the maximum age for an incoming 'real-time' pc11 $pc23_max_age = 1*3600; # the maximum age for an incoming 'real-time' pc23 -$pc11_dup_age = 3*3600; # the maximum time to keep the spot dup list for -$pc23_dup_age = 3*3600; # the maximum time to keep the wwv dup list for -$pc12_dup_age = 24*3600; # the maximum time to keep the ann dup list for -$pc12_dup_lth = 60; # the length of ANN text to save for deduping -%spotdup = (); # the pc11 and 26 dup hash -%wwvdup = (); # the pc23 and 27 dup hash -%anndup = (); # the PC12 dup hash + $last_hour = time; # last time I did an hourly periodic update %pings = (); # outstanding ping requests outbound %rcmds = (); # outstanding rcmd requests outbound %nodehops = (); # node specific hop control -@baddx = (); # list of illegal spotted callsigns - - -$baddxfn = "$main::data/baddx.pl"; +$censorpc = 1; # Do a BadWords::check on text fields and reject things + # loads of 'bad things' +$baddx = new DXHash "baddx"; +$badspotter = new DXHash "badspotter"; +$badnode = new DXHash "badnode"; + +@checklist = +( + [ qw(c c m bp bc c) ], # pc10 + [ qw(f m d t m c c h) ], # pc11 + [ qw(c bc m bp bm p h) ], # pc12 + [ qw(c h) ], # + [ qw(c h) ], # + [ qw(c m h) ], # + undef , # pc16 has to be validated manually + [ qw(c c h) ], # pc17 + [ qw(m n) ], # pc18 + undef , # pc19 has to be validated manually + undef , # pc20 no validation + [ qw(c m h) ], # pc21 + undef , # pc22 no validation + [ qw(d n n n n m c c h) ], # pc23 + [ qw(c p h) ], # pc24 + [ qw(c c n n) ], # pc25 + [ qw(f m d t m c c bc) ], # pc26 + [ qw(d n n n n m c c bc) ], # pc27 + [ qw(c c m c d t p m bp n p bp bc) ], # pc28 + [ qw(c c n m) ], # pc29 + [ qw(c c n) ], # pc30 + [ qw(c c n) ], # pc31 + [ qw(c c n) ], # pc32 + [ qw(c c n) ], # pc33 + [ qw(c c m) ], # pc34 + [ qw(c c m) ], # pc35 + [ qw(c c m) ], # pc36 + [ qw(c c n m) ], # pc37 + undef, # pc38 not interested + [ qw(c m) ], # pc39 + [ qw(c c m p n) ], # pc40 + [ qw(c n m h) ], # pc41 + [ qw(c c n) ], # pc42 + undef, # pc43 don't handle it + [ qw(c c n m m c) ], # pc44 + [ qw(c c n m) ], # pc45 + [ qw(c c n) ], # pc46 + undef, # pc47 + undef, # pc48 + [ qw(c m h) ], # pc49 + [ qw(c n h) ], # pc50 + [ qw(c c n) ], # pc51 + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, # pc60 + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, # pc70 + undef, + undef, + [ qw(d n n n n n n m m m c c h) ], # pc73 + undef, + undef, + undef, + undef, + undef, + undef, + undef, # pc80 + undef, + undef, + undef, + [ qw(c c c m) ], # pc84 + [ qw(c c c m) ], # pc85 +); + +# use the entry in the check list to check the field list presented +# return OK if line NOT in check list (for now) +sub check +{ + my $n = shift; + $n -= 10; + return 0 if $n < 0 || $n > @checklist; + my $ref = $checklist[$n]; + return 0 unless ref $ref; + + my $i; + shift; # not interested in the first field + for ($i = 0; $i < @$ref; $i++) { + my ($blank, $act) = $$ref[$i] =~ /^(b?)(\w)$/; + return 0 unless $act; + next if $blank && $_[$i] =~ /^[ \*]$/; + if ($act eq 'c') { + return $i+1 unless is_callsign($_[$i]); + } elsif ($act eq 'm') { + return $i+1 unless is_pctext($_[$i]); + } elsif ($act eq 'p') { + return $i+1 unless is_pcflag($_[$i]); + } elsif ($act eq 'f') { + return $i+1 unless is_freq($_[$i]); + } elsif ($act eq 'n') { + return $i+1 unless $_[$i] =~ /^[\d ]+$/; + } elsif ($act eq 'h') { + return $i+1 unless $_[$i] =~ /^H\d\d?$/; + } elsif ($act eq 'd') { + return $i+1 unless $_[$i] =~ /^\s*\d+-\w\w\w-[12][90]\d\d$/; + } elsif ($act eq 't') { + return $i+1 unless $_[$i] =~ /^[012]\d[012345]\dZ$/; + } + } + return 0; +} sub init { @@ -64,28 +178,7 @@ sub init $me->{state} = "indifferent"; do "$main::data/hop_table.pl" if -e "$main::data/hop_table.pl"; confess $@ if $@; - # $me->{sort} = 'M'; # M for me - - # now prime the spot duplicates file with today's and yesterday's data - my @today = Julian::unixtoj(time); - my @spots = Spot::readfile(@today); - @today = Julian::sub(@today, 1); - push @spots, Spot::readfile(@today); - for (@spots) { - my $dupkey = "$_->[0]$_->[1]$_->[2]$_->[3]$_->[4]"; - $spotdup{$dupkey} = $_->[2]; - } - - # now prime the wwv duplicates file with just this month's data - my @wwv = Geomag::readfile(time); - for (@wwv) { - my $dupkey = "$_->[1].$_->[2]$_->[3]$_->[4]"; - $wwvdup{$dupkey} = $_->[1]; - } - - # load the baddx file - do "$baddxfn" if -e "$baddxfn"; - print "$@\n" if $@; + $me->{sort} = 'S'; # S for spider } # @@ -95,7 +188,6 @@ sub init sub new { my $self = DXChannel::alloc(@_); - $self->{'sort'} = 'A'; # in absence of how to find out what sort of an object I am return $self; } @@ -111,16 +203,24 @@ sub start # remember type of connection $self->{consort} = $line; $self->{outbound} = $sort eq 'O'; - $self->{priv} = $user->priv; - $self->{lang} = $user->lang; + $self->{priv} = $user->priv || 1; # other clusters can always be 'normal' users + $self->{lang} = $user->lang || 'en'; $self->{isolate} = $user->{isolate}; $self->{consort} = $line; # save the connection type $self->{here} = 1; + # get the output filters + $self->{spotsfilter} = Filter::read_in('spots', $call, 0) || Filter::read_in('spots', 'node_default', 0); + $self->{wwvfilter} = Filter::read_in('wwv', $call, 0) || Filter::read_in('wwv', 'node_default', 0); + $self->{wcyfilter} = Filter::read_in('wcy', $call, 0) || Filter::read_in('wcy', 'node_default', 0); + $self->{annfilter} = Filter::read_in('ann', $call, 0) || Filter::read_in('ann', 'node_default', 0) ; + + # get the INPUT filters (these only pertain to Clusters) - $self->{inspotfilter} = Filter::read_in('spots', $call, 1); - $self->{inwwvfilter} = Filter::read_in('wwv', $call, 1); - $self->{inannfilter} = Filter::read_in('ann', $call, 1); + $self->{inspotsfilter} = Filter::read_in('spots', $call, 1) || Filter::read_in('spots', 'node_default', 1); + $self->{inwwvfilter} = Filter::read_in('wwv', $call, 1) || Filter::read_in('wwv', 'node_default', 1); + $self->{inwcyfilter} = Filter::read_in('wcy', $call, 1) || Filter::read_in('wcy', 'node_default', 1); + $self->{inannfilter} = Filter::read_in('ann', $call, 1) || Filter::read_in('ann', 'node_default', 1); # set unbuffered and no echo $self->send_now('B',"0"); @@ -136,13 +236,10 @@ sub start # send initialisation string unless ($self->{outbound}) { - $self->send(pc38()) if DXNode->get_all(); $self->send(pc18()); $self->{lastping} = $main::systime; } else { - # remove from outstanding connects queue - @main::outstanding_connects = grep {$_->{call} ne $call} @main::outstanding_connects; - $self->{lastping} = $main::systime + $self->pingint / 2; + $self->{lastping} = $main::systime + ($self->pingint / 2); } $self->state('init'); $self->pc50_t(time); @@ -160,6 +257,8 @@ sub normal { my ($self, $line) = @_; my @field = split /\^/, $line; + return unless @field; + pop @field if $field[-1] eq '~'; # print join(',', @field), "\n"; @@ -170,11 +269,12 @@ sub normal # process PC frames my ($pcno) = $field[0] =~ /^PC(\d\d)/; # just get the number return unless $pcno; - return if $pcno < 10 || $pcno > 51; + return if $pcno < 10 || $pcno > 99; - # dump bad protocol messages unless it is a PC29 - if ($line =~ /\%[0-9A-F][0-9A-F]/o && $pcno != 29) { - dbg('chan', "CORRUPT protocol message - dumped"); + # 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; } @@ -188,17 +288,28 @@ sub normal 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 $call = ($field[5] gt ' ') ? $field[5] : $field[2]; - if ($call eq $main::mycall || grep $_ eq $call, DXChannel::get_all_user_calls()) { - - # yes, it is - my $text = unpad($field[3]); - Log('talk', $call, $field[1], $field[6], $text); - $call = $main::myalias if $call eq $main::mycall; - my $ref = DXChannel->get($call); - $ref->send("$call de $field[1]: $text") if $ref && $ref->{talk}; + 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 } @@ -216,8 +327,14 @@ sub normal } # if this is a 'nodx' node then ignore it - if (grep $field[7] =~ /^$_/, @DXProt::nodx_node) { - dbg('chan', "Bad DXNode, dropped"); + if ($badnode->in($field[7])) { + dbg('chan', "PCPROT: Bad Node, dropped"); + return; + } + + # if this is a 'bad spotter' user then ignore it + if ($badspotter->in($field[6])) { + dbg('chan', "PCPROT: Bad Spotter, dropped"); return; } @@ -225,40 +342,36 @@ sub normal 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', "Spot ignored, invalid date or out of range ($field[3] $field[4])\n"); + dbg('chan', "PCPROT: Spot ignored, invalid date or out of range ($field[3] $field[4])\n"); return; } - # strip off the leading & trailing spaces from the comment - my $text = unpad($field[5]); - - # store it away - my $spotter = $field[6]; - $spotter =~ s/-[\@\d]+$//o; # strip off the ssid from the spotter - - # do some de-duping - my $freq = $field[1] - 0; - my $dupkey = "$freq$field[2]$d$text$spotter"; - if ($spotdup{$dupkey}) { - dbg('chan', "Duplicate Spot ignored\n"); + # is it 'baddx' + if ($baddx->in($field[2])) { + dbg('chan', "PCPROT: Bad DX spot, ignored"); return; } - $spotdup{$dupkey} = $d; - - # is it 'baddx' - if (grep $field[2] eq $_, @baddx) { - dbg('chan', "Bad DX spot, ignored"); + # 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; } - - # are any of the crucial fields invalid? - if ($field[2] =~ /[a-z]/ || $field[6] =~ /[a-z]/ || $field[7] =~ /[a-z]/) { - dbg('chan', "Spot contains lower case callsigns, rejected"); + 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($freq, $field[2], $d, $text, $spotter, $field[7]); + my @spot = Spot::add($field[1], $field[2], $d, $field[5], $field[6], $field[7]); # # @spot at this point contains:- @@ -267,6 +380,49 @@ sub normal # 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 { @@ -285,34 +441,47 @@ sub normal if ($pcno == 12) { # announces # announce duplicate checking - my $text = substr(uc unpad($field[3]), 0, $pc12_dup_lth); - my $dupkey = $field[1].$field[2].$text; - if ($anndup{$dupkey}) { - dbg('chan', "Duplicate Announce ignored\n"); + $field[3] =~ s/^\s+//; # remove leading blanks + if (AnnTalk::dup($field[1], $field[2], $field[3])) { + dbg('chan', "PCPROT: Duplicate Announce ignored"); return; } - $anndup{$dupkey} = $main::systime; + + 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 ($filter, $hops) = Filter::it($self->{inannfilter}, @field[1..6], $self->{call} ); + 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', "Rejected by filter"); + dbg('chan', "PCPROT: Rejected by filter"); return; } } # send it $self->send_announce($line, @field[1..6]); - - if ($decode_dk0wcy && $field[1] eq $decode_dk0wcy) { - my ($hour, $k, $next, $a, $r, $sfi, $alarm) = $field[3] =~ /^Aurora Beacon\s+(\d+)UTC,\s+Kiel\s+K=(\d+),.*ed\s+K=(\d+),\s+A=(\d+),\s+R=(\d+),\s+SFI=(\d+),.*larm:\s+(\w+)/; - $alarm = ($alarm =~ /^Y/i) ? ', Aurora in DE' : ''; - my $wwv = Geomag::update($main::systime, $hour, $sfi, $a, $k, "R=$r, Next K=$next$alarm", $decode_dk0wcy, $field[5], $r) if $sfi && $r; - } - } else { $self->route($field[2], $line); } @@ -337,27 +506,49 @@ sub normal # 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); - broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate}; + dbg('chan', "PCPROT: $field[1] no PC19 yet, autovivified as node"); +# broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate}; } - return unless $node; # ignore if havn't seen a PC19 for this one yet - return unless $node->isa('DXNode'); + 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', "LOOP: $field[1] came in on wrong channel"); + dbg('chan', "PCPROT: $field[1] came in on wrong channel"); return; } if (($dxchan = DXChannel->get($field[1])) && $dxchan != $self) { - dbg('chan', "LOOP: $field[1] connected locally"); + 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 if !$call || length $call < 3 || length $call > 8; - next if !$confmode; - $call = uc $call; - next if DXCluster->get_exact($call); # we already have this (loop?) + 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); @@ -374,6 +565,8 @@ sub normal # queue up any messages (look for privates only) DXMsg::queue_msg(1) if $self->state eq 'normal'; +# broadcast_route($line, $self, $field[1]); +# return; last SWITCH; } @@ -384,21 +577,46 @@ sub normal # 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); - broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate}; + 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; } - return unless $node; - return unless $node->isa('DXNode'); if ($node->dxchan != $self) { - dbg('chan', "LOOP: $field[2] came in on wrong channel"); + dbg('chan', "PCPROT: $field[2] came in on wrong channel"); return; } - if (($dxchan = DXChannel->get($field[2])) && $dxchan != $self) { - dbg('chan', "LOOP: $field[2] connected locally"); + if ($dxchan = DXChannel->get($field[1])) { + dbg('chan', "PCPROT: $field[1] connected locally"); return; } my $ref = DXCluster->get_exact($field[1]); - $ref->del() if $ref; + 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; + } +# broadcast_route($line, $self, $field[2]); +# return; last SWITCH; } @@ -425,6 +643,7 @@ sub normal 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; @@ -433,13 +652,14 @@ sub normal if ($node) { my $dxchan; if (($dxchan = DXChannel->get($call)) && $dxchan != $self) { - dbg('chan', "LOOP: $call connected locally"); + dbg('chan', "PCPROT: $call connected locally"); } if ($node->dxchan != $self) { - dbg('chan', "LOOP: $call come in on wrong channel"); + dbg('chan', "PCPROT: $call come in on wrong channel"); next; } - dbg('chan', "already have $call"); + my $rcall = $node->mynode->call; + dbg('chan', "PCPROT: already have $call on $rcall"); next; } @@ -489,21 +709,34 @@ sub normal 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', "LOOP: $call come in on wrong channel"); + dbg('chan', "PCPROT: $call come in on wrong channel"); return; } my $dxchan; - if (($dxchan = DXChannel->get($call)) && $dxchan != $self) { - dbg('chan', "LOOP: $call connected locally"); + if ($dxchan = DXChannel->get($call)) { + dbg('chan', "PCPROT: $call connected locally"); return; } $node->del(); } else { - dbg('chan', "$call not in table, dropped"); + dbg('chan', "PCPROT: $call not in table, dropped"); return; } + } else { + dbg('chan', "PCPROT: I WILL _NOT_ be disconnected!"); + return; } +# broadcast_route($line, $self, $call); +# return; last SWITCH; } @@ -529,17 +762,15 @@ sub normal my $i = unpad($field[5]); my ($r) = $field[6] =~ /R=(\d+)/; $r = 0 unless $r; - my $dupkey = "$d.$sfi$k$i"; - if ($wwvdup{$dupkey}) { - dbg('chan', "Dup WWV Spot ignored\n"); + 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 (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $field[2] < 0 || $field[2] > 23) { - dbg('chan', "WWV Date ($field[1] $field[2]) out of range"); + if (Geomag::dup($d,$sfi,$k,$i,$field[6])) { + dbg('chan', "PCPROT: Dup WWV Spot ignored\n"); return; } - $wwvdup{$dupkey} = $d; - $field[6] =~ s/-\d+$//o; # remove spotter's ssid + $field[7] =~ s/-\d+$//o; # remove spotter's ssid my $wwv = Geomag::update($d, $field[2], $sfi, $k, $i, @field[6..8], $r); @@ -571,7 +802,7 @@ sub normal return; } if ($field[2] eq $main::mycall) { - dbg('chan', "Trying to merge to myself, ignored"); + dbg('chan', "PCPROT: Trying to merge to myself, ignored"); return; } @@ -601,7 +832,7 @@ sub normal if ($pcno == 49 || $field[1] eq $main::mycall) { DXMsg::process($self, $line); } else { - $self->route($field[1], $line); + $self->route($field[1], $line) unless $self->is_clx; } return; } @@ -611,7 +842,7 @@ sub normal my $ref = DXUser->get_current($field[2]); my $cref = DXCluster->get($field[2]); Log('rcmd', 'in', $ref->{priv}, $field[2], $field[3]); - unless ($field[3] =~ /rcmd/i || !$cref || !$ref || $cref->mynode->call ne $ref->homenode) { # not allowed to relay RCMDS! + 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}; @@ -631,7 +862,12 @@ sub normal $self->send(pc35($main::mycall, $field[2], "$main::mycall:your attempt is logged, Tut tut tut...!")); } } else { - $self->route($field[1], $line); + 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; } @@ -643,9 +879,18 @@ sub normal 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 { - $self->route($field[1], $line); + 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; } @@ -657,18 +902,17 @@ sub normal } if ($pcno == 39) { # incoming disconnect - $self->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]); - if (!$user) { - # then try without an SSID - $field[1] =~ s/-\d+$//o; - $user = DXUser->get_current($field[1]); - } $user = DXUser->new($field[1]) if !$user; if ($field[2] == 1) { @@ -679,16 +923,18 @@ sub normal 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) { + if ($pcno == 37 || $pcno == 44 || $pcno == 45 || $pcno == 46 || $pcno == 47 || $pcno == 48) { DXDb::process($self, $line); return; } @@ -724,9 +970,9 @@ sub normal 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_ak1a) { + } elsif ($dxchan->is_node) { if ($tochan) { - $tochan->{nopings} = 2; # pump up the timer + $tochan->{nopings} = $tochan->user->nopings || 2; # pump up the timer push @{$tochan->{pingtime}}, $t; shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6; my $st; @@ -745,15 +991,113 @@ sub normal } 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!!!! - # + # 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 @@ -771,12 +1115,12 @@ sub process my $dxchan; foreach $dxchan (@dxchan) { - next unless $dxchan->is_ak1a(); + next unless $dxchan->is_node(); next if $dxchan == $me; # send a pc50 out on this channel if ($t >= $dxchan->pc50_t + $DXProt::pc50_interval) { - $dxchan->send(pc50()); + $dxchan->send(pc50(scalar DXChannel::get_all_users)); $dxchan->pc50_t($t); } @@ -796,18 +1140,9 @@ sub process my $val; my $cutoff; if ($main::systime - 3600 > $last_hour) { - $cutoff = $main::systime - $pc11_dup_age; - while (($key, $val) = each %spotdup) { - delete $spotdup{$key} if $val < $cutoff; - } - $cutoff = $main::systime - $pc23_dup_age; - while (($key, $val) = each %wwvdup) { - delete $wwvdup{$key} if $val < $cutoff; - } - $cutoff = $main::systime - $pc12_dup_age; - while (($key, $val) = each %anndup) { - delete $anndup{$key} if $val < $cutoff; - } +# Spot::process; +# Geomag::process; +# AnnTalk::process; $last_hour = $main::systime; } } @@ -815,45 +1150,43 @@ sub process # # finish up a pc context # -sub finish + +# +# some active measures +# +sub send_route { my $self = shift; - my $call = $self->call; - my $nopc39 = shift; - my $ref = DXCluster->get_exact($call); - - $self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op"))) unless $nopc39; - - # unbusy and stop and outgoing mail - my $mref = DXMsg::get_busy($call); - $mref->stop_msg($call) if $mref; - - # broadcast to all other nodes that all the nodes connected to via me are gone - my @gonenodes = map { $_->dxchan == $self ? $_ : () } DXNode::get_all(); - my $node; - - foreach $node (@gonenodes) { - next if $node->call eq $call; - broadcast_ak1a(pc21($node->call, 'Gone') , $self) unless $self->{isolate}; - $node->del(); - } - - # remove outstanding pings - delete $pings{$call}; + my $line = shift; + my @dxchan = DXChannel::get_all_nodes(); + my $dxchan; - # now broadcast to all other ak1a nodes that I have gone - broadcast_ak1a(pc21($call, 'Gone.'), $self) unless $self->{isolate}; - - # send info to all logged in thingies - $self->tell_login('logoutn'); + # 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) { + my $routeit; + my ($filter, $hops); - Log('DXProt', $call . " Disconnected"); - $ref->del() if $ref; + if ($dxchan->{routefilter}) { + ($filter, $hops) = $dxchan->{routefilter}->it($self->{call}, @_); + next unless $filter; + } + next if $dxchan == $self; + if ($hops) { + $routeit = $line; + $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/; + } else { + $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name + next unless $routeit; + } + if ($filter) { + $dxchan->send($routeit) if $routeit; + } else { + $dxchan->send($routeit) unless $dxchan->{isolate} || $self->{isolate}; + } + } } -# -# some active measures -# sub send_dx_spot { my $self = shift; @@ -867,12 +1200,12 @@ sub send_dx_spot my $routeit; my ($filter, $hops); - if ($dxchan->{spotfilter}) { - ($filter, $hops) = Filter::it($dxchan->{spotfilter}, @_, $self->{call} ); + if ($dxchan->{spotsfilter}) { + ($filter, $hops) = $dxchan->{spotsfilter}->it(@_, $self->{call} ); next unless $filter; } - if ($dxchan->is_ak1a) { + if ($dxchan->is_node) { next if $dxchan == $self; if ($hops) { $routeit = $line; @@ -887,9 +1220,10 @@ sub send_dx_spot $dxchan->send($routeit) unless $dxchan->{isolate} || $self->{isolate}; } } elsif ($dxchan->is_user && $dxchan->{dx}) { - my $buf = Spot::formatb($_[0], $_[1], $_[2], $_[3], $_[4]); + my $buf = Spot::formatb($dxchan->{user}->wantgrid, $_[0], $_[1], $_[2], $_[3], $_[4]); $buf .= "\a\a" if $dxchan->{beep}; - if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') { + $buf =~ s/\%5E/^/g; + if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'talk') { $dxchan->send($buf); } else { $dxchan->delay($buf); @@ -904,6 +1238,19 @@ sub send_wwv_spot 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($_[7]); + if (@dxcc > 0) { + $wwv_dxcc = $dxcc[1]->dxcc; + $wwv_itu = $dxcc[1]->itu; + $wwv_cq = $dxcc[1]->cq; + } + @dxcc = Prefix::extract($_[8]); + 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 @@ -911,11 +1258,11 @@ sub send_wwv_spot my $routeit; my ($filter, $hops); - if ($dxchan->{spotfilter}) { - ($filter, $hops) = Filter::it($dxchan->{wwvfilter}, @_, $self->{call} ); + if ($dxchan->{wwvfilter}) { + ($filter, $hops) = $dxchan->{wwvfilter}->it(@_, $self->{call}, $wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq); next unless $filter; } - if ($dxchan->is_ak1a) { + if ($dxchan->is_node) { next if $dxchan == $self; if ($hops) { $routeit = $line; @@ -933,7 +1280,63 @@ sub send_wwv_spot } elsif ($dxchan->is_user && $dxchan->{wwv}) { my $buf = "WWV de $_[6] <$_[1]>: SFI=$_[2], A=$_[3], K=$_[4], $_[5]"; $buf .= "\a\a" if $dxchan->{beep}; - if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') { + if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'talk') { + $dxchan->send($buf); + } else { + $dxchan->delay($buf); + } + } + } +} + +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($_[11]); + if (@dxcc > 0) { + $wcy_dxcc = $dxcc[1]->dxcc; + $wcy_itu = $dxcc[1]->itu; + $wcy_cq = $dxcc[1]->cq; + } + @dxcc = Prefix::extract($_[12]); + 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) { + my $routeit; + my ($filter, $hops); + + if ($dxchan->{wcyfilter}) { + ($filter, $hops) = $dxchan->{wcyfilter}->it(@_, $self->{call}, $wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq); + next unless $filter; + } + if ($dxchan->is_clx || $dxchan->is_spider || $dxchan->is_dxnet) { + next if $dxchan == $self; + if ($hops) { + $routeit = $line; + $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/; + } else { + $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name + next unless $routeit; + } + if ($filter) { + $dxchan->send($routeit) if $routeit; + } else { + $dxchan->send($routeit) unless $dxchan->{isolate} || $self->{isolate}; + } + } elsif ($dxchan->is_user && $dxchan->{wcy}) { + my $buf = "WCY de $_[10] <$_[1]> : K=$_[4] expK=$_[5] A=$_[3] R=$_[6] SFI=$_[2] SA=$_[7] GMF=$_[8] Au=$_[9]"; + $buf .= "\a\a" if $dxchan->{beep}; + if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'talk') { $dxchan->send($buf); } else { $dxchan->delay($buf); @@ -964,10 +1367,25 @@ sub send_announce $target = "WX"; $to = ''; } - $target = "All" if !$target; + $target = "ALL" if !$target; Log('ann', $target, $_[0], $text); + # obtain country codes etc + my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0); + my @dxcc = Prefix::extract($_[0]); + if (@dxcc > 0) { + $ann_dxcc = $dxcc[1]->dxcc; + $ann_itu = $dxcc[1]->itu; + $ann_cq = $dxcc[1]->cq; + } + @dxcc = Prefix::extract($_[4]); + 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) { @@ -975,10 +1393,10 @@ sub send_announce my ($filter, $hops); if ($dxchan->{annfilter}) { - ($filter, $hops) = Filter::it($dxchan->{annfilter}, @_, $self->{call} ); + ($filter, $hops) = $dxchan->{annfilter}->it(@_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq); next unless $filter; } - if ($dxchan->is_ak1a && $_[1] ne $main::mycall) { # i.e not specifically routed to me + if ($dxchan->is_node && $_[1] ne $main::mycall) { # i.e not specifically routed to me next if $dxchan == $self; if ($hops) { $routeit = $line; @@ -993,11 +1411,15 @@ sub send_announce $dxchan->send($routeit) unless $dxchan->{isolate} || $self->{isolate}; } - } elsif ($dxchan->is_user && $dxchan->{ann}) { + } elsif ($dxchan->is_user) { + unless ($dxchan->{ann}) { + next if $_[0] ne $main::myalias && $_[0] ne $main::mycall; + } next if $target eq 'SYSOP' && $dxchan->{priv} < 5; my $buf = "$to$target de $_[0]: $text"; + $buf =~ s/\%5E/^/g; $buf .= "\a\a" if $dxchan->{beep}; - if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') { + if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'talk') { $dxchan->send($buf); } else { $dxchan->delay($buf); @@ -1055,21 +1477,32 @@ sub send_local_config sub route { my ($self, $call, $line) = @_; - my $cl = DXCluster->get_exact($call); - if ($cl) { # don't route it back down itself - if (ref $self && $call eq $self->{call}) { - dbg('chan', "Trying to route back to source, dropped"); - return; - } - my $hops; - my $dxchan = $cl->{dxchan}; - if ($dxchan) { - my $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name - if ($routeit) { - $dxchan->send($routeit) if $dxchan; + + if (ref $self && $call eq $self->{call}) { + dbg('chan', "PCPROT: Trying to route back to source, dropped"); + return; + } + + # always send it down the local interface if available + my $dxchan = DXChannel->get($call); + unless ($dxchan) { + my $cl = DXCluster->get_exact($call); + $dxchan = $cl->dxchan if $cl; + if (ref $dxchan) { + if (ref $self && $dxchan eq $self) { + dbg('chan', "PCPROT: Trying to route back to source, dropped"); + return; } } } + if ($dxchan) { + my $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name + if ($routeit) { + $dxchan->send($routeit); + } + } else { + dbg('chan', "PCPROT: No route available, dropped"); + } } # broadcast a message to all clusters taking into account isolation @@ -1078,12 +1511,14 @@ sub broadcast_ak1a { my $s = shift; # the line to be rebroadcast my @except = @_; # to all channels EXCEPT these (dxchannel refs) - my @dxchan = DXChannel::get_all_ak1a(); + my @dxchan = DXChannel::get_all_nodes(); my $dxchan; # send it if it isn't the except list and isn't isolated and still has a hop count foreach $dxchan (@dxchan) { next if grep $dxchan == $_, @except; + next if $dxchan == $me; + my $routeit = adjust_hops($dxchan, $s); # adjust its hop count by node name $dxchan->send($routeit) unless $dxchan->{isolate} || !$routeit; } @@ -1095,12 +1530,14 @@ sub broadcast_all_ak1a { my $s = shift; # the line to be rebroadcast my @except = @_; # to all channels EXCEPT these (dxchannel refs) - my @dxchan = DXChannel::get_all_ak1a(); + my @dxchan = DXChannel::get_all_nodes(); my $dxchan; # send it if it isn't the except list and isn't isolated and still has a hop count foreach $dxchan (@dxchan) { next if grep $dxchan == $_, @except; + next if $dxchan == $me; + my $routeit = adjust_hops($dxchan, $s); # adjust its hop count by node name $dxchan->send($routeit); } @@ -1135,19 +1572,21 @@ sub broadcast_list foreach $dxchan (@_) { my $filter = 1; + next if $dxchan == $me; if ($sort eq 'dx') { next unless $dxchan->{dx}; - ($filter) = Filter::it($dxchan->{spotfilter}, @{$fref}) if ref $fref; + ($filter) = $dxchan->{spotsfilter}->it(@{$fref}) if ref $fref; next unless $filter; } next if $sort eq 'ann' && !$dxchan->{ann}; next if $sort eq 'wwv' && !$dxchan->{wwv}; + next if $sort eq 'wcy' && !$dxchan->{wcy}; next if $sort eq 'wx' && !$dxchan->{wx}; $s =~ s/\a//og unless $dxchan->{beep}; - if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') { + if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'talk') { $dxchan->send($s); } else { $dxchan->delay($s); @@ -1213,13 +1652,6 @@ sub load_hops return 0; } -# remove leading and trailing spaces from an input string -sub unpad -{ - my $s = shift; - $s =~ s/^\s+|\s+$//; - return $s; -} # add a ping request to the ping queues sub addping @@ -1237,13 +1669,82 @@ sub addping # add a rcmd request to the rcmd queues sub addrcmd { - my ($from, $to, $cmd) = @_; + my ($self, $to, $cmd) = @_; + my $r = {}; - $r->{call} = $from; + $r->{call} = $self->{call}; $r->{t} = $main::systime; $r->{cmd} = $cmd; - route(undef, $to, pc34($main::mycall, $to, $cmd)); $rcmds{$to} = $r; + + my $ref = DXCluster->get_exact($to); + if ($ref && $ref->dxchan && $ref->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 $nopc39 = shift; + my $call = $self->call; + + unless ($nopc39) { + $self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op"))); + } + + # unbusy and stop and outgoing mail + my $mref = DXMsg::get_busy($call); + $mref->stop_msg($call) if $mref; + + # create a list of all the nodes that have gone and delete them from the table + my @nodes; + foreach my $node (grep { $_->dxchancall eq $call } DXNode::get_all) { + next if $node->call eq $call; + next if $node->call eq $main::mycall; + push @nodes, $node->call; + $node->del; + } + + # broadcast to all other nodes that all the nodes connected to via me are gone + unless ($self->{isolate}) { + push @nodes, $call; + for (@nodes) { + broadcast_ak1a(pc21($_, 'Gone.'), $self); + } + } + + # remove this node from the tables + my $node = DXCluster->get_exact($call); + $node->del if $node; + + # 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) = @_; + + $line =~ s/\^/\\5E/g; # remove any ^ characters + $self->send(DXProt::pc10($from, $to, $via, $line)); + Log('talk', $self->call, $from, $via?$via:$main::mycall, $line); } 1; __END__