merge in changes from BAA
[spider.git] / perl / DXProt.pm
index c1fad11126edbc072d6681e71a70834ef5856f8e..04d62e4b55b02c3c23820cb3761c047b44af99e1 100644 (file)
@@ -22,22 +22,32 @@ use DXLog;
 use Spot;
 use DXProtout;
 use DXDebug;
+use Filter;
+use Local;
+
 use Carp;
 
 use strict;
-use vars qw($me $pc11_max_age $pc11_dup_age $pc23_dup_age %spotdup %wwvdup $last_hour %pings %rcmds %nodehops);
+use vars qw($me $pc11_max_age $pc11_dup_age $pc23_dup_age 
+                       %spotdup %wwvdup $last_hour %pings %rcmds 
+                       %nodehops @baddx $baddxfn $pc12_dup_age
+                       %anndup);
 
 $me = undef;                                   # the channel id for this cluster
 $pc11_max_age = 1*3600;                        # the maximum age for an incoming 'real-time' pc11
 $pc11_dup_age = 24*3600;               # the maximum time to keep the spot dup list for
 $pc23_dup_age = 24*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
 %spotdup = ();                             # the pc11 and 26 dup hash 
-%wwvdup = ();                              # the pc23 and 27 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";
 
 sub init
 {
@@ -67,6 +77,9 @@ sub init
                $wwvdup{$dupkey} = $_->[1];
        }
 
+       # load the baddx file
+       do "$baddxfn" if -e "$baddxfn";
+       print "$@\n" if $@;
 }
 
 #
@@ -97,6 +110,11 @@ sub start
        $self->{isolate} = $user->{isolate};
        $self->{consort} = $line;       # save the connection type
        $self->{here} = 1;
+
+       # get the filters
+       $self->{spotfilter} = Filter::read_in('spots', $call);
+       $self->{wwvfilter} = Filter::read_in('wwv', $call);
+       $self->{annfilter} = Filter::read_in('ann', $call);
        
        # set unbuffered
        $self->send_now('B',"0");
