X-Git-Url: http://www.dxcluster.org/gitweb/gitweb.cgi?a=blobdiff_plain;f=perl%2FDXProt.pm;h=48fad54a02b48f5a35d23c0cfdf99f4a147be6f0;hb=6896890cedfefd163cec63c5dcde207e28c87472;hp=4b96fe9608fad9b2eeebbf143c27e302cb654b82;hpb=2a43619b670b8f9249558f814b0183262c3ba4f6;p=spider.git diff --git a/perl/DXProt.pm b/perl/DXProt.pm index 4b96fe96..48fad54a 100644 --- a/perl/DXProt.pm +++ b/perl/DXProt.pm @@ -32,14 +32,22 @@ use BadWords; use DXHash; use Route; use Route::Node; +use Script; use strict; -use vars qw($me $pc11_max_age $pc23_max_age - $last_hour $last10 %eph %pings %rcmds - %nodehops $baddx $badspotter $badnode $censorpc + +use vars qw($VERSION $BRANCH); +$VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ ); +$BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/ || (0,0)); +$main::build += $VERSION; +$main::branch += $BRANCH; + +use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restime $eph_pc34_restime + $last_hour $last10 %eph %pings %rcmds $ann_to_talk + $pingint $obscount %pc19list $chatdupeage + %nodehops $baddx $badspotter $badnode $censorpc $rspfcheck $allowzero $decode_dk0wcy $send_opernam @checklist); -$me = undef; # the channel id for this cluster $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 @@ -47,57 +55,67 @@ $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 +%pc19list = (); # list of outstanding PC19s that haven't had PC16s on them + $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"; -$last10 = time; +$last10 = $last_pc50 = time; +$ann_to_talk = 1; +$rspfcheck = 1; +$eph_restime = 180; +$eph_info_restime = 60*60; +$eph_pc34_restime = 30; +$pingint = 5*60; +$obscount = 2; +$chatdupeage = 20 * 60 * 60; @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) ], # + [ qw(i c c m bp bc c) ], # pc10 + [ qw(i f m d t m c c h) ], # pc11 + [ qw(i c bm m bm bm p h) ], # pc12 + [ qw(i c h) ], # + [ qw(i c h) ], # + [ qw(i c m h) ], # undef , # pc16 has to be validated manually - [ qw(c c h) ], # pc17 - [ qw(m n) ], # pc18 + [ qw(i c c h) ], # pc17 + [ qw(i m n) ], # pc18 undef , # pc19 has to be validated manually undef , # pc20 no validation - [ qw(c m h) ], # pc21 + [ qw(i 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 + [ qw(i d n n n n m c c h) ], # pc23 + [ qw(i c p h) ], # pc24 + [ qw(i c c n n) ], # pc25 + [ qw(i f m d t m c c bc) ], # pc26 + [ qw(i d n n n n m c c bc) ], # pc27 + [ qw(i c c m c d t p m bp n p bp bc) ], # pc28 + [ qw(i c c n m) ], # pc29 + [ qw(i c c n) ], # pc30 + [ qw(i c c n) ], # pc31 + [ qw(i c c n) ], # pc32 + [ qw(i c c n) ], # pc33 + [ qw(i c c m) ], # pc34 + [ qw(i c c m) ], # pc35 + [ qw(i c c m) ], # pc36 + [ qw(i 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 + [ qw(i c m) ], # pc39 + [ qw(i c c m p n) ], # pc40 + [ qw(i c n m h) ], # pc41 + [ qw(i 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 + [ qw(i c c n m m c) ], # pc44 + [ qw(i c c n m) ], # pc45 + [ qw(i c c n) ], # pc46 undef, # pc47 undef, # pc48 - [ qw(c m h) ], # pc49 - [ qw(c n h) ], # pc50 - [ qw(c c n) ], # pc51 + [ qw(i c m h) ], # pc49 + [ qw(i c n h) ], # pc50 + [ qw(i c c n) ], # pc51 undef, undef, undef, @@ -119,7 +137,7 @@ $last10 = time; undef, # pc70 undef, undef, - [ qw(d n n n n n n m m m c c h) ], # pc73 + [ qw(i d n n n n n n m m m c c h) ], # pc73 undef, undef, undef, @@ -130,8 +148,13 @@ $last10 = time; undef, undef, undef, - [ qw(c c c m) ], # pc84 - [ qw(c c c m) ], # pc85 + [ qw(i c c c m) ], # pc84 + [ qw(i c c c m) ], # pc85 + undef, + undef, + undef, + undef, + [ qw(i c n) ], # pc90 ); # use the entry in the check list to check the field list presented @@ -145,44 +168,37 @@ sub check return 0 unless ref $ref; my $i; - shift; # not interested in the first field - for ($i = 0; $i < @$ref; $i++) { + for ($i = 1; $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]); + return $i unless is_callsign($_[$i]); + } elsif ($act eq 'i') { + ; # do nothing } elsif ($act eq 'm') { - return $i+1 unless is_pctext($_[$i]); + return $i unless is_pctext($_[$i]); } elsif ($act eq 'p') { - return $i+1 unless is_pcflag($_[$i]); + return $i unless is_pcflag($_[$i]); } elsif ($act eq 'f') { - return $i+1 unless is_freq($_[$i]); + return $i unless is_freq($_[$i]); } elsif ($act eq 'n') { - return $i+1 unless $_[$i] =~ /^[\d ]+$/; + return $i unless $_[$i] =~ /^[\d ]+$/; } elsif ($act eq 'h') { - return $i+1 unless $_[$i] =~ /^H\d\d?$/; + return $i unless $_[$i] =~ /^H\d\d?$/; } elsif ($act eq 'd') { - return $i+1 unless $_[$i] =~ /^\s*\d+-\w\w\w-[12][90]\d\d$/; + return $i 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 $i unless $_[$i] =~ /^[012]\d[012345]\dZ$/; + } } return 0; } sub init { - my $user = DXUser->get($main::mycall); - $DXProt::myprot_version += $main::version*100; - $me = DXProt->new($main::mycall, 0, $user); - $me->{here} = 1; - $me->{state} = "indifferent"; do "$main::data/hop_table.pl" if -e "$main::data/hop_table.pl"; confess $@ if $@; - $me->{sort} = 'S'; # S for spider - $me->{priv} = 9; -# $Route::Node::me->adddxchan($me); } # @@ -196,7 +212,7 @@ sub new # add this node to the table, the values get filled in later my $pkg = shift; my $call = shift; - $main::routeroot->add($call, '0000', Route::here(1)) if $call ne $main::mycall; + $main::routeroot->add($call, '5000', Route::here(1)) if $call ne $main::mycall; return $self; } @@ -209,6 +225,10 @@ sub start my ($self, $line, $sort) = @_; my $call = $self->{call}; my $user = $self->{user}; + + # log it + my $host = $self->{conn}->{peerhost} || "unknown"; + Log('DXProt', "$call connected from $host"); # remember type of connection $self->{consort} = $line; @@ -220,13 +240,17 @@ sub start $self->{isolate} = $user->{isolate}; $self->{consort} = $line; # save the connection type $self->{here} = 1; + $self->{width} = 80; + + # sort out registration + $self->{registered} = 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) ; - $self->{routefilter} = Filter::read_in('route', $call, 0) || Filter::read_in('route', 'node_default', 0) ; + $self->{routefilter} = Filter::read_in('route', $call, 0) || Filter::read_in('route', 'node_default', 0) unless $self->{isolate} ; # get the INPUT filters (these only pertain to Clusters) @@ -234,42 +258,74 @@ sub start $self->{inwwvfilter} = Filter::read_in('wwv', $call, 1) || Filter::read_in('wwv', 'node_default', 1); $self->{inwcyfilter} = Filter::read_in('wcy', $call, 1) || Filter::read_in('wcy', 'node_default', 1); $self->{inannfilter} = Filter::read_in('ann', $call, 1) || Filter::read_in('ann', 'node_default', 1); - $self->{inroutefilter} = Filter::read_in('route', $call, 1) || Filter::read_in('route', 'node_default', 1); + $self->{inroutefilter} = Filter::read_in('route', $call, 1) || Filter::read_in('route', 'node_default', 1) unless $self->{isolate}; # set unbuffered and no echo $self->send_now('B',"0"); $self->send_now('E',"0"); + $self->conn->echo(0) if $self->conn->can('echo'); # ping neighbour node stuff my $ping = $user->pingint; - $ping = 5*60 unless defined $ping; + $ping = $pingint unless defined $ping; $self->{pingint} = $ping; - $self->{nopings} = $user->nopings || 2; + $self->{nopings} = $user->nopings || $obscount; $self->{pingtime} = [ ]; - $self->{pingave} = 0; + $self->{pingave} = 999; + $self->{metric} ||= 100; + $self->{lastping} = $main::systime; # send initialisation string unless ($self->{outbound}) { - $self->send(pc18()); - $self->{lastping} = $main::systime; - } else { - $self->{lastping} = $main::systime + ($self->pingint / 2); + $self->sendinit; } + $self->state('init'); $self->{pc50_t} = $main::systime; # send info to all logged in thingies $self->tell_login('loginn'); - Log('DXProt', "$call connected"); + # run a script send the output to the debug file + my $script = new Script(lc $call) || new Script('node_default'); + $script->run($self) if $script; +} + +# +# send outgoing 'challenge' +# + +sub sendinit +{ + my $self = shift; + $self->send(pc18()); +} + +sub removepc90 +{ + $_[0] =~ s/^PC90\^[-A-Z0-9]+\^\d+\^//; + $_[0] =~ s/^PC91\^[-A-Z0-9]+\^\d+\^[-A-Z0-9]+\^//; } +#sub send +#{ +# my $self = shift; +# while (@_) { +# my $line = shift; +# $self->SUPER::send($line); +# } +#} + # # This is the normal pcxx despatcher # sub normal { my ($self, $line) = @_; + + # remove any incoming PC90 frames + removepc90($line); + my @field = split /\^/, $line; return unless @field; @@ -280,8 +336,10 @@ sub normal # process PC frames, this will fail unless the frame starts PCnn my ($pcno) = $field[0] =~ /^PC(\d\d)/; # just get the number - return unless $pcno; - return if $pcno < 10 || $pcno > 99; + unless (defined $pcno && $pcno >= 10 && $pcno <= 99) { + dbg("PCPROT: unknown protocol") if isdbg('chanerr'); + return; + } # check for and dump bad protocol messages my $n = check($pcno, @field); @@ -290,818 +348,1238 @@ sub normal return; } - # local processing 1 - my $pcr; - eval { - $pcr = Local::pcprot($self, $pcno, @field); - }; -# dbg("Local::pcprot error $@") if isdbg('local') if $@; - return if $pcr; + my $origin = $self->{call}; + no strict 'subs'; + my $sub = "handle_$pcno"; + + if ($self->can($sub)) { + $self->$sub($pcno, $line, $origin, @field); + } else { + $self->handle_default($pcno, $line, $origin, @field); + } +} - SWITCH: { - if ($pcno == 10) { # incoming talk - - # will we allow it at all? - if ($censorpc) { - my @bad; - if (@bad = BadWords::check($field[3])) { - dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr'); - return; - } - } +# incoming talk commands +sub handle_10 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; - # 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($main::myalias) if $call eq $main::mycall; - $dxchan = DXChannel->get($call) unless $dxchan; - 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 - } + # rsfp check + return if $rspfcheck and !$self->rspfcheck(0, $_[6], $_[1]); + + # will we allow it at all? + if ($censorpc) { + my @bad; + if (@bad = BadWords::check($_[3])) { + dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr'); 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; - } - } + # is it for me or one of mine? + my ($from, $to, $via, $call, $dxchan); + $from = $_[1]; + if ($_[5] gt ' ') { + $via = $_[2]; + $to = $_[5]; + } else { + $to = $_[2]; + } + + # if this is a 'nodx' node then ignore it + if ($badnode->in($_[6]) || ($via && $badnode->in($via))) { + dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr'); + return; + } + + # if this is a 'bad spotter' user then ignore it + my $nossid = $from; + $nossid =~ s/-\d+$//; + if ($badspotter->in($nossid)) { + dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr'); + return; + } + + # if we are converting announces to talk is it a dup? + if ($ann_to_talk) { + if (AnnTalk::is_talk_candidate($from, $_[3]) && AnnTalk::dup($from, $to, $_[3])) { + dbg("DXPROT: Dupe talk from announce, dropped") if isdbg('chanerr'); + return; + } + } + + # it is here and logged on + $dxchan = DXChannel->get($main::myalias) if $to eq $main::mycall; + $dxchan = DXChannel->get($to) unless $dxchan; + if ($dxchan && $dxchan->is_user) { + $_[3] =~ s/\%5E/^/g; + $dxchan->talk($from, $to, $via, $_[3]); + return; + } + + # is it elsewhere, visible on the cluster via the to address? + # note: this discards the via unless the to address is on + # the via address + my ($ref, $vref); + if ($ref = Route::get($to)) { + $vref = Route::Node::get($via) if $via; + $vref = undef unless $vref && grep $to eq $_, $vref->users; + $ref->dxchan->talk($from, $to, $vref ? $via : undef, $_[3], $_[6]); + return; + } + + # not visible here, send a message of condolence + $vref = undef; + $ref = Route::get($from); + $vref = $ref = Route::Node::get($_[6]) unless $ref; + if ($ref) { + $dxchan = $ref->dxchan; + $dxchan->talk($main::mycall, $from, $vref ? $vref->call : undef, $dxchan->msg('talknh', $to) ); + } +} + +# DX Spot handling +sub handle_11 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + + # route 'foreign' pc26s + if ($pcno == 26) { + if ($_[7] ne $main::mycall) { + $self->route($_[7], $line); + return; + } + } - # if this is a 'nodx' node then ignore it - if ($badnode->in($field[7])) { - dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr'); - return; - } + # rsfp check + # return if $rspfcheck and !$self->rspfcheck(1, $_[7], $_[6]); + + # if this is a 'nodx' node then ignore it + if ($badnode->in($_[7])) { + dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr'); + return; + } - # if this is a 'bad spotter' user then ignore it - if ($badspotter->in($field[6])) { - dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr'); - return; - } + # if this is a 'bad spotter' user then ignore it + my $nossid = $_[6]; + $nossid =~ s/-\d+$//; + if ($badspotter->in($nossid)) { + dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr'); + 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("PCPROT: Spot ignored, invalid date or out of range ($field[3] $field[4])\n") if isdbg('chanerr'); - return; - } + # convert the date to a unix date + my $d = cltounix($_[3], $_[4]); + # bang out (and don't pass on) if date is invalid or the spot is too old (or too young) + if (!$d || ($pcno == 11 && ($d < $main::systime - $pc11_max_age || $d > $main::systime + 900))) { + dbg("PCPROT: Spot ignored, invalid date or out of range ($_[3] $_[4])\n") if isdbg('chanerr'); + return; + } - # is it 'baddx' - if ($baddx->in($field[2])) { - dbg("PCPROT: Bad DX spot, ignored") if isdbg('chanerr'); - return; - } + # is it 'baddx' + if ($baddx->in($_[2]) || BadWords::check($_[2]) || $_[2] =~ /COCK/) { + dbg("PCPROT: Bad DX spot, ignored") if isdbg('chanerr'); + 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("PCPROT: useless 'BUSTED' spot") if isdbg('chanerr'); - return; - } - if (Spot::dup($field[1], $field[2], $d, $field[5])) { - dbg("PCPROT: Duplicate Spot ignored\n") if isdbg('chanerr'); - return; - } - if ($censorpc) { - my @bad; - if (@bad = BadWords::check($field[5])) { - dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr'); - return; - } - } + # do some de-duping + $_[5] =~ s/^\s+//; # take any leading blanks off + $_[2] = unpad($_[2]); # take off leading and trailing blanks from spotted callsign + if ($_[2] =~ /BUST\w*$/) { + dbg("PCPROT: useless 'BUSTED' spot") if isdbg('chanerr'); + return; + } + if ($censorpc) { + my @bad; + if (@bad = BadWords::check($_[5])) { + dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr'); + return; + } + } - my @spot = Spot::prepare($field[1], $field[2], $d, $field[5], $field[6], $field[7]); - # global spot filtering on INPUT - if ($self->{inspotsfilter}) { - my ($filter, $hops) = $self->{inspotsfilter}->it(@spot); - unless ($filter) { - dbg("PCPROT: Rejected by input spot filter") if isdbg('chanerr'); - return; - } - } - - # add it - Spot::add(@spot); - - # - # @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 - # + + my @spot = Spot::prepare($_[1], $_[2], $d, $_[5], $_[6], $_[7]); + # global spot filtering on INPUT + if ($self->{inspotsfilter}) { + my ($filter, $hops) = $self->{inspotsfilter}->it(@spot); + unless ($filter) { + dbg("PCPROT: Rejected by input spot filter") if isdbg('chanerr'); + return; + } + } + + # this goes after the input filtering, but before the add + # so that if it is input filtered, it isn't added to the dup + # list. This allows it to come in from a "legitimate" source + if (Spot::dup($_[1], $_[2], $d, $_[5], $_[6])) { + dbg("PCPROT: Duplicate Spot ignored\n") if isdbg('chanerr'); + return; + } + + # add it + Spot::add(@spot); + + # + # @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 && is_qra($qra)) { - my $lat = $user->lat; - my $long = $user->long; - if (defined $lat && defined $long) { - $user->qra(DXBearing::lltoqra($lat, $long)); - $user->put; - } - } + # fix up qra locators of known users + my $user = DXUser->get_current($spot[4]); + if ($user) { + my $qra = $user->qra; + unless ($qra && 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 && $to && $to ne $main::mycall && $main::systime > $last + $DXUser::lastoperinterval && ($node = Route::Node::get($to)) ) { - my $cmd = "forward/opernam $spot[4]"; - # send the rcmd but we aren't interested in the replies... - my $dxchan = $node->dxchan; + # 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 && $to && $to ne $main::mycall && $main::systime > $last + $DXUser::lastoperinterval && ($node = Route::Node::get($to)) ) { + my $cmd = "forward/opernam $spot[4]"; + # send the rcmd but we aren't interested in the replies... + my $dxchan = $node->dxchan; + if ($dxchan && $dxchan->is_clx) { + route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd)); + } else { + route(undef, $to, pc34($main::mycall, $to, $cmd)); + } + if ($to ne $_[7]) { + $to = $_[7]; + $node = Route::Node::get($to); + if ($node) { + $dxchan = $node->dxchan; if ($dxchan && $dxchan->is_clx) { route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd)); } else { route(undef, $to, pc34($main::mycall, $to, $cmd)); } - if ($to ne $field[7]) { - $to = $field[7]; - $node = Route::Node::get($to); - if ($node) { - $dxchan = $node->dxchan; - if ($dxchan && $dxchan->is_clx) { - route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd)); - } else { - route(undef, $to, pc34($main::mycall, $to, $cmd)); - } - } - } - $user->lastoper($main::systime); - $user->put; } } + $user->lastoper($main::systime); + $user->put; } + } + } - # local processing - my $r; - eval { - $r = Local::spot($self, @spot); - }; -# dbg("Local::spot1 error $@") if isdbg('local') 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; + # local processing + my $r; + eval { + $r = Local::spot($self, @spot); + }; + # dbg("Local::spot1 error $@") if isdbg('local') 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; +} + +# announces +sub handle_12 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + + # return if $rspfcheck and !$self->rspfcheck(1, $_[5], $_[1]); + + # announce duplicate checking + $_[3] =~ s/^\s+//; # remove leading blanks + + if ($censorpc) { + my @bad; + if (@bad = BadWords::check($_[3])) { + dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr'); 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("PCPROT: Duplicate Announce ignored") if isdbg('chanerr'); - return; - } + } + + # if this is a 'nodx' node then ignore it + if ($badnode->in($_[5])) { + dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr'); + return; + } - if ($censorpc) { - my @bad; - if (@bad = BadWords::check($field[3])) { - dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr'); + # if this is a 'bad spotter' user then ignore it + my $nossid = $_[1]; + $nossid =~ s/-\d+$//; + if ($badspotter->in($nossid)) { + dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr'); + return; + } + + my $dxchan; + + if ((($dxchan = DXChannel->get($_[2])) && $dxchan->is_user) || $_[4] =~ /^[\#\w.]+$/){ + $self->send_chat($line, @_[1..6]); + } elsif ($_[2] eq '*' || $_[2] eq $main::mycall) { + + # ignore something that looks like a chat line coming in with sysop + # flag - this is a kludge... + if ($_[3] =~ /^\#\d+ / && $_[4] eq '*') { + dbg('PCPROT: Probable chat rewrite, dropped') if isdbg('chanerr'); + return; + } + + # here's a bit of fun, convert incoming ann with a callsign in the first word + # or one saying 'to ' to a talk if we can route to the recipient + if ($ann_to_talk) { + my $call = AnnTalk::is_talk_candidate($_[1], $_[3]); + if ($call) { + my $ref = Route::get($call); + if ($ref) { + $dxchan = $ref->dxchan; + $dxchan->talk($_[1], $call, undef, $_[3], $_[5]) if $dxchan != $self; return; } } + } + + # send it + $self->send_announce($line, @_[1..6]); + } else { + $self->route($_[2], $line); + } +} + +# incoming user +sub handle_16 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + + if (eph_dup($line)) { + dbg("PCPROT: dup PC16 detected") if isdbg('chanerr'); + return; + } + + # general checks + my $dxchan; + my $ncall = $_[1]; + my $newline = "PC16^"; - 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(); + # do I want users from this channel? + unless ($self->user->wantpc16) { + dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr'); + return; + } + # is it me? + if ($ncall eq $main::mycall) { + dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr'); + return; + } + my $parent = Route::Node::get($ncall); + + # if there is a parent, proceed, otherwise if there is a latent PC19 in the PC19list, + # fix it up in the routing tables and issue it forth before the PC16 + unless ($parent) { + my $nl = $pc19list{$ncall}; + + if ($nl && @_ > 3) { # 3 because of the hop count! + + # this is a new (remembered) node, now attach it to me if it isn't in filtered + # and we haven't disallowed it + my $user = DXUser->get_current($ncall); + if (!$user) { + $user = DXUser->new($ncall); + $user->sort('A'); + $user->priv(1); # I have relented and defaulted nodes + $user->lockout(1); + $user->homenode($ncall); + $user->node($ncall); + } + + my $wantpc19 = $user->wantroutepc19; + if ($wantpc19 || !defined $wantpc19) { + my $new = Route->new($ncall); # throw away + if ($self->in_filter_route($new)) { + my @nrout; + for (@$nl) { + $parent = Route::Node::get($_->[0]); + $dxchan = $parent->dxchan if $parent; + if ($dxchan && $dxchan ne $self) { + dbg("PCPROT: PC19 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr'); + $parent = undef; + } + if ($parent) { + my $r = $parent->add($ncall, $_->[1], $_->[2]); + push @nrout, $r unless @nrout; + } } - 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("PCPROT: Rejected by input announce filter") if isdbg('chanerr'); + $user->wantroutepc19(1) unless defined $wantpc19; # for now we work on the basis that pc16 = real route + $user->lastin($main::systime) unless DXChannel->get($ncall); + $user->put; + + # route the pc19 - this will cause 'stuttering PC19s' for a while + $self->route_pc19($origin, $line, @nrout) if @nrout ; + $parent = Route::Node::get($ncall); + unless ($parent) { + dbg("PCPROT: lost $ncall after sending PC19 for it?"); return; } + } else { + return; } - - # send it - $self->send_announce($line, @field[1..6]); - } else { - $self->route($field[2], $line); + delete $pc19list{$ncall}; } - + } else { + dbg("PCPROT: Node $ncall not in config") if isdbg('chanerr'); return; } - - if ($pcno == 13) { - last SWITCH; + } else { + + $dxchan = $parent->dxchan; + if ($dxchan && $dxchan ne $self) { + dbg("PCPROT: PC16 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr'); + return; } - if ($pcno == 14) { - last SWITCH; + + # input filter if required + return unless $self->in_filter_route($parent); + } + + my $i; + my @rout; + for ($i = 2; $i < $#_; $i++) { + my ($call, $conf, $here) = $_[$i] =~ /^(\S+) (\S) (\d)/o; + next unless $call && $conf && defined $here && is_callsign($call); + next if $call eq $main::mycall; + + eph_del_regex("^PC17\\^$call\\^$ncall"); + + $conf = $conf eq '*'; + + # reject this if we think it is a node already + my $r = Route::Node::get($call); + my $u = DXUser->get_current($call) unless $r; + if ($r || ($u && $u->is_node)) { + dbg("PCPROT: $call is a node") if isdbg('chanerr'); + next; } - if ($pcno == 15) { - last SWITCH; + + $r = Route::User::get($call); + my $flags = Route::here($here)|Route::conf($conf); + + if ($r) { + my $au = $r->addparent($parent); + if ($r->flags != $flags) { + $r->flags($flags); + $au = $r; + } + push @rout, $r if $au; + } else { + push @rout, $parent->add_user($call, $flags); } - if ($pcno == 16) { # add a user + + # 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($parent->call) if !$user->homenode; + $user->node($parent->call); + $user->lastin($main::systime) unless DXChannel->get($call); + $user->put; + } + $self->route_pc16($origin, $line, $parent, @rout) if @rout; +} + +# remove a user +sub handle_17 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + my $dxchan; + my $ncall = $_[2]; + my $ucall = $_[1]; - # general checks - my $dxchan; - my $ncall = $field[1]; - my $newline = "PC16^"; + eph_del_regex("^PC16\\^$ncall.*$ucall"); - if ($ncall eq $main::mycall) { - dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr'); - return; - } - $dxchan = DXChannel->get($ncall); - if ($dxchan && $dxchan ne $self) { - dbg("PCPROT: PC16 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr'); - return; - } - my $parent = Route::Node::get($ncall); - unless ($parent) { - dbg("PCPROT: Node $ncall not in config") if isdbg('chanerr'); - return; - } - - # input filter if required - return unless $self->in_filter_route($parent); - - my $i; - my @rout; - for ($i = 2; $i < $#field; $i++) { - my ($call, $conf, $here) = $field[$i] =~ /^(\S+) (\S) (\d)/o; - next unless $call && $conf && defined $here && is_callsign($call); - next if $call eq $main::mycall; - - eph_del_regex("^PC17\^$call\^$ncall"); - - $conf = $conf eq '*'; + # do I want users from this channel? + unless ($self->user->wantpc16) { + dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr'); + return; + } + if ($ncall eq $main::mycall) { + dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr'); + return; + } - my $r = Route::User::get($call); - my $flags = Route::here($here)|Route::conf($conf); - - if ($r) { - if ($r->flags != $flags) { - $r->flags($flags); - push @rout, $r; - } - $r->addparent($ncall); - } else { - push @rout, $parent->add_user($call, $flags); - } - - # 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($parent->call) if !$user->homenode; - $user->node($parent->call); - $user->lastin($main::systime) unless DXChannel->get($call); - $user->put; - } + my $uref = Route::User::get($ucall); + unless ($uref) { + dbg("PCPROT: Route::User $ucall not in config") if isdbg('chanerr'); + return; + } + my $parent = Route::Node::get($ncall); + unless ($parent) { + dbg("PCPROT: Route::Node $ncall not in config") if isdbg('chanerr'); + return; + } - if (eph_dup($line)) { - dbg("PCPROT: dup PC16 detected") if isdbg('chanerr'); - return; - } + $dxchan = $parent->dxchan; + if ($dxchan && $dxchan ne $self) { + dbg("PCPROT: PC17 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr'); + return; + } + + # input filter if required + return unless $self->in_filter_route($parent); - # queue up any messages (look for privates only) - DXMsg::queue_msg(1) if $self->state eq 'normal'; + $parent->del_user($uref); - $self->route_pc16($parent, @rout) if @rout; - return; + if (eph_dup($line)) { + dbg("PCPROT: dup PC17 detected") if isdbg('chanerr'); + return; + } + + $self->route_pc17($origin, $line, $parent, $uref); +} + +# link request +sub handle_18 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + $self->state('init'); + + # record the type and version offered + if ($_[1] =~ /DXSpider Version: (\d+\.\d+) Build: (\d+\.\d+)/) { + $self->version(53 + $1); + $self->user->version(53 + $1); + $self->build(0 + $2); + $self->user->build(0 + $2); + unless ($self->is_spider) { + $self->user->sort('S'); + $self->user->put; + $self->sort('S'); } + } else { + $self->version(50.0); + $self->version($_[2] / 100) if $_[2] && $_[2] =~ /^\d+$/; + $self->user->version($self->version); + } + + # first clear out any nodes on this dxchannel + my $parent = Route::Node::get($self->{call}); + my @rout = $parent->del_nodes; + $self->route_pc21($origin, $line, @rout, $parent) if @rout; + $self->send_local_config(); + $self->send(pc20()); +} - if ($pcno == 17) { # remove a user - my $dxchan; - my $ncall = $field[2]; - my $ucall = $field[1]; +# incoming cluster list +sub handle_19 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; - eph_del_regex("^PC16.*$ncall.*$ucall"); - - if ($ncall eq $main::mycall) { - dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr'); - return; - } - $dxchan = DXChannel->get($ncall); - if ($dxchan && $dxchan ne $self) { - dbg("PCPROT: PC17 from $self->{call} trying to alter locally connected $ncall, ignored!") if isdbg('chanerr'); - return; - } - my $parent = Route::Node::get($ncall); - unless ($parent) { - dbg("PCPROT: Route::Node $ncall not in config") if isdbg('chanerr'); - return; - } + my $i; + my $newline = "PC19^"; - # input filter if required - return unless $self->in_filter_route($parent); + if (eph_dup($line)) { + dbg("PCPROT: dup PC19 detected") if isdbg('chanerr'); + return; + } + + # new routing list + my @rout; - my @rout = $parent->del_user($ucall); + # first get the INTERFACE node + my $parent = Route::Node::get($self->{call}); + unless ($parent) { + dbg("DXPROT: my parent $self->{call} has disappeared"); + $self->disconnect; + return; + } - if (eph_dup($line)) { - dbg("PCPROT: dup PC17 detected") if isdbg('chanerr'); - return; + # if the origin isn't the same as the INTERFACE, then reparent, creating nodes as necessary + if ($origin ne $self->call) { + my $op = Route::Node::get($origin); + unless ($op) { + $op = $parent->add($origin, 5000, Route::here(1)); + my $user = DXUser->get_current($origin); + if (!$user) { + $user = DXUser->new($origin); + $user->sort('S'); + $user->priv(1); # I have relented and defaulted nodes + $user->lockout(1); + $user->homenode($origin); + $user->node($origin); + $user->wantroutepc19(1); } + $user->put; + } + $parent = $op; + } - $self->route_pc17($parent, @rout) if @rout; - return; + # parse the PC19 + for ($i = 1; $i < $#_-1; $i += 4) { + my $here = $_[$i]; + my $call = uc $_[$i+1]; + my $conf = $_[$i+2]; + my $ver = $_[$i+3]; + next unless defined $here && defined $conf && is_callsign($call); + + eph_del_regex("^PC(?:21\\^$call|17\\^[^\\^]+\\^$call)"); + + # check for sane parameters + # $ver = 5000 if $ver eq '0000'; + next if $ver < 5000; # only works with version 5 software + next if length $call < 3; # min 3 letter callsigns + next if $call eq $main::mycall; + + # check that this PC19 isn't trying to alter the wrong dxchan + my $dxchan = DXChannel->get($call); + if ($dxchan && $dxchan != $self) { + dbg("PCPROT: PC19 from $self->{call} trying to alter wrong locally connected $call, ignored!") if isdbg('chanerr'); + next; } - - if ($pcno == 18) { # link request - $self->state('init'); - - # first clear out any nodes on this dxchannel - my $parent = Route::Node::get($self->{call}); - my @rout = $parent->del_nodes; - $self->route_pc21(@rout, $parent); - $self->send_local_config(); - $self->send(pc20()); - return; # we don't pass these on + + # 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 + $user->lockout(1); + $user->homenode($call); + $user->node($call); } - - if ($pcno == 19) { # incoming cluster list - my $i; - my $newline = "PC19^"; - - # new routing list - my @rout; - my $parent = Route::Node::get($self->{call}); - unless ($parent) { - dbg("DXPROT: my parent $self->{call} has disappeared"); - $self->disconnect; - return; - } - # parse the PC19 - for ($i = 1; $i < $#field-1; $i += 4) { - my $here = $field[$i]; - my $call = uc $field[$i+1]; - my $conf = $field[$i+2]; - my $ver = $field[$i+3]; - next unless defined $here && defined $conf && is_callsign($call); + my $r = Route::Node::get($call); + my $flags = Route::here($here)|Route::conf($conf); - eph_del_regex("^PC(?:21\^$call|17\^[^\^]+\^$call)"); - - # check for sane parameters - $ver = 5000 if $ver eq '0000'; - next if $ver < 5000; # only works with version 5 software - next if length $call < 3; # min 3 letter callsigns - next if $call eq $main::mycall; - - # update it if required - my $r = Route::Node::get($call); - my $flags = Route::here($here)|Route::conf($conf); - if ($r) { - my $ar; - if ($call ne $parent->call) { - if ($self->in_filter_route($r)) { - $ar = $parent->add($call, $ver, $flags); - push @rout, $ar if $ar; - } else { - next; - } - } - if ($r->version ne $ver || $r->flags != $flags) { - $r->version($ver); - $r->flags($flags); - push @rout, $r unless $ar; - } + # modify the routing table if it is in it, otherwise store it in the pc19list for now + if ($r) { + my $ar; + if ($call ne $parent->call) { + if ($self->in_filter_route($r)) { + $ar = $parent->add($call, $ver, $flags); + push @rout, $ar if $ar; } else { - if ($call eq $self->{call}) { - dbg("DXPROT: my channel route for $call has disappeared"); - next; - }; - - my $new = Route->new($call); # throw away - if ($self->in_filter_route($new)) { - my $r = $parent->add($call, $ver, $flags); - push @rout, $r; - } else { - next; - } + next; } + } + if ($r->version ne $ver || $r->flags != $flags) { + $r->version($ver); + $r->flags($flags); + push @rout, $r unless $ar; + } + } else { - # 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 - $user->lockout(1); - $user->homenode($call); - $user->node($call); + # if he is directly connected or allowed then add him, otherwise store him up for later + if ($call eq $self->{call} || $user->wantroutepc19) { + my $new = Route->new($call); # throw away + if ($self->in_filter_route($new)) { + my $ar = $parent->add($call, $ver, $flags); + $user->wantroutepc19(1) unless defined $user->wantroutepc19; + push @rout, $ar if $ar; + } else { + next; } - $user->lastin($main::systime) unless DXChannel->get($call); - $user->put; + } else { + $pc19list{$call} = [] unless exists $pc19list{$call}; + my $nl = $pc19list{$call}; + push @{$pc19list{$call}}, [$self->{call}, $ver, $flags] unless grep $_->[0] eq $self->{call}, @$nl; } + } - if (eph_dup($line)) { - dbg("PCPROT: dup PC19 detected") if isdbg('chanerr'); - return; - } + # 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; + + $user->lastin($main::systime) unless DXChannel->get($call); + $user->put; + } - $self->route_pc19(@rout) if @rout; - return; - } + + $self->route_pc19($origin, $line, @rout) if @rout; +} - if ($pcno == 20) { # send local configuration - $self->send_local_config(); - $self->send(pc22()); - $self->state('normal'); - return; - } +# send local configuration +sub handle_20 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + $self->send_local_config(); + $self->send(pc22()); + $self->state('normal'); + $self->{lastping} = 0; +} - if ($pcno == 21) { # delete a cluster from the list - my $call = uc $field[1]; +# delete a cluster from the list +sub handle_21 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + my $call = uc $_[1]; - eph_del_regex("^PC1[79].*$call"); + eph_del_regex("^PC1[679].*$call"); - my @rout; - my $parent = Route::Node::get($self->{call}); - unless ($parent) { - dbg("DXPROT: my parent $self->{call} has disappeared"); - $self->disconnect; - return; - } + # if I get a PC21 from the same callsign as self then treat it + # as a PC39: I have gone away + if ($call eq $self->call) { + $self->disconnect(1); + return; + } + + # check to see if we are in the pc19list, if we are then don't bother with any of + # this routing table manipulation, just remove it from the list and dump it + my @rout; + if (my $nl = $pc19list{$call}) { + $pc19list{$call} = [ grep {$_->[0] ne $self->{call}} @$nl ]; + delete $pc19list{$call} unless @{$pc19list{$call}}; + } else { + + my $parent = Route::Node::get($self->{call}); + unless ($parent) { + dbg("DXPROT: my parent $self->{call} has disappeared"); + $self->disconnect; + return; + } + if ($call ne $main::mycall) { # don't allow malicious buggers to disconnect me! my $node = Route::Node::get($call); - if ($call ne $main::mycall) { # don't allow malicious buggers to disconnect me! - if ($call eq $self->{call}) { - dbg("PCPROT: Trying to disconnect myself with PC21") if isdbg('chanerr'); + if ($node) { + + my $dxchan = DXChannel->get($call); + if ($dxchan && $dxchan != $self) { + dbg("PCPROT: PC21 from $self->{call} trying to alter locally connected $call, ignored!") if isdbg('chanerr'); return; } - - if ($node) { - # input filter it - return unless $self->in_filter_route($node); - - # routing objects - push @rout, $node->del($parent); - } - } else { - dbg("PCPROT: I WILL _NOT_ be disconnected!") if isdbg('chanerr'); - return; - } - - if (eph_dup($line)) { - dbg("PCPROT: dup PC21 detected") if isdbg('chanerr'); - return; + + # input filter it + return unless $self->in_filter_route($node); + + # routing objects + push @rout, $node->del($parent); } - - $self->route_pc21(@rout) if @rout; + } else { + dbg("PCPROT: I WILL _NOT_ be disconnected!") if isdbg('chanerr'); return; } + } + + $self->route_pc21($origin, $line, @rout) if @rout; +} - if ($pcno == 22) { - $self->state('normal'); - return; - } + +sub handle_22 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + $self->state('normal'); + $self->{lastping} = 0; +} - if ($pcno == 23 || $pcno == 27) { # WWV info +# WWV info +sub handle_23 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; - # route 'foreign' pc27s - if ($pcno == 27) { - if ($field[8] ne $main::mycall) { - $self->route($field[8], $line); - return; - } - } + # route foreign' pc27s + if ($pcno == 27) { + if ($_[8] ne $main::mycall) { + $self->route($_[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("PCPROT: WWV Date ($field[1] $field[2]) out of range") if isdbg('chanerr'); - return; - } - if (Geomag::dup($d,$sfi,$k,$i,$field[6])) { - dbg("PCPROT: Dup WWV Spot ignored\n") if isdbg('chanerr'); - return; - } - $field[7] =~ s/-\d+$//o; # remove spotter's ssid + return if $rspfcheck and !$self->rspfcheck(1, $_[8], $_[7]); + + # do some de-duping + my $d = cltounix($_[1], sprintf("%02d18Z", $_[2])); + my $sfi = unpad($_[3]); + my $k = unpad($_[4]); + my $i = unpad($_[5]); + my ($r) = $_[6] =~ /R=(\d+)/; + $r = 0 unless $r; + if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) { + dbg("PCPROT: WWV Date ($_[1] $_[2]) out of range") if isdbg('chanerr'); + return; + } + if (Geomag::dup($d,$sfi,$k,$i,$_[6])) { + dbg("PCPROT: Dup WWV Spot ignored\n") if isdbg('chanerr'); + return; + } + $_[7] =~ s/-\d+$//o; # remove spotter's ssid - my $wwv = Geomag::update($d, $field[2], $sfi, $k, $i, @field[6..8], $r); + my $wwv = Geomag::update($d, $_[2], $sfi, $k, $i, @_[6..8], $r); - my $rep; - eval { - $rep = Local::wwv($self, $field[1], $field[2], $sfi, $k, $i, @field[6..8], $r); - }; -# dbg("Local::wwv2 error $@") if isdbg('local') if $@; - return if $rep; + my $rep; + eval { + $rep = Local::wwv($self, $_[1], $_[2], $sfi, $k, $i, @_[6..8], $r); + }; + # dbg("Local::wwv2 error $@") if isdbg('local') if $@; + return if $rep; - # DON'T be silly and send on PC27s! - return if $pcno == 27; + # 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; - } + # broadcast to the eager world + send_wwv_spot($self, $line, $d, $_[2], $sfi, $k, $i, @_[6..8]); +} - if ($pcno == 24) { # set here status - my $call = uc $field[1]; - my ($nref, $uref); - $nref = Route::Node::get($call); - $uref = Route::User::get($call); - return unless $nref || $uref; # if we don't know where they are, it's pointless sending it on +# set here status +sub handle_24 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + my $call = uc $_[1]; + my ($nref, $uref); + $nref = Route::Node::get($call); + $uref = Route::User::get($call); + return unless $nref || $uref; # if we don't know where they are, it's pointless sending it on - unless (eph_dup($line)) { - $nref->here($field[2]) if $nref; - $uref->here($field[2]) if $uref; - my $ref = $nref || $uref; - return unless $self->in_filter_route($ref); - $self->route_pc24($ref, $field[3]); - } - return; - } + if (eph_dup($line)) { + dbg("PCPROT: Dup PC24 ignored\n") if isdbg('chanerr'); + return; + } + + $nref->here($_[2]) if $nref; + $uref->here($_[2]) if $uref; + my $ref = $nref || $uref; + return unless $self->in_filter_route($ref); + + $self->route_pc24($origin, $line, $ref, $_[3]); +} - if ($pcno == 25) { # merge request - if ($field[1] ne $main::mycall) { - $self->route($field[1], $line); - return; - } - if ($field[2] eq $main::mycall) { - dbg("PCPROT: Trying to merge to myself, ignored") if isdbg('chanerr'); - return; - } +# merge request +sub handle_25 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + if ($_[1] ne $main::mycall) { + $self->route($_[1], $line); + return; + } + if ($_[2] eq $main::mycall) { + dbg("PCPROT: Trying to merge to myself, ignored") if isdbg('chanerr'); + return; + } - Log('DXProt', "Merge request for $field[3] spots and $field[4] WWV from $field[1]"); + Log('DXProt', "Merge request for $_[3] spots and $_[4] WWV from $_[2]"); - # 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; + # spots + if ($_[3] > 0) { + my @in = reverse Spot::search(1, undef, undef, 0, $_[3]); + my $in; + foreach $in (@in) { + $self->send(pc26(@{$in}[0..4], $_[2])); } + } - 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; + # wwv + if ($_[4] > 0) { + my @in = reverse Geomag::search(0, $_[4], time, 1); + my $in; + foreach $in (@in) { + $self->send(pc27(@{$in}[0..5], $_[2])); } + } +} + +sub handle_26 {goto &handle_11} +sub handle_27 {goto &handle_23} + +# mail/file handling +sub handle_28 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + if ($_[1] eq $main::mycall) { + no strict 'refs'; + my $sub = "DXMsg::handle_$pcno"; + &$sub($self, @_); + } else { + $self->route($_[1], $line) unless $self->is_clx; + } +} + +sub handle_29 {goto &handle_28} +sub handle_30 {goto &handle_28} +sub handle_31 {goto &handle_28} +sub handle_32 {goto &handle_28} +sub handle_33 {goto &handle_28} - if ($pcno == 34 || $pcno == 36) { # remote commands (incoming) - $self->process_rcmd($field[1], $field[2], $field[2], $field[3]); - return; - } +sub handle_34 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + if (eph_dup($line, $eph_pc34_restime)) { + dbg("PCPROT: dupe PC34, ignored") if isdbg('chanerr'); + } else { + $self->process_rcmd($_[1], $_[2], $_[2], $_[3]); + } +} - if ($pcno == 35) { # remote command replies - $self->process_rcmd_reply($field[1], $field[2], $field[1], $field[3]); - return; - } +# remote command replies +sub handle_35 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + eph_del_regex("^PC35\\^$_[2]\\^$_[1]\\^"); + $self->process_rcmd_reply($_[1], $_[2], $_[1], $_[3]); +} - # for pc 37 see 44 onwards +sub handle_36 {goto &handle_34} - if ($pcno == 38) { # node connected list from neighbour - return; - } +# database stuff +sub handle_37 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + if ($_[1] eq $main::mycall) { + no strict 'refs'; + my $sub = "DXDb::handle_$pcno"; + &$sub($self, @_); + } else { + $self->route($_[1], $line) unless $self->is_clx; + } +} + +# node connected list from neighbour +sub handle_38 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; +} - if ($pcno == 39) { # incoming disconnect - if ($field[1] eq $self->{call}) { - $self->disconnect(1); - eph_del_regex("^PC(?:1[679]|21).*$field[1]"); - } else { - dbg("PCPROT: came in on wrong channel") if isdbg('chanerr'); - } - return; - } +# incoming disconnect +sub handle_39 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + if ($_[1] eq $self->{call}) { + $self->disconnect(1); + } else { + dbg("PCPROT: came in on wrong channel") if isdbg('chanerr'); + } +} + +sub handle_40 {goto &handle_28} - if ($pcno == 41) { # user info - my $call = $field[1]; +# user info +sub handle_41 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + my $call = $_[1]; - # input filter if required -# my $ref = Route::get($call) || Route->new($call); -# return unless $self->in_filter_route($ref); + my $l = $line; + $l =~ s/[\x00-\x20\x7f-\xff]+//g; # remove all funny characters and spaces for dup checking + if (eph_dup($l, $eph_info_restime)) { + dbg("PCPROT: dup PC41, ignored") if isdbg('chanerr'); + return; + } + + # input filter if required + # my $ref = Route::get($call) || Route->new($call); + # return unless $self->in_filter_route($ref); - if ($field[3] eq $field[2]) { - dbg('PCPROT: invalid value') if isdbg('chanerr'); - return; - } + 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) if !$user; + # add this station to the user database, if required + my $user = DXUser->get_current($call); + $user = DXUser->new($call) unless $user; - if ($field[2] == 1) { - $user->name($field[3]); - } elsif ($field[2] == 2) { - $user->qth($field[3]); - } elsif ($field[2] == 3) { - if (is_latlong($field[3])) { - my ($lat, $long) = DXBearing::stoll($field[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 ($field[2] == 4) { - $user->homenode($field[3]); - } elsif ($field[2] == 5) { - if (is_qra($field[3])) { - my ($lat, $long) = DXBearing::qratoll($field[3]); - $user->lat($lat); - $user->long($long); - $user->qra($field[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; + 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, $field[2], $field[3], $field[4]); -# return; - } + # perhaps this IS what we want after all + # $self->route_pc41($ref, $call, $_[2], $_[3], $_[4]); +} - if ($pcno == 43) { - last SWITCH; - } +sub handle_42 {goto &handle_28} - if ($pcno == 37 || $pcno == 44 || $pcno == 45 || $pcno == 46 || $pcno == 47 || $pcno == 48) { - DXDb::process($self, $line); - return; - } + +# 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} - if ($pcno == 50) { # keep alive/user list - my $call = $field[1]; - my $node = Route::Node::get($call); - if ($node) { - return unless $node->call eq $self->{call}; - $node->usercount($field[2]); +# message and database +sub handle_49 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; - # input filter if required - return unless $self->in_filter_route($node); + 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; + } +} - $self->route_pc50($node, $field[2], $field[3]) unless eph_dup($line); - } - return; - } +# keep alive/user list +sub handle_50 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = 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($origin, $line, $node, $_[2], $_[3]) unless eph_dup($line); + } +} - if ($pcno == 51) { # incoming ping requests/answers - my $to = $field[1]; - my $from = $field[2]; - my $flag = $field[3]; +# incoming ping requests/answers +sub handle_51 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = 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) { - $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}}; - } - } + # 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 || $obscount; + 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); } - return; } - - if ($pcno == 75) { # dunno but route it - my $call = $field[1]; - if ($call ne $main::mycall) { - $self->route($call, $line); - } + } 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 $origin = shift; + my $call = $_[1]; + if ($call ne $main::mycall) { + $self->route($call, $line); + } +} - if ($pcno == 73) { # WCY broadcasts - my $call = $field[1]; +# WCY broadcasts +sub handle_73 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + my $call = $_[1]; - # do some de-duping - my $d = cltounix($call, sprintf("%02d18Z", $field[2])); - if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $field[2] < 0 || $field[2] > 23) { - dbg("PCPROT: WCY Date ($call $field[2]) out of range") if isdbg('chanerr'); - return; - } - @field = map { unpad($_) } @field; - if (WCY::dup($d,@field[3..7])) { - dbg("PCPROT: Dup WCY Spot ignored\n") if isdbg('chanerr'); - return; - } + # 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, @field[2..12]); + my $wcy = WCY::update($d, @_[2..12]); - my $rep; - eval { - $rep = Local::wwv($self, @field[1..12]); - }; - # dbg("Local::wcy error $@") if isdbg('local') if $@; - return if $rep; + 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, @field[2..12]); - return; - } + # broadcast to the eager world + send_wcy_spot($self, $line, $d, @_[2..12]); +} - if ($pcno == 84) { # remote commands (incoming) - $self->process_rcmd($field[1], $field[2], $field[3], $field[4]); - return; - } +# remote commands (incoming) +sub handle_84 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = shift; + $self->process_rcmd($_[1], $_[2], $_[3], $_[4]); +} - if ($pcno == 85) { # remote command replies - $self->process_rcmd_reply($field[1], $field[2], $field[3], $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!!!! - # +# remote command replies +sub handle_85 +{ + my $self = shift; + my $pcno = shift; + my $line = shift; + my $origin = 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; + my $origin = shift; if (eph_dup($line)) { dbg("PCPROT: Ephemeral dup, dropped") if isdbg('chanerr'); } else { unless ($self->{isolate}) { - broadcast_ak1a($line, $self); # send it to everyone but me + DXChannel::broadcast_nodes($line, $self) if $line =~ /\^H\d+\^?~?$/; # send it to everyone but me } } } @@ -1115,20 +1593,22 @@ 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 == $me; - - # send a pc50 out on this channel - $dxchan->{pc50_t} = $main::systime unless exists $dxchan->{pc50_t}; - if ($t >= $dxchan->{pc50_t} + $DXProt::pc50_interval) { - my $s = pc50($me, scalar DXChannel::get_all_users); - eph_dup($s); - $dxchan->send($s); - $dxchan->{pc50_t} = $t; - } + next if $dxchan == $main::me; + # send the pc50 + $dxchan->send($pc50s) if $pc50s; + # send a ping out on this channel if ($dxchan->{pingint} && $t >= $dxchan->{pingint} + $dxchan->{lastping}) { if ($dxchan->{nopings} <= 0) { @@ -1163,6 +1643,7 @@ sub process # some active measures # + sub send_dx_spot { my $self = shift; @@ -1173,7 +1654,7 @@ sub send_dx_spot # send it if it isn't the except list and isn't isolated and still has a hop count # taking into account filtering and so on foreach $dxchan (@dxchan) { - next if $dxchan == $me; + next if $dxchan == $main::me; next if $dxchan == $self && $self->is_node; $dxchan->dx_spot($line, $self->{isolate}, @_, $self->{call}); } @@ -1197,7 +1678,8 @@ sub send_prot_line { my ($self, $filter, $hops, $isolate, $line) = @_; my $routeit; - + + if ($hops) { $routeit = $line; $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/; @@ -1220,13 +1702,13 @@ sub send_wwv_spot 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]); + 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($_[8]); + @dxcc = Prefix::extract($_[7]); if (@dxcc > 0) { $org_dxcc = $dxcc[1]->dxcc; $org_itu = $dxcc[1]->itu; @@ -1236,14 +1718,13 @@ sub send_wwv_spot # send it if it isn't the except list and isn't isolated and still has a hop count # taking into account filtering and so on foreach $dxchan (@dxchan) { - next if $dxchan == $me; + 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 @@ -1267,13 +1748,13 @@ sub send_wcy_spot 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]); + 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($_[12]); + @dxcc = Prefix::extract($_[11]); if (@dxcc > 0) { $org_dxcc = $dxcc[1]->dxcc; $org_itu = $dxcc[1]->itu; @@ -1283,7 +1764,7 @@ sub send_wcy_spot # send it if it isn't the except list and isn't isolated and still has a hop count # taking into account filtering and so on foreach $dxchan (@dxchan) { - next if $dxchan == $me; + 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); @@ -1327,33 +1808,131 @@ sub send_announce $to = ''; } $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 ($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 == $me; + next if $dxchan == $main::me; next if $dxchan == $self && $self->is_node; - my $routeit; - my ($filter, $hops); + $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]); + my $ak1a_line; + + # munge the group and recast the line if required + if ($target =~ s/\.LST$//) { + $ak1a_line = $line; + } + + # 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; + } + - $dxchan->announce($line, $self->{isolate}, $to, $target, $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) + 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) { + my $is_ak1a = $dxchan->is_ak1a; + + if ($dxchan->is_node) { + next if $dxchan == $main::me; + next if $dxchan == $self; + next unless $dxchan->is_spider || $is_ak1a; + next if $target eq 'LOCAL'; + if (!$ak1a_line && $is_ak1a) { + $ak1a_line = DXProt::pc12($_[0], $text, $_[1], "$target.LST"); + } + } + + $dxchan->chat($is_ak1a ? $ak1a_line : $line, $self->{isolate}, $target, $_[1], $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq); } } @@ -1364,6 +1943,7 @@ sub announce my $isolate = shift; my $to = shift; my $target = shift; + my $text = shift; my ($filter, $hops); if ($self->{annfilter}) { @@ -1373,11 +1953,16 @@ sub announce 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 $n; + my $node; my @nodes; my @localnodes; my @remotenodes; @@ -1387,27 +1972,36 @@ sub send_local_config # send our nodes if ($self->{isolate}) { @localnodes = ( $main::routeroot ); + $self->send_route($main::mycall, \&pc19, 1, $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 && $_->call ne $self->{call} } DXChannel::get_all_nodes(); + + # send locally connected nodes + 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; + $self->send_route($main::mycall, \&pc19, scalar(@localnodes)+1, $main::routeroot, @localnodes); + + my $node; + my @rawintcalls = map { $_->nodes } @localnodes if @localnodes; + my @intcalls; + for $node (@rawintcalls) { + push @intcalls, $node unless grep $node eq $_, @intcalls; + } my $ref = Route::Node::get($self->{call}); my @rnodes = $ref->nodes; - for my $n (@intcalls) { - push @remotenodes, Route::Node::get($n) unless grep $n eq $_, @rnodes; + for $node (@intcalls) { + push @remotenodes, Route::Node::get($node) unless grep $node eq $_, @rnodes, @remotenodes; } - unshift @localnodes, $main::routeroot; + $self->send_route($main::mycall, \&pc19, scalar(@remotenodes), @remotenodes); } - send_route($self, \&pc19, scalar(@localnodes)+scalar(@remotenodes), @localnodes, @remotenodes); - # get all the users connected on the above nodes and send them out - foreach $n (@localnodes, @remotenodes) { - if ($n) { - send_route($self, \&pc16, 1, $n, map {my $r = Route::User::get($_); $r ? ($r) : ()} $n->users); + foreach $node ($main::routeroot, @localnodes, @remotenodes) { + if ($node) { + my @rout = map {my $r = Route::User::get($_); $r ? ($r) : ()} $node->users; + $self->send_route($main::mycall, \&pc16, 1, $node, @rout) if @rout && $self->user->wantsendpc16; } else { dbg("sent a null value") if isdbg('chanerr'); } @@ -1443,103 +2037,13 @@ sub route if ($dxchan) { my $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name if ($routeit) { - $dxchan->send($routeit) unless $dxchan == $me; + $dxchan->send($routeit) unless $dxchan == $main::me; } } else { dbg("PCPROT: No route available, dropped") if isdbg('chanerr'); } } -# broadcast a message to all clusters taking into account isolation -# [except those mentioned after buffer] -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_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; - } -} - -# broadcast a message to all clusters ignoring isolation -# [except those mentioned after buffer] -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_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); - } -} - -# broadcast to all users -# storing the spot or whatever until it is in a state to receive it -sub broadcast_users -{ - my $s = shift; # the line to be rebroadcast - my $sort = shift; # the type of transmission - my $fref = shift; # a reference to an object to filter on - my @except = @_; # to all channels EXCEPT these (dxchannel refs) - my @dxchan = DXChannel::get_all_users(); - my $dxchan; - my @out; - - foreach $dxchan (@dxchan) { - next if grep $dxchan == $_, @except; - push @out, $dxchan; - } - broadcast_list($s, $sort, $fref, @out); -} - -# broadcast to a list of users -sub broadcast_list -{ - my $s = shift; - my $sort = shift; - my $fref = shift; - my $dxchan; - - foreach $dxchan (@_) { - my $filter = 1; - next if $dxchan == $me; - - if ($sort eq 'dx') { - next unless $dxchan->{dx}; - ($filter) = $dxchan->{spotsfilter}->it(@{$fref}) if ref $fref; - next unless $filter; - } - next if $sort eq 'ann' && !$dxchan->{ann} && $s !~ /^To\s+LOCAL\s+de\s+(?:$main::myalias|$main::mycall)/i; - 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 'talk') { - $dxchan->send($s); - } else { - $dxchan->delay($s); - } - } -} - - # # obtain the hops from the list for this callsign and pc no # @@ -1594,7 +2098,7 @@ sub load_hops return $self->msg('lh1') unless -e "$main::data/hop_table.pl"; do "$main::data/hop_table.pl"; return $@ if $@; - return 0; + return (); } @@ -1712,25 +2216,48 @@ sub disconnect 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"))); } - # do routing stuff + # 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_nodes; # at the next level @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; + $self->route_pc21($main::mycall, undef, @rout) if @rout; } # remove outstanding pings @@ -1753,18 +2280,20 @@ sub disconnect # sub talk { - my ($self, $from, $to, $via, $line) = @_; + my ($self, $from, $to, $via, $line, $origin) = @_; $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); + $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 $origin = shift; my $generate = shift; my $no = shift; # the no of things to filter on my $routeit; @@ -1774,10 +2303,10 @@ sub send_route for (; @_ && $no; $no--) { my $r = shift; - if ($self->{routefilter}) { + 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); + ($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 { @@ -1787,7 +2316,7 @@ sub send_route dbg("was sent a null value") if isdbg('chanerr'); } } else { - push @rin, $r; + push @rin, $r unless $self->{isolate} && $r->call ne $main::mycall; } } if (@rin) { @@ -1799,6 +2328,7 @@ sub send_route $routeit = adjust_hops($self, $line); # adjust its hop count by node name next unless $routeit; } + $self->send($routeit); } } @@ -1807,18 +2337,20 @@ sub send_route sub broadcast_route { my $self = shift; + my $origin = shift; my $generate = shift; + my $line = shift; my @dxchan = DXChannel::get_all_nodes(); my $dxchan; - my $line; - foreach $dxchan (@dxchan) { - next if $dxchan == $self; - next if $dxchan == $me; - if ($dxchan->{routefilter}) { - $dxchan->send_route($generate, @_); - } else { - $dxchan->send_route($generate, @_) unless $self->{isolate} || $dxchan->{isolate}; + 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($origin, $generate, @_); } } } @@ -1826,43 +2358,59 @@ sub broadcast_route sub route_pc16 { my $self = shift; - broadcast_route($self, \&pc16, 1, @_); + return unless $self->user->wantpc16; + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc16, $line, 1, @_); } sub route_pc17 { my $self = shift; - broadcast_route($self, \&pc17, 1, @_); + return unless $self->user->wantpc16; + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc17, $line, 1, @_); } sub route_pc19 { my $self = shift; - broadcast_route($self, \&pc19, scalar @_, @_); + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc19, $line, scalar @_, @_); } sub route_pc21 { my $self = shift; - broadcast_route($self, \&pc21, scalar @_, @_); + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc21, $line, scalar @_, @_); } sub route_pc24 { my $self = shift; - broadcast_route($self, \&pc24, 1, @_); + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc24, $line, 1, @_); } sub route_pc41 { my $self = shift; - broadcast_route($self, \&pc41, 1, @_); + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc41, $line, 1, @_); } sub route_pc50 { my $self = shift; - broadcast_route($self, \&pc50, 1, @_); + my $origin = shift; + my $line = shift; + broadcast_route($self, $origin, \&pc50, $line, 1, @_); } sub in_filter_route @@ -1872,7 +2420,7 @@ sub in_filter_route 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); + ($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; @@ -1881,12 +2429,14 @@ sub in_filter_route sub eph_dup { my $s = shift; + my $t = shift || $eph_restime; + my $r; # chop the end off $s =~ s/\^H\d\d?\^?\~?$//; - return 1 if exists $eph{$s}; - $eph{$s} = $main::systime; - return undef; + $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 @@ -1905,11 +2455,26 @@ sub eph_clean my ($key, $val); while (($key, $val) = each %eph) { - if ($main::systime - $val > 90) { + 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; +} + +sub run_cmd +{ + goto &DXCommandmode::run_cmd; +} 1; __END__