@@ -118,8 +136,11 @@ sub start
 sub normal
 {
        my ($self, $line) = @_;
-       my @field = split /[\^\~]/, $line;
+       my @field = split /\^/, $line;
+       pop @field if $field[-1] eq '~';
        
+#      print join(',', @field), "\n";
+                                               
        # ignore any lines that don't start with PC
        return if !$field[0] =~ /^PC/;
        
@@ -128,6 +149,14 @@ sub normal
        return unless $pcno;
        return if $pcno < 10 || $pcno > 51;
        
+       # local processing 1
+       my $pcr;
+       eval {
+               $pcr = Local::pcprot($self, $pcno, @field);
+       };
+#      dbg('local', "Local::pcprot error $@") if $@;
+       return if $pcr;
+       
  SWITCH: {
                if ($pcno == 10) {              # incoming talk
                        
@@ -140,7 +169,7 @@ sub normal
                                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->send("$call de $field[1]: $text") if $ref && $ref->{talk};
                        } else {
                                route($field[2], $line); # relay it on its way
                        }
@@ -154,9 +183,9 @@ sub normal
                        
                        # 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
-                       if (!$d || ($pcno == 11 && $d < $main::systime - $pc11_max_age)) {
-                               dbg('chan', "Spot ignored, invalid date or too old\n");
+                       # 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");
                                return;
                        }
 
@@ -176,22 +205,54 @@ sub normal
                        }
                        
                        $spotdup{$dupkey} = $d;
+
+                       # is it 'baddx'
+                       if (grep $field[2] eq $_, @baddx) {
+                               dbg('chan', "Bad DX spot, ignored");
+                               return;
+                       }
                        
-                       my $spot = Spot::add($freq, $field[2], $d, $text, $spotter);
+                       my @spot = Spot::add($freq, $field[2], $d, $text, $spotter, $field[7]);
+
+            #
+                       # @spot at this point contains:-
+            # freq, spotted call, time, text, spotter, spotted cc, spotters cc, orig node
+                       # then  spotted itu, spotted cq, spotters itu, spotters cq
+                       # you should be able to route on any of these
+            #
                        
-                       # send orf to the users
-                       if ($spot && $pcno == 11) {
-                               my $buf = Spot::formatb($field[1], $field[2], $d, $text, $spotter);
-                               broadcast_users("$buf\a\a");
-                       }
+                       # local processing 
+                       my $r;
+                       eval {
+                               $r = Local::spot($self, @spot);
+                       };
+#                      dbg('local', "Local::spot1 error $@") if $@;
+                       return if $r;
 
                        # DON'T be silly and send on PC26s!
                        return if $pcno == 26;
-                       
-                       last SWITCH;
+
+                       # send out the filtered spots
+                       send_dx_spot($self, $line, @spot) if @spot;
+                       return;
                }
                
                if ($pcno == 12) {              # announces
+                       # announce duplicate checking
+                       my $text = uc unpad($field[3]);
+                       my $dupkey = $field[1].$field[2].$text.$field[4].$field[6];
+                       if ($anndup{$dupkey}) {
+                               dbg('chan', "Duplicate Announce ignored\n");
+                               return;
+                       }
+                       $anndup{$dupkey} = $main::systime;
+                       
+                       # global ann filtering
+                       my ($filter, $hops) = Filter::it($self->{annfilter}, @field[1..6], $self->{call} ) if $self->{annfilter};
+                       if ($self->{annfilter} && !$filter) {
+                               dbg('chan', "Rejected by filter");
+                               return;
+                       }
                        
                        if ($field[2] eq '*' || $field[2] eq $main::mycall) {
                                
@@ -216,9 +277,9 @@ sub normal
                                $target = "All" if !$target;
                                
                                if (@list > 0) {
-                                       broadcast_list("$to$target de $field[1]: $text", @list);
+                                       broadcast_list("$to$target de $field[1]: $text", 'ann', undef, @list);
                                } else {
-                                       broadcast_users("$target de $field[1]: $text");
+                                       broadcast_users("$target de $field[1]: $text", 'ann', undef);
                                }
                                Log('ann', $target, $field[1], $text);
                                
@@ -244,12 +305,13 @@ sub normal
                if ($pcno == 16) {              # add a user
                        my $node = DXCluster->get_exact($field[1]); 
                        last SWITCH if !$node; # ignore if havn't seen a PC19 for this one yet
+                       last SWITCH unless $node->isa('DXNode');
                        my $i;
                        
                        
                        for ($i = 2; $i < $#field; $i++) {
-                               my ($call, $confmode, $here) = $field[$i] =~ /^(\S+) (-) (\d)/o;
-                               next if length $call < 3;
+                               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?)
@@ -332,6 +394,7 @@ sub normal
                        
                        # queue mail
                        DXMsg::queue_msg(0);
+
                        return;
                }
                
@@ -363,13 +426,27 @@ sub normal
                                dbg('chan', "Dup WWV Spot ignored\n");
                                return;
                        }
-                       
+                       if ($d > $main::systime + 900 || $field[2] < 0 || $field[2] > 23) {
+                               dbg('chan', "WWV Date ($field[1] $field[2]) out of range");
+                               return;
+                       }
                        $wwvdup{$dupkey} = $d;
-                       Geomag::update($field[1], $field[2], $sfi, $k, $i, @field[6..$#field]);
+                       $field[6] =~ s/-\d+$//o;            # remove spotter's ssid
+               
+                       my $wwv = Geomag::update($d, $field[2], $sfi, $k, $i, @field[6..$#field]);
+
+                       my $r;
+                       eval {
+                               $r = Local::wwv($self, $field[1], $field[2], $sfi, $k, $i, @field[6..$#field]);
+                       };
+#                      dbg('local', "Local::wwv2 error $@") if $@;
+                       return if $r;
 
                        # DON'T be silly and send on PC27s!
                        return if $pcno == 27;
-                       
+
+                       # broadcast to the eager users
+                       broadcast_users("WWV de $field[7] <$field[2]>:   SFI=$sfi, A=$k, K=$i, $field[6]", 'wwv', $wwv );
                        last SWITCH;
                }
                
@@ -380,8 +457,32 @@ sub normal
                        last SWITCH;
                }
                
-               if ($pcno == 25) {
-                       last SWITCH;
+               if ($pcno == 25) {      # merge request
+                       unless ($field[1] eq $main::mycall) {
+                               dbg('chan', "merge request to $field[1] from $field[2] ignored");
+                               return;
+                       }
+
+                       Log('DXProt', "Merge request for $field[3] spots and $field[4] WWV from $field[1]");
+                       
+                       # spots
+                       if ($field[3] > 0) {
+                               my @in = reverse Spot::search(1, undef, undef, 0, $field[3]-1);
+                               my $in;
+                               foreach $in (@in) {
+                                       $self->send(pc26(@{$in}[0..4], $in->[7]));
+                               }
+                       }
+
+                       # wwv
+                       if ($field[4] > 0) {
+                               my @in = reverse Geomag::search(0, $field[4], time, 1);
+                               my $in;
+                               foreach $in (@in) {
+                                       $self->send(pc27(@{$in}));
+                               }
+                       }
+                       return;
                }
                
                if (($pcno >= 28 && $pcno <= 33) || $pcno == 40 || $pcno == 42 || $pcno == 49) { # mail/file handling
@@ -392,8 +493,9 @@ sub normal
                if ($pcno == 34 || $pcno == 36) { # remote commands (incoming)
                        if ($field[1] eq $main::mycall) {
                                my $ref = DXUser->get_current($field[2]);
+                               my $cref = DXCluster->get($field[2]);
                                Log('rcmd', 'in', $ref->{priv}, $field[2], $field[3]);
-                               unless ($field[3] =~ /rcmd/i) {    # not allowed to relay RCMDS!
+                               unless ($field[3] =~ /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 @in = (DXCommandmode::run_cmd($self, $field[3]));
@@ -403,9 +505,11 @@ sub normal
                                                        Log('rcmd', 'out', $field[2], $_);
                                                }
                                                delete $self->{remotecmd};
+                                       } else {
+                                               $self->send(pc35($main::mycall, $field[2], "$main::mycall:sorry...!"));
                                        }
                                } else {
-                                       $self->send(pc35($main::mycall, $field[2], "$main::mycall:Tut tut tut...!"));
+                                       $self->send(pc35($main::mycall, $field[2], "$main::mycall:your attempt is logged, Tut tut tut...!"));
                                }
                        } else {
                                route($field[1], $line);
@@ -485,7 +589,7 @@ sub normal
                
                if ($pcno == 50) {              # keep alive/user list
                        my $ref = DXCluster->get_exact($field[1]);
-                       $ref->update_users($field[2]) if $ref;
+                       $ref->update_users($field[2]) if $ref;                  
                        last SWITCH;
                }
                
@@ -560,6 +664,10 @@ sub process
                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;
+               }
                $last_hour = $main::systime;
        }
 }
@@ -600,30 +708,77 @@ sub finish
 #
 # some active measures
 #
+sub send_dx_spot
+{
+       my $self = shift;
+       my $line = shift;
+       my @dxchan = DXChannel->get_all();
+       my $dxchan;
+       
+       # send it if it isn't the except list and isn't isolated and still has a hop count
+       # taking into account filtering and so on
+       foreach $dxchan (@dxchan) {
+               my $routeit;
+               my ($filter, $hops) = Filter::it($dxchan->{spotfilter}, @_, $self->{call} ) if $dxchan->{spotfilter};
+               if ($dxchan->is_ak1a) {
+                       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->{dx}) {
+                       my $buf = Spot::formatb($_[0], $_[1], $_[2], $_[3], $_[4]);
+                       $buf .= "\a\a" if $dxchan->{beep};
+                       if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') {
+                               $dxchan->send($buf) if !$hops || ($hops && $filter);
+                       } else {
+                               $dxchan->delay($buf) if !$hops || ($hops && $filter);
+                       }
+               }                                       
+       }
+}
 
 sub send_local_config
 {
        my $self = shift;
        my $n;
        my @nodes;
-       
+       my @localnodes;
+       my @remotenodes;
+               
        # send our nodes
        if ($self->{isolate}) {
-               @nodes = (DXCluster->get_exact($main::mycall));
+               @localnodes = (DXCluster->get_exact($main::mycall));
        } 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
                @nodes = DXNode::get_all();
-               @nodes = grep { $_->dxchan != $self } @nodes;
+               @nodes = grep { $_->{call} ne $main::mycall } @nodes;
+               @nodes = grep { $_->dxchan != $self } @nodes if @nodes;
+               @nodes = grep { !$_->dxchan->{isolate} } @nodes if @nodes;
+               @localnodes = grep { $_->dxchan->{call} eq $_->{call} } @nodes if @nodes;
+               unshift @localnodes, DXCluster->get_exact($main::mycall);
+               @remotenodes = grep { $_->dxchan->{call} ne $_->{call} } @nodes if @nodes;
        }
 
-       my @s = $me->pc19(@nodes);
+       my @s = $me->pc19(@localnodes, @remotenodes);
        for (@s) {
                my $routeit = adjust_hops($self, $_);
                $self->send($routeit) if $routeit;
        }
        
        # get all the users connected on the above nodes and send them out
-       foreach $n (@nodes) {
+       foreach $n (@localnodes, @remotenodes) {
                my @users = values %{$n->list};
                my @s = pc16($n, @users);
                for (@s) {
@@ -654,7 +809,8 @@ sub route
        }
 }
 
-# broadcast a message to all clusters [except those mentioned after buffer]
+# 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
@@ -670,29 +826,69 @@ sub broadcast_ak1a
        }
 }
 
+# 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 = get_all_ak1a();
+       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;
+               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 = get_all_users();
        my $dxchan;
+       my @out;
        
        foreach $dxchan (@dxchan) {
                next if grep $dxchan == $_, @except;
-               $s =~ s/\a//og if !$dxchan->{beep};
-               $dxchan->send($s);              # send it if it isn't the except list or hasn't a passout flag
+               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 (@_) {
-               $dxchan->send($s);              # send it 
+               my $filter = 1;
+               
+               if ($sort eq 'dx') {
+                   next unless $dxchan->{dx};
+                       ($filter) = Filter::it($dxchan->{spotfilter}, @{$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 'wx' && !$dxchan->{wx};
+
+               $s =~ s/\a//og unless $dxchan->{beep};
+
+               if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') {
+                       $dxchan->send($s);      
+               } else {
+                       $dxchan->delay($s);
+               }
        }
 }