add more code gradually
[spider.git] / perl / DXProt.pm
1 #!/usr/bin/perl
2 #
3 # This module impliments the protocal mode for a dx cluster
4 #
5 # Copyright (c) 1998 Dirk Koopman G1TLH
6 #
7 # $Id$
8
9
10 package DXProt;
11
12 @ISA = qw(DXChannel);
13
14 use DXUtil;
15 use DXChannel;
16 use DXUser;
17 use DXM;
18 use DXProtVars;
19 use DXCommandmode;
20 use DXLog;
21 use Spot;
22 use DXProtout;
23 use DXDebug;
24 use Filter;
25 use Local;
26 use DXDb;
27 use AnnTalk;
28 use Geomag;
29 use WCY;
30 use Time::HiRes qw(gettimeofday tv_interval);
31 use BadWords;
32 use DXHash;
33 use Route;
34 use Route::Node;
35 use Script;
36 use Thingy;
37
38 use strict;
39
40 use vars qw($VERSION $BRANCH);
41 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
42 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/,(0,0));
43 $main::build += $VERSION;
44 $main::branch += $BRANCH;
45
46 use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restime $eph_pc34_restime
47                         $last_hour $last10 %eph  %pings %rcmds $ann_to_talk
48                         $pingint $obscount %pc19list $chatdupeage
49                         %nodehops $baddx $badspotter $badnode $censorpc $rspfcheck
50                         $allowzero $decode_dk0wcy $send_opernam @checklist);
51
52 $pc11_max_age = 1*3600;                 # the maximum age for an incoming 'real-time' pc11
53 $pc23_max_age = 1*3600;                 # the maximum age for an incoming 'real-time' pc23
54
55 $last_hour = time;                              # last time I did an hourly periodic update
56 %pings = ();                    # outstanding ping requests outbound
57 %rcmds = ();                    # outstanding rcmd requests outbound
58 %nodehops = ();                 # node specific hop control
59 %pc19list = ();                                 # list of outstanding PC19s that haven't had PC16s on them
60
61 $censorpc = 1;                                  # Do a BadWords::check on text fields and reject things
62                                                                 # loads of 'bad things'
63 $baddx = new DXHash "baddx";
64 $badspotter = new DXHash "badspotter";
65 $badnode = new DXHash "badnode";
66 $last10 = $last_pc50 = time;
67 $ann_to_talk = 1;
68 $rspfcheck = 1;
69 $eph_restime = 180;
70 $eph_info_restime = 60*60;
71 $eph_pc34_restime = 30;
72 $pingint = 5*60;
73 $obscount = 2;
74 $chatdupeage = 20 * 60 * 60;
75
76 @checklist = 
77 (
78  [ qw(i c c m bp bc c) ],                       # pc10
79  [ qw(i f m d t m c c h) ],             # pc11
80  [ qw(i c bm m bm bm p h) ],            # pc12
81  [ qw(i c h) ],                                 # 
82  [ qw(i c h) ],                                 # 
83  [ qw(i c m h) ],                                       # 
84  undef ,                                                # pc16 has to be validated manually
85  [ qw(i c c h) ],                                       # pc17
86  [ qw(i m n) ],                                 # pc18
87  undef ,                                                # pc19 has to be validated manually
88  undef ,                                                # pc20 no validation
89  [ qw(i c m h) ],                                       # pc21
90  undef ,                                                # pc22 no validation
91  [ qw(i d n n n n m c c h) ],           # pc23
92  [ qw(i c p h) ],                                       # pc24
93  [ qw(i c c n n) ],                             # pc25
94  [ qw(i f m d t m c c bc) ],            # pc26
95  [ qw(i d n n n n m c c bc) ],  # pc27
96  [ qw(i c c m c d t p m bp n p bp bc) ], # pc28
97  [ qw(i c c n m) ],                             # pc29
98  [ qw(i c c n) ],                                       # pc30
99  [ qw(i c c n) ],                                       # pc31
100  [ qw(i c c n) ],                                       # pc32
101  [ qw(i c c n) ],                                       # pc33
102  [ qw(i c c m) ],                                       # pc34
103  [ qw(i c c m) ],                                       # pc35
104  [ qw(i c c m) ],                                       # pc36
105  [ qw(i c c n m) ],                             # pc37
106  undef,                                                 # pc38 not interested
107  [ qw(i c m) ],                                 # pc39
108  [ qw(i c c m p n) ],                           # pc40
109  [ qw(i c n m h) ],                             # pc41
110  [ qw(i c c n) ],                                       # pc42
111  undef,                                                 # pc43 don't handle it
112  [ qw(i c c n m m c) ],                 # pc44
113  [ qw(i c c n m) ],                             # pc45
114  [ qw(i c c n) ],                                       # pc46
115  undef,                                                 # pc47
116  undef,                                                 # pc48
117  [ qw(i c m h) ],                                       # pc49
118  [ qw(i c n h) ],                                       # pc50
119  [ qw(i c c n) ],                                       # pc51
120  undef,
121  undef,
122  undef,
123  undef,
124  undef,
125  undef,
126  undef,
127  undef,
128  undef,                                                 # pc60
129  undef,
130  undef,
131  undef,
132  undef,
133  undef,
134  undef,
135  undef,
136  undef,
137  undef,
138  undef,                                                 # pc70
139  undef,
140  undef,
141  [ qw(i d n n n n n n m m m c c h) ],   # pc73
142  undef,
143  undef,
144  undef,
145  undef,
146  undef,
147  undef,
148  undef,                                                 # pc80
149  undef,
150  undef,
151  undef,
152  [ qw(i c c c m) ],                             # pc84
153  [ qw(i c c c m) ],                             # pc85
154  undef,
155  undef,
156  undef,
157  undef,
158  [ qw(i c n) ],                                 # pc90
159 );
160
161 # use the entry in the check list to check the field list presented
162 # return OK if line NOT in check list (for now)
163 sub check
164 {
165         my $n = shift;
166         $n -= 10;
167         return 0 if $n < 0 || $n > @checklist; 
168         my $ref = $checklist[$n];
169         return 0 unless ref $ref;
170         
171         my $i;
172         for ($i = 1; $i < @$ref; $i++) {
173                 my ($blank, $act) = $$ref[$i] =~ /^(b?)(\w)$/;
174                 return 0 unless $act;
175                 next if $blank && $_[$i] =~ /^[ \*]$/;
176                 if ($act eq 'c') {
177                         return $i unless is_callsign($_[$i]);
178                 } elsif ($act eq 'i') {                 
179                         ;                                       # do nothing
180                 } elsif ($act eq 'm') {
181                         return $i unless is_pctext($_[$i]);
182                 } elsif ($act eq 'p') {
183                         return $i unless is_pcflag($_[$i]);
184                 } elsif ($act eq 'f') {
185                         return $i unless is_freq($_[$i]);
186                 } elsif ($act eq 'n') {
187                         return $i unless $_[$i] =~ /^[\d ]+$/;
188                 } elsif ($act eq 'h') {
189                         return $i unless $_[$i] =~ /^H\d\d?$/;
190                 } elsif ($act eq 'd') {
191                         return $i unless $_[$i] =~ /^\s*\d+-\w\w\w-[12][90]\d\d$/;
192                 } elsif ($act eq 't') {
193                         return $i unless $_[$i] =~ /^[012]\d[012345]\dZ$/;
194                 } 
195         }
196         return 0;
197 }
198
199 sub init
200 {
201         do "$main::data/hop_table.pl" if -e "$main::data/hop_table.pl";
202         confess $@ if $@;
203 }
204
205 #
206 # obtain a new connection this is derived from dxchannel
207 #
208
209 sub new 
210 {
211         my $self = DXChannel::alloc(@_);
212
213         # add this node to the table, the values get filled in later
214         my $pkg = shift;
215         my $call = shift;
216         $main::routeroot->add($call, '5000', Route::here(1)) if $call ne $main::mycall;
217
218         return $self;
219 }
220
221 # this is how a pc connection starts (for an incoming connection)
222 # issue a PC38 followed by a PC18, then wait for a PC20 (remembering
223 # all the crap that comes between).
224 sub start
225 {
226         my ($self, $line, $sort) = @_;
227         my $call = $self->{call};
228         my $user = $self->{user};
229
230         # log it
231         my $host = $self->{conn}->{peerhost} || "unknown";
232         Log('DXProt', "$call connected from $host");
233         
234         # remember type of connection
235         $self->{consort} = $line;
236         $self->{outbound} = $sort eq 'O';
237         my $priv = $user->priv;
238         $priv = $user->priv(1) unless $priv;
239         $self->{priv} = $priv;     # other clusters can always be 'normal' users
240         $self->{lang} = $user->lang || 'en';
241         $self->{isolate} = $user->{isolate};
242         $self->{consort} = $line;       # save the connection type
243         $self->{here} = 1;
244         $self->{width} = 80;
245
246         # sort out registration
247         $self->{registered} = 1;
248
249         # get the output filters
250         $self->{spotsfilter} = Filter::read_in('spots', $call, 0) || Filter::read_in('spots', 'node_default', 0);
251         $self->{wwvfilter} = Filter::read_in('wwv', $call, 0) || Filter::read_in('wwv', 'node_default', 0);
252         $self->{wcyfilter} = Filter::read_in('wcy', $call, 0) || Filter::read_in('wcy', 'node_default', 0);
253         $self->{annfilter} = Filter::read_in('ann', $call, 0) || Filter::read_in('ann', 'node_default', 0) ;
254         $self->{routefilter} = Filter::read_in('route', $call, 0) || Filter::read_in('route', 'node_default', 0) unless $self->{isolate} ;
255
256
257         # get the INPUT filters (these only pertain to Clusters)
258         $self->{inspotsfilter} = Filter::read_in('spots', $call, 1) || Filter::read_in('spots', 'node_default', 1);
259         $self->{inwwvfilter} = Filter::read_in('wwv', $call, 1) || Filter::read_in('wwv', 'node_default', 1);
260         $self->{inwcyfilter} = Filter::read_in('wcy', $call, 1) || Filter::read_in('wcy', 'node_default', 1);
261         $self->{inannfilter} = Filter::read_in('ann', $call, 1) || Filter::read_in('ann', 'node_default', 1);
262         $self->{inroutefilter} = Filter::read_in('route', $call, 1) || Filter::read_in('route', 'node_default', 1) unless $self->{isolate};
263         
264         # set unbuffered and no echo
265         $self->send_now('B',"0");
266         $self->send_now('E',"0");
267         $self->conn->echo(0) if $self->conn->can('echo');
268         
269         # ping neighbour node stuff
270         my $ping = $user->pingint;
271         $ping = $pingint unless defined $ping;
272         $self->{pingint} = $ping;
273         $self->{nopings} = $user->nopings || $obscount;
274         $self->{pingtime} = [ ];
275         $self->{pingave} = 999;
276         $self->{metric} ||= 100;
277         $self->{lastping} = $main::systime;
278
279         # send initialisation string
280         unless ($self->{outbound}) {
281                 $self->sendinit;
282         }
283         
284         $self->state('init');
285         $self->{pc50_t} = $main::systime;
286
287         # send info to all logged in thingies
288         $self->tell_login('loginn');
289
290         # run a script send the output to the debug file
291         my $script = new Script(lc $call) || new Script('node_default');
292         $script->run($self) if $script;
293 }
294
295 #
296 # send outgoing 'challenge'
297 #
298
299 sub sendinit
300 {
301         my $self = shift;
302         $self->send(pc18());
303 }
304
305
306 sub send
307 {
308         my $self = shift;
309         while (@_) {
310                 my $line = shift;
311                 $self->SUPER::send($line);
312         }
313 }
314
315 #
316 # This is the normal pcxx despatcher
317 #
318 sub normal
319 {
320         my ($self, $line) = @_;
321         my @field = split /\^/, $line;
322         return unless @field;
323         
324         pop @field if $field[-1] eq '~';
325         
326 #       print join(',', @field), "\n";
327                                                 
328         
329         # process PC frames, this will fail unless the frame starts PCnn
330         my ($pcno) = $field[0] =~ /^PC(\d\d)/; # just get the number
331         unless (defined $pcno && $pcno >= 10 && $pcno <= 99) {
332                 dbg("PCPROT: unknown protocol") if isdbg('chanerr');
333                 return;
334         }
335
336         # check for and dump bad protocol messages
337         my $n = check($pcno, @field);
338         if ($n) {
339                 dbg("PCPROT: bad field $n, dumped (" . parray($checklist[$pcno-10]) . ")") if isdbg('chanerr');
340                 return;
341         }
342
343         my $origin = $self->{call};
344         
345         no strict 'subs';
346         my $sub = "handle_$pcno";
347
348         if ($self->can($sub)) {
349                 $self->$sub($pcno, $line, $origin, @field);
350         } else {
351                 $self->handle_default($pcno, $line, $origin, @field);
352         }
353 }
354         
355 # incoming talk commands
356 sub handle_10
357 {
358         my $self = shift;
359         my $pcno = shift;
360         my $line = shift;
361         my $origin = shift;
362
363         # rsfp check
364         return if $rspfcheck and !$self->rspfcheck(0, $_[6], $_[1]);
365                         
366         # will we allow it at all?
367         if ($censorpc) {
368                 my @bad;
369                 if (@bad = BadWords::check($_[3])) {
370                         dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
371                         return;
372                 }
373         }
374
375         # is it for me or one of mine?
376         my ($from, $to, $via, $call, $dxchan);
377         $from = $_[1];
378         if ($_[5] gt ' ') {
379                 $via = $_[2];
380                 $to = $_[5];
381         } else {
382                 $to = $_[2];
383         }
384
385         # if this is a 'nodx' node then ignore it
386         if ($badnode->in($_[6]) || ($via && $badnode->in($via))) {
387                 dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
388                 return;
389         }
390
391         # if this is a 'bad spotter' user then ignore it
392         my $nossid = $from;
393         $nossid =~ s/-\d+$//;
394         if ($badspotter->in($nossid)) {
395                 dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
396                 return;
397         }
398
399         # if we are converting announces to talk is it a dup?
400         if ($ann_to_talk) {
401                 if (AnnTalk::is_talk_candidate($from, $_[3]) && AnnTalk::dup($from, $to, $_[3])) {
402                         dbg("DXPROT: Dupe talk from announce, dropped") if isdbg('chanerr');
403                         return;
404                 }
405         }
406
407         # it is here and logged on
408         $dxchan = DXChannel->get($main::myalias) if $to eq $main::mycall;
409         $dxchan = DXChannel->get($to) unless $dxchan;
410         if ($dxchan && $dxchan->is_user) {
411                 $_[3] =~ s/\%5E/^/g;
412                 $dxchan->talk($from, $to, $via, $_[3]);
413                 return;
414         }
415
416         # is it elsewhere, visible on the cluster via the to address?
417         # note: this discards the via unless the to address is on
418         # the via address
419         my ($ref, $vref);
420         if ($ref = Route::get($to)) {
421                 $vref = Route::Node::get($via) if $via;
422                 $vref = undef unless $vref && grep $to eq $_, $vref->users;
423                 $ref->dxchan->talk($from, $to, $vref ? $via : undef, $_[3], $_[6]);
424                 return;
425         }
426
427         # not visible here, send a message of condolence
428         $vref = undef;
429         $ref = Route::get($from);
430         $vref = $ref = Route::Node::get($_[6]) unless $ref; 
431         if ($ref) {
432                 $dxchan = $ref->dxchan;
433                 $dxchan->talk($main::mycall, $from, $vref ? $vref->call : undef, $dxchan->msg('talknh', $to) );
434         }
435 }
436
437 # DX Spot handling
438 sub handle_11
439 {
440         my $self = shift;
441         my $pcno = shift;
442         my $line = shift;
443         my $origin = shift;
444
445         # route 'foreign' pc26s 
446         if ($pcno == 26) {
447                 if ($_[7] ne $main::mycall) {
448                         $self->route($_[7], $line);
449                         return;
450                 }
451         }
452                         
453         # rsfp check
454         #                       return if $rspfcheck and !$self->rspfcheck(1, $_[7], $_[6]);
455
456         # if this is a 'nodx' node then ignore it
457         if ($badnode->in($_[7])) {
458                 dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
459                 return;
460         }
461                         
462         # if this is a 'bad spotter' user then ignore it
463         my $nossid = $_[6];
464         $nossid =~ s/-\d+$//;
465         if ($badspotter->in($nossid)) {
466                 dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
467                 return;
468         }
469                         
470         # convert the date to a unix date
471         my $d = cltounix($_[3], $_[4]);
472         # bang out (and don't pass on) if date is invalid or the spot is too old (or too young)
473         if (!$d || ($pcno == 11 && ($d < $main::systime - $pc11_max_age || $d > $main::systime + 900))) {
474                 dbg("PCPROT: Spot ignored, invalid date or out of range ($_[3] $_[4])\n") if isdbg('chanerr');
475                 return;
476         }
477
478         # is it 'baddx'
479         if ($baddx->in($_[2]) || BadWords::check($_[2]) || $_[2] =~ /COCK/) {
480                 dbg("PCPROT: Bad DX spot, ignored") if isdbg('chanerr');
481                 return;
482         }
483                         
484         # do some de-duping
485         $_[5] =~ s/^\s+//;                      # take any leading blanks off
486         $_[2] = unpad($_[2]);           # take off leading and trailing blanks from spotted callsign
487         if ($_[2] =~ /BUST\w*$/) {
488                 dbg("PCPROT: useless 'BUSTED' spot") if isdbg('chanerr');
489                 return;
490         }
491         if ($censorpc) {
492                 my @bad;
493                 if (@bad = BadWords::check($_[5])) {
494                         dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
495                         return;
496                 }
497         }
498
499
500         my @spot = Spot::prepare($_[1], $_[2], $d, $_[5], $_[6], $_[7]);
501         # global spot filtering on INPUT
502         if ($self->{inspotsfilter}) {
503                 my ($filter, $hops) = $self->{inspotsfilter}->it(@spot);
504                 unless ($filter) {
505                         dbg("PCPROT: Rejected by input spot filter") if isdbg('chanerr');
506                         return;
507                 }
508         }
509
510         # this goes after the input filtering, but before the add
511         # so that if it is input filtered, it isn't added to the dup
512         # list. This allows it to come in from a "legitimate" source
513         if (Spot::dup($_[1], $_[2], $d, $_[5], $_[6])) {
514                 dbg("PCPROT: Duplicate Spot ignored\n") if isdbg('chanerr');
515                 return;
516         }
517
518         # add it 
519         Spot::add(@spot);
520
521         #
522         # @spot at this point contains:-
523         # freq, spotted call, time, text, spotter, spotted cc, spotters cc, orig node
524         # then  spotted itu, spotted cq, spotters itu, spotters cq
525         # you should be able to route on any of these
526         #
527                         
528         # fix up qra locators of known users 
529         my $user = DXUser->get_current($spot[4]);
530         if ($user) {
531                 my $qra = $user->qra;
532                 unless ($qra && is_qra($qra)) {
533                         my $lat = $user->lat;
534                         my $long = $user->long;
535                         if (defined $lat && defined $long) {
536                                 $user->qra(DXBearing::lltoqra($lat, $long)); 
537                                 $user->put;
538                         }
539                 }
540
541                 # send a remote command to a distant cluster if it is visible and there is no
542                 # qra locator and we havn't done it for a month.
543
544                 unless ($user->qra) {
545                         my $node;
546                         my $to = $user->homenode;
547                         my $last = $user->lastoper || 0;
548                         if ($send_opernam && $to && $to ne $main::mycall && $main::systime > $last + $DXUser::lastoperinterval && ($node = Route::Node::get($to)) ) {
549                                 my $cmd = "forward/opernam $spot[4]";
550                                 # send the rcmd but we aren't interested in the replies...
551                                 my $dxchan = $node->dxchan;
552                                 if ($dxchan && $dxchan->is_clx) {
553                                         route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
554                                 } else {
555                                         route(undef, $to, pc34($main::mycall, $to, $cmd));
556                                 }
557                                 if ($to ne $_[7]) {
558                                         $to = $_[7];
559                                         $node = Route::Node::get($to);
560                                         if ($node) {
561                                                 $dxchan = $node->dxchan;
562                                                 if ($dxchan && $dxchan->is_clx) {
563                                                         route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
564                                                 } else {
565                                                         route(undef, $to, pc34($main::mycall, $to, $cmd));
566                                                 }
567                                         }
568                                 }
569                                 $user->lastoper($main::systime);
570                                 $user->put;
571                         }
572                 }
573         }
574                                 
575         # local processing 
576         my $r;
577         eval {
578                 $r = Local::spot($self, @spot);
579         };
580         #                       dbg("Local::spot1 error $@") if isdbg('local') if $@;
581         return if $r;
582
583         # DON'T be silly and send on PC26s!
584         return if $pcno == 26;
585
586         # send out the filtered spots
587         send_dx_spot($self, $line, @spot) if @spot;
588 }
589                 
590 # announces
591 sub handle_12
592 {
593         my $self = shift;
594         my $pcno = shift;
595         my $line = shift;
596         my $origin = shift;
597
598         #                       return if $rspfcheck and !$self->rspfcheck(1, $_[5], $_[1]);
599
600         # announce duplicate checking
601         $_[3] =~ s/^\s+//;                      # remove leading blanks
602
603         if ($censorpc) {
604                 my @bad;
605                 if (@bad = BadWords::check($_[3])) {
606                         dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
607                         return;
608                 }
609         }
610
611         # if this is a 'nodx' node then ignore it
612         if ($badnode->in($_[5])) {
613                 dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
614                 return;
615         }
616
617         # if this is a 'bad spotter' user then ignore it
618         my $nossid = $_[1];
619         $nossid =~ s/-\d+$//;
620         if ($badspotter->in($nossid)) {
621                 dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
622                 return;
623         }
624
625         my $dxchan;
626         
627         if ((($dxchan = DXChannel->get($_[2])) && $dxchan->is_user) || $_[4] =~ /^[\#\w.]+$/){
628                 $self->send_chat($line, @_[1..6]);
629         } elsif ($_[2] eq '*' || $_[2] eq $main::mycall) {
630
631                 # ignore something that looks like a chat line coming in with sysop
632                 # flag - this is a kludge...
633                 if ($_[3] =~ /^\#\d+ / && $_[4] eq '*') {
634                         dbg('PCPROT: Probable chat rewrite, dropped') if isdbg('chanerr');
635                         return;
636                 }
637
638                 # here's a bit of fun, convert incoming ann with a callsign in the first word
639                 # or one saying 'to <call>' to a talk if we can route to the recipient
640                 if ($ann_to_talk) {
641                         my $call = AnnTalk::is_talk_candidate($_[1], $_[3]);
642                         if ($call) {
643                                 my $ref = Route::get($call);
644                                 if ($ref) {
645                                         $dxchan = $ref->dxchan;
646                                         $dxchan->talk($_[1], $call, undef, $_[3], $_[5]) if $dxchan != $self;
647                                         return;
648                                 }
649                         }
650                 }
651         
652                 # send it
653                 $self->send_announce($line, @_[1..6]);
654         } else {
655                 $self->route($_[2], $line);
656         }
657 }
658                 
659 # incoming user         
660 sub handle_16
661 {
662         my $self = shift;
663         my $pcno = shift;
664         my $line = shift;
665         my $origin = shift;
666
667         if (eph_dup($line)) {
668                 dbg("PCPROT: dup PC16 detected") if isdbg('chanerr');
669                 return;
670         }
671
672         # general checks
673         my $ncall = $_[1];
674                         
675         # do I want users from this channel?
676         unless ($self->user->wantpc16) {
677                 dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr');
678                 return;
679         }
680         # is it me?
681         if ($ncall eq $main::mycall) {
682                 dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr');
683                 return;
684         }
685
686         # is it connected directly to me?
687         if ($ncall eq $self->{call}) {
688                 my @users;
689                 for (my $i = 2; $i < $#_; $i++) {
690                         my ($call, $conf, $here) = $_[$i] =~ /^(\S+) (\S) (\d)/o;
691                         next unless $call && $conf && defined $here && is_callsign($call);
692                         next if $call eq $main::mycall;
693                         push @users, "$here$call";
694                 }
695
696                 if (@users) {
697                         my $t = Thingy::Route->new_user_connection($ncall, $ncall, @users);
698                         $t->{_pcline} = [$line];
699                         $t->queue;
700                 } else {
701                         dbg("PCPROT: no valid users, dropped") if isdbg('chanerr');
702                 }
703         } else {
704                 dbg("PCPROT: non-local PC16, dropped") if isdbg('chanerr');
705         }
706 }
707                 
708 # remove a user
709 sub handle_17
710 {
711         my $self = shift;
712         my $pcno = shift;
713         my $line = shift;
714         my $origin = shift;
715         my $dxchan;
716         my $ncall = $_[2];
717         my $ucall = $_[1];
718
719         eph_del_regex("^PC16\\^$ncall.*$ucall");
720                         
721         # do I want users from this channel?
722         unless ($self->user->wantpc16) {
723                 dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr');
724                 return;
725         }
726
727         # ignore PC17 addressed from me
728         if ($ncall eq $main::mycall) {
729                 dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr');
730                 return;
731         }
732
733         # is it connected directly to me?
734         if ($ncall eq $self->{call}) {
735                 my $t = Thingy::Route->new_user_disconnection($ncall, $ncall, $ucall);
736                 $t->{_pcline} = [$line];
737                 $t->queue;
738         } else {
739                 dbg("PCPROT: non-local PC17, dropped") if isdbg('chanerr');
740         }
741 }
742                 
743 # link request
744 sub handle_18
745 {
746         my $self = shift;
747         my $pcno = shift;
748         my $line = shift;
749         my $origin = shift;
750         $self->state('init');   
751
752         # record the type and version offered
753         if ($_[1] =~ /DXSpider Version: (\d+\.\d+) Build: (\d+\.\d+)/) {
754                 $self->version($1);
755                 $self->user->version($1);
756                 $self->build($2);
757                 $self->user->build($2);
758                 unless ($self->is_spider) {
759                         $self->user->sort('S');
760                         $self->user->put;
761                         $self->sort('S');
762                 }
763         } else {
764                 $self->version(0.5000);
765                 $self->version("0.$_[2]") if $_[2] && $_[2] =~ /^\d+$/;
766                 $self->user->version($self->version);
767         }
768
769         if ($self->version >= 2.0 && $self->version < 5) {
770                 $self = bless $self, 'QXProt' unless $self->isa('QXProt');
771                 $self->sendinit;
772         } else {
773                 # first clear out any nodes on this dxchannel
774                 my $parent = Route::Node::get($self->{call});
775                 my @rout = $parent->del_nodes;
776                 $self->route_pc21($origin, $line, @rout, $parent) if @rout;
777                 $self->send_local_config();
778                 $self->send(pc20());
779         }
780 }
781                 
782 # incoming cluster list
783 sub handle_19
784 {
785         my $self = shift;
786         my $pcno = shift;
787         my $line = shift;
788         my $origin = shift;
789
790         if (eph_dup($line)) {
791                 dbg("PCPROT: dup PC19 detected") if isdbg('chanerr');
792                 return;
793         }
794
795         # parse the PC19
796         for (my $i = 1; $i < $#_-1; $i += 4) {
797                 my $here = $_[$i];
798                 my $call = uc $_[$i+1];
799                 my $conf = $_[$i+2];
800                 my $ver = $_[$i+3];
801                 next unless defined $here && defined $conf && is_callsign($call);
802
803                 eph_del_regex("^PC(?:21\\^$call|17\\^[^\\^]+\\^$call)");
804                                 
805                 # check for sane parameters
806                 next if $ver < 5000;    # only works with version 5 software
807                 next if $call eq $main::mycall;
808
809                 if ($call eq $self->{call}) {
810                         my $t = Thingy::Route->new_node_connection($main::mycall, $call, "$here$call");
811                         $t->{v} = $ver;
812                         $t->queue;
813                         last;
814                 }
815         }
816 }
817                 
818 # send local configuration
819 sub handle_20
820 {
821         my $self = shift;
822         my $pcno = shift;
823         my $line = shift;
824         my $origin = shift;
825         $self->send_local_config();
826         $self->send(pc22());
827         $self->state('normal');
828         $self->{lastping} = 0;
829 }
830                 
831 # delete a cluster from the list
832 sub handle_21
833 {
834         my $self = shift;
835         my $pcno = shift;
836         my $line = shift;
837         my $origin = shift;
838         my $call = uc $_[1];
839
840         eph_del_regex("^PC1[679].*$call");
841                         
842         # if I get a PC21 from the same callsign as self then treat it
843         # as a PC39: I have gone away
844         if ($call eq $self->call) {
845                 $self->disconnect(1);
846                 return;
847         }
848 }
849                 
850
851 sub handle_22
852 {
853         my $self = shift;
854         my $pcno = shift;
855         my $line = shift;
856         my $origin = shift;
857         $self->state('normal');
858         $self->{lastping} = 0;
859 }
860                                 
861 # WWV info
862 sub handle_23
863 {
864         my $self = shift;
865         my $pcno = shift;
866         my $line = shift;
867         my $origin = shift;
868                         
869         # route foreign' pc27s 
870         if ($pcno == 27) {
871                 if ($_[8] ne $main::mycall) {
872                         $self->route($_[8], $line);
873                         return;
874                 }
875         }
876
877         return if $rspfcheck and !$self->rspfcheck(1, $_[8], $_[7]);
878
879         # do some de-duping
880         my $d = cltounix($_[1], sprintf("%02d18Z", $_[2]));
881         my $sfi = unpad($_[3]);
882         my $k = unpad($_[4]);
883         my $i = unpad($_[5]);
884         my ($r) = $_[6] =~ /R=(\d+)/;
885         $r = 0 unless $r;
886         if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) {
887                 dbg("PCPROT: WWV Date ($_[1] $_[2]) out of range") if isdbg('chanerr');
888                 return;
889         }
890         if (Geomag::dup($d,$sfi,$k,$i,$_[6])) {
891                 dbg("PCPROT: Dup WWV Spot ignored\n") if isdbg('chanerr');
892                 return;
893         }
894         $_[7] =~ s/-\d+$//o;            # remove spotter's ssid
895                 
896         my $wwv = Geomag::update($d, $_[2], $sfi, $k, $i, @_[6..8], $r);
897
898         my $rep;
899         eval {
900                 $rep = Local::wwv($self, $_[1], $_[2], $sfi, $k, $i, @_[6..8], $r);
901         };
902         #                       dbg("Local::wwv2 error $@") if isdbg('local') if $@;
903         return if $rep;
904
905         # DON'T be silly and send on PC27s!
906         return if $pcno == 27;
907
908         # broadcast to the eager world
909         send_wwv_spot($self, $line, $d, $_[2], $sfi, $k, $i, @_[6..8]);
910 }
911                 
912 # set here status
913 sub handle_24
914 {
915         my $self = shift;
916         my $pcno = shift;
917         my $line = shift;
918         my $origin = shift;
919         my $call = uc $_[1];
920         my ($nref, $uref);
921         $nref = Route::Node::get($call);
922         $uref = Route::User::get($call);
923         return unless $nref || $uref; # if we don't know where they are, it's pointless sending it on
924                         
925         if (eph_dup($line)) {
926                 dbg("PCPROT: Dup PC24 ignored\n") if isdbg('chanerr');
927                 return;
928         }
929         
930         $nref->here($_[2]) if $nref;
931         $uref->here($_[2]) if $uref;
932         my $ref = $nref || $uref;
933         return unless $self->in_filter_route($ref);
934
935         $self->route_pc24($origin, $line, $ref, $_[3]);
936 }
937                 
938 # merge request
939 sub handle_25
940 {
941         my $self = shift;
942         my $pcno = shift;
943         my $line = shift;
944         my $origin = shift;
945         if ($_[1] ne $main::mycall) {
946                 $self->route($_[1], $line);
947                 return;
948         }
949         if ($_[2] eq $main::mycall) {
950                 dbg("PCPROT: Trying to merge to myself, ignored") if isdbg('chanerr');
951                 return;
952         }
953
954         Log('DXProt', "Merge request for $_[3] spots and $_[4] WWV from $_[2]");
955                         
956         # spots
957         if ($_[3] > 0) {
958                 my @in = reverse Spot::search(1, undef, undef, 0, $_[3]);
959                 my $in;
960                 foreach $in (@in) {
961                         $self->send(pc26(@{$in}[0..4], $_[2]));
962                 }
963         }
964
965         # wwv
966         if ($_[4] > 0) {
967                 my @in = reverse Geomag::search(0, $_[4], time, 1);
968                 my $in;
969                 foreach $in (@in) {
970                         $self->send(pc27(@{$in}[0..5], $_[2]));
971                 }
972         }
973 }
974
975 sub handle_26 {goto &handle_11}
976 sub handle_27 {goto &handle_23}
977
978 # mail/file handling
979 sub handle_28
980 {
981         my $self = shift;
982         my $pcno = shift;
983         my $line = shift;
984         my $origin = shift;
985         if ($_[1] eq $main::mycall) {
986                 no strict 'refs';
987                 my $sub = "DXMsg::handle_$pcno";
988                 &$sub($self, @_);
989         } else {
990                 $self->route($_[1], $line) unless $self->is_clx;
991         }
992 }
993
994 sub handle_29 {goto &handle_28}
995 sub handle_30 {goto &handle_28}
996 sub handle_31 {goto &handle_28}
997 sub handle_32 {goto &handle_28}
998 sub handle_33 {goto &handle_28}
999                 
1000 sub handle_34
1001 {
1002         my $self = shift;
1003         my $pcno = shift;
1004         my $line = shift;
1005         my $origin = shift;
1006         if (eph_dup($line, $eph_pc34_restime)) {
1007                 dbg("PCPROT: dupe PC34, ignored") if isdbg('chanerr');
1008         } else {
1009                 $self->process_rcmd($_[1], $_[2], $_[2], $_[3]);
1010         }
1011 }
1012                 
1013 # remote command replies
1014 sub handle_35
1015 {
1016         my $self = shift;
1017         my $pcno = shift;
1018         my $line = shift;
1019         my $origin = shift;
1020         eph_del_regex("^PC35\\^$_[2]\\^$_[1]\\^");
1021         $self->process_rcmd_reply($_[1], $_[2], $_[1], $_[3]);
1022 }
1023                 
1024 sub handle_36 {goto &handle_34}
1025
1026 # database stuff
1027 sub handle_37
1028 {
1029         my $self = shift;
1030         my $pcno = shift;
1031         my $line = shift;
1032         my $origin = shift;
1033         if ($_[1] eq $main::mycall) {
1034                 no strict 'refs';
1035                 my $sub = "DXDb::handle_$pcno";
1036                 &$sub($self, @_);
1037         } else {
1038                 $self->route($_[1], $line) unless $self->is_clx;
1039         }
1040 }
1041
1042 # node connected list from neighbour
1043 sub handle_38
1044 {
1045         my $self = shift;
1046         my $pcno = shift;
1047         my $line = shift;
1048         my $origin = shift;
1049 }
1050                 
1051 # incoming disconnect
1052 sub handle_39
1053 {
1054         my $self = shift;
1055         my $pcno = shift;
1056         my $line = shift;
1057         my $origin = shift;
1058         if ($_[1] eq $self->{call}) {
1059                 $self->disconnect(1);
1060         } else {
1061                 dbg("PCPROT: came in on wrong channel") if isdbg('chanerr');
1062         }
1063 }
1064
1065 sub handle_40 {goto &handle_28}
1066                 
1067 # user info
1068 sub handle_41
1069 {
1070         my $self = shift;
1071         my $pcno = shift;
1072         my $line = shift;
1073         my $origin = shift;
1074         my $call = $_[1];
1075
1076         my $l = $line;
1077         $l =~ s/[\x00-\x20\x7f-\xff]+//g; # remove all funny characters and spaces for dup checking
1078         if (eph_dup($l, $eph_info_restime)) {
1079                 dbg("PCPROT: dup PC41, ignored") if isdbg('chanerr');
1080                 return;
1081         }
1082                         
1083         # input filter if required
1084         #                       my $ref = Route::get($call) || Route->new($call);
1085         #                       return unless $self->in_filter_route($ref);
1086
1087         if ($_[3] eq $_[2] || $_[3] =~ /^\s*$/) {
1088                 dbg('PCPROT: invalid value') if isdbg('chanerr');
1089                 return;
1090         }
1091
1092         # add this station to the user database, if required
1093         my $user = DXUser->get_current($call);
1094         $user = DXUser->new($call) unless $user;
1095                         
1096         if ($_[2] == 1) {
1097                 $user->name($_[3]);
1098         } elsif ($_[2] == 2) {
1099                 $user->qth($_[3]);
1100         } elsif ($_[2] == 3) {
1101                 if (is_latlong($_[3])) {
1102                         my ($lat, $long) = DXBearing::stoll($_[3]);
1103                         $user->lat($lat);
1104                         $user->long($long);
1105                         $user->qra(DXBearing::lltoqra($lat, $long));
1106                 } else {
1107                         dbg('PCPROT: not a valid lat/long') if isdbg('chanerr');
1108                         return;
1109                 }
1110         } elsif ($_[2] == 4) {
1111                 $user->homenode($_[3]);
1112         } elsif ($_[2] == 5) {
1113                 if (is_qra(uc $_[3])) {
1114                         my ($lat, $long) = DXBearing::qratoll(uc $_[3]);
1115                         $user->lat($lat);
1116                         $user->long($long);
1117                         $user->qra(uc $_[3]);
1118                 } else {
1119                         dbg('PCPROT: not a valid QRA locator') if isdbg('chanerr');
1120                         return;
1121                 }
1122         }
1123         $user->lastoper($main::systime); # to cut down on excessive for/opers being generated
1124         $user->put;
1125
1126         unless ($self->{isolate}) {
1127                 DXChannel::broadcast_nodes($line, $self); # send it to everyone but me
1128         }
1129
1130         #  perhaps this IS what we want after all
1131         #                       $self->route_pc41($ref, $call, $_[2], $_[3], $_[4]);
1132 }
1133
1134 sub handle_42 {goto &handle_28}
1135
1136
1137 # database
1138 sub handle_44 {goto &handle_37}
1139 sub handle_45 {goto &handle_37}
1140 sub handle_46 {goto &handle_37}
1141 sub handle_47 {goto &handle_37}
1142 sub handle_48 {goto &handle_37}
1143                 
1144 # message and database
1145 sub handle_49
1146 {
1147         my $self = shift;
1148         my $pcno = shift;
1149         my $line = shift;
1150         my $origin = shift;
1151
1152         if (eph_dup($line)) {
1153                 dbg("PCPROT: Dup PC49 ignored\n") if isdbg('chanerr');
1154                 return;
1155         }
1156         
1157         if ($_[1] eq $main::mycall) {
1158                 DXMsg::handle_49($self, @_);
1159         } else {
1160                 $self->route($_[1], $line) unless $self->is_clx;
1161         }
1162 }
1163
1164 # keep alive/user list
1165 sub handle_50
1166 {
1167         my $self = shift;
1168         my $pcno = shift;
1169         my $line = shift;
1170         my $origin = shift;
1171
1172         my $call = $_[1];
1173         my $node = Route::Node::get($call);
1174         if ($node) {
1175                 return unless $node->call eq $self->{call};
1176                 $node->usercount($_[2]);
1177
1178                 # input filter if required
1179                 return unless $self->in_filter_route($node);
1180
1181                 $self->route_pc50($origin, $line, $node, $_[2], $_[3]) unless eph_dup($line);
1182         }
1183 }
1184                 
1185 # incoming ping requests/answers
1186 sub handle_51
1187 {
1188         my $self = shift;
1189         my $pcno = shift;
1190         my $line = shift;
1191         my $origin = shift;
1192         my $to = $_[1];
1193         my $from = $_[2];
1194         my $flag = $_[3];
1195
1196                         
1197         # is it for us?
1198         if ($to eq $main::mycall) {
1199                 if ($flag == 1) {
1200                         $self->send(pc51($from, $to, '0'));
1201                 } else {
1202                         # it's a reply, look in the ping list for this one
1203                         my $ref = $pings{$from};
1204                         if ($ref) {
1205                                 my $tochan =  DXChannel->get($from);
1206                                 while (@$ref) {
1207                                         my $r = shift @$ref;
1208                                         my $dxchan = DXChannel->get($r->{call});
1209                                         next unless $dxchan;
1210                                         my $t = tv_interval($r->{t}, [ gettimeofday ]);
1211                                         if ($dxchan->is_user) {
1212                                                 my $s = sprintf "%.2f", $t; 
1213                                                 my $ave = sprintf "%.2f", $tochan ? ($tochan->{pingave} || $t) : $t;
1214                                                 $dxchan->send($dxchan->msg('pingi', $from, $s, $ave))
1215                                         } elsif ($dxchan->is_node) {
1216                                                 if ($tochan) {
1217                                                         my $nopings = $tochan->user->nopings || $obscount;
1218                                                         push @{$tochan->{pingtime}}, $t;
1219                                                         shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
1220
1221                                                                 # cope with a missed ping, this means you must set the pingint large enough
1222                                                         if ($t > $tochan->{pingint}  && $t < 2 * $tochan->{pingint} ) {
1223                                                                 $t -= $tochan->{pingint};
1224                                                         }
1225
1226                                                                 # calc smoothed RTT a la TCP
1227                                                         if (@{$tochan->{pingtime}} == 1) {
1228                                                                 $tochan->{pingave} = $t;
1229                                                         } else {
1230                                                                 $tochan->{pingave} = $tochan->{pingave} + (($t - $tochan->{pingave}) / 6);
1231                                                         }
1232                                                         $tochan->{nopings} = $nopings; # pump up the timer
1233                                                 }
1234                                         } 
1235                                 }
1236                         }
1237                 }
1238         } else {
1239                 if (eph_dup($line)) {
1240                         dbg("PCPROT: dup PC51 detected") if isdbg('chanerr');
1241                         return;
1242                 }
1243                 # route down an appropriate thingy
1244                 $self->route($to, $line);
1245         }
1246 }
1247
1248 # dunno but route it
1249 sub handle_75
1250 {
1251         my $self = shift;
1252         my $pcno = shift;
1253         my $line = shift;
1254         my $origin = shift;
1255         my $call = $_[1];
1256         if ($call ne $main::mycall) {
1257                 $self->route($call, $line);
1258         }
1259 }
1260
1261 # WCY broadcasts
1262 sub handle_73
1263 {
1264         my $self = shift;
1265         my $pcno = shift;
1266         my $line = shift;
1267         my $origin = shift;
1268         my $call = $_[1];
1269                         
1270         # do some de-duping
1271         my $d = cltounix($call, sprintf("%02d18Z", $_[2]));
1272         if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) {
1273                 dbg("PCPROT: WCY Date ($call $_[2]) out of range") if isdbg('chanerr');
1274                 return;
1275         }
1276         @_ = map { unpad($_) } @_;
1277         if (WCY::dup($d)) {
1278                 dbg("PCPROT: Dup WCY Spot ignored\n") if isdbg('chanerr');
1279                 return;
1280         }
1281                 
1282         my $wcy = WCY::update($d, @_[2..12]);
1283
1284         my $rep;
1285         eval {
1286                 $rep = Local::wcy($self, @_[1..12]);
1287         };
1288         # dbg("Local::wcy error $@") if isdbg('local') if $@;
1289         return if $rep;
1290
1291         # broadcast to the eager world
1292         send_wcy_spot($self, $line, $d, @_[2..12]);
1293 }
1294
1295 # remote commands (incoming)
1296 sub handle_84
1297 {
1298         my $self = shift;
1299         my $pcno = shift;
1300         my $line = shift;
1301         my $origin = shift;
1302         $self->process_rcmd($_[1], $_[2], $_[3], $_[4]);
1303 }
1304
1305 # remote command replies
1306 sub handle_85
1307 {
1308         my $self = shift;
1309         my $pcno = shift;
1310         my $line = shift;
1311         my $origin = shift;
1312         $self->process_rcmd_reply($_[1], $_[2], $_[3], $_[4]);
1313 }
1314
1315 # if get here then rebroadcast the thing with its Hop count decremented (if
1316 # there is one). If it has a hop count and it decrements to zero then don't
1317 # rebroadcast it.
1318 #
1319 # NOTE - don't arrive here UNLESS YOU WANT this lump of protocol to be
1320 #        REBROADCAST!!!!
1321 #
1322
1323 sub handle_default
1324 {
1325         my $self = shift;
1326         my $pcno = shift;
1327         my $line = shift;
1328         my $origin = shift;
1329
1330         if (eph_dup($line)) {
1331                 dbg("PCPROT: Ephemeral dup, dropped") if isdbg('chanerr');
1332         } else {
1333                 unless ($self->{isolate}) {
1334                         DXChannel::broadcast_nodes($line, $self) if $line =~ /\^H\d+\^?~?$/; # send it to everyone but me
1335                 }
1336         }
1337 }
1338
1339 #
1340 # This is called from inside the main cluster processing loop and is used
1341 # for despatching commands that are doing some long processing job
1342 #
1343 sub process
1344 {
1345         my $t = time;
1346         my @dxchan = DXChannel->get_all();
1347         my $dxchan;
1348         my $pc50s;
1349         
1350         # send out a pc50 on EVERY channel all at once
1351         if ($t >= $last_pc50 + $DXProt::pc50_interval) {
1352                 $pc50s = pc50($main::me, scalar DXChannel::get_all_users);
1353                 eph_dup($pc50s);
1354                 $last_pc50 = $t;
1355         }
1356
1357         foreach $dxchan (@dxchan) {
1358                 next unless $dxchan->is_node();
1359                 next if $dxchan == $main::me;
1360
1361                 # send the pc50
1362                 $dxchan->send($pc50s) if $pc50s;
1363                 
1364                 # send a ping out on this channel
1365                 if ($dxchan->{pingint} && $t >= $dxchan->{pingint} + $dxchan->{lastping}) {
1366                         if ($dxchan->{nopings} <= 0) {
1367                                 $dxchan->disconnect;
1368                         } else {
1369                                 addping($main::mycall, $dxchan->call);
1370                                 $dxchan->{nopings} -= 1;
1371                                 $dxchan->{lastping} = $t;
1372                         }
1373                 }
1374         }
1375
1376         # every ten seconds
1377         if ($t - $last10 >= 10) {       
1378                 # clean out ephemera 
1379
1380                 eph_clean();
1381
1382                 $last10 = $t;
1383         }
1384         
1385         if ($main::systime - 3600 > $last_hour) {
1386                 $last_hour = $main::systime;
1387         }
1388 }
1389
1390 #
1391 # finish up a pc context
1392 #
1393
1394 #
1395 # some active measures
1396 #
1397
1398
1399 sub send_dx_spot
1400 {
1401         my $self = shift;
1402         my $line = shift;
1403         my @dxchan = DXChannel->get_all();
1404         my $dxchan;
1405         
1406         # send it if it isn't the except list and isn't isolated and still has a hop count
1407         # taking into account filtering and so on
1408         foreach $dxchan (@dxchan) {
1409                 next if $dxchan == $main::me;
1410                 next if $dxchan == $self && $self->is_node;
1411                 $dxchan->dx_spot($line, $self->{isolate}, @_, $self->{call});
1412         }
1413 }
1414
1415 sub dx_spot
1416 {
1417         my $self = shift;
1418         my $line = shift;
1419         my $isolate = shift;
1420         my ($filter, $hops);
1421
1422         if ($self->{spotsfilter}) {
1423                 ($filter, $hops) = $self->{spotsfilter}->it(@_);
1424                 return unless $filter;
1425         }
1426         send_prot_line($self, $filter, $hops, $isolate, $line);
1427 }
1428
1429 sub send_prot_line
1430 {
1431         my ($self, $filter, $hops, $isolate, $line) = @_;
1432         my $routeit;
1433
1434
1435         if ($hops) {
1436                 $routeit = $line;
1437                 $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
1438         } else {
1439                 $routeit = adjust_hops($self, $line);  # adjust its hop count by node name
1440                 return unless $routeit;
1441         }
1442         if ($filter) {
1443                 $self->send($routeit);
1444         } else {
1445                 $self->send($routeit) unless $self->{isolate} || $isolate;
1446         }
1447 }
1448
1449
1450 sub send_wwv_spot
1451 {
1452         my $self = shift;
1453         my $line = shift;
1454         my @dxchan = DXChannel->get_all();
1455         my $dxchan;
1456         my ($wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
1457         my @dxcc = Prefix::extract($_[6]);
1458         if (@dxcc > 0) {
1459                 $wwv_dxcc = $dxcc[1]->dxcc;
1460                 $wwv_itu = $dxcc[1]->itu;
1461                 $wwv_cq = $dxcc[1]->cq;                                         
1462         }
1463         @dxcc = Prefix::extract($_[7]);
1464         if (@dxcc > 0) {
1465                 $org_dxcc = $dxcc[1]->dxcc;
1466                 $org_itu = $dxcc[1]->itu;
1467                 $org_cq = $dxcc[1]->cq;                                         
1468         }
1469         
1470         # send it if it isn't the except list and isn't isolated and still has a hop count
1471         # taking into account filtering and so on
1472         foreach $dxchan (@dxchan) {
1473                 next if $dxchan == $main::me;
1474                 next if $dxchan == $self && $self->is_node;
1475                 my $routeit;
1476                 my ($filter, $hops);
1477
1478                 $dxchan->wwv($line, $self->{isolate}, @_, $self->{call}, $wwv_dxcc, $wwv_itu, $wwv_cq, $org_dxcc, $org_itu, $org_cq);
1479         }
1480 }
1481
1482 sub wwv
1483 {
1484         my $self = shift;
1485         my $line = shift;
1486         my $isolate = shift;
1487         my ($filter, $hops);
1488         
1489         if ($self->{wwvfilter}) {
1490                 ($filter, $hops) = $self->{wwvfilter}->it(@_);
1491                 return unless $filter;
1492         }
1493         send_prot_line($self, $filter, $hops, $isolate, $line)
1494 }
1495
1496 sub send_wcy_spot
1497 {
1498         my $self = shift;
1499         my $line = shift;
1500         my @dxchan = DXChannel->get_all();
1501         my $dxchan;
1502         my ($wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
1503         my @dxcc = Prefix::extract($_[10]);
1504         if (@dxcc > 0) {
1505                 $wcy_dxcc = $dxcc[1]->dxcc;
1506                 $wcy_itu = $dxcc[1]->itu;
1507                 $wcy_cq = $dxcc[1]->cq;                                         
1508         }
1509         @dxcc = Prefix::extract($_[11]);
1510         if (@dxcc > 0) {
1511                 $org_dxcc = $dxcc[1]->dxcc;
1512                 $org_itu = $dxcc[1]->itu;
1513                 $org_cq = $dxcc[1]->cq;                                         
1514         }
1515         
1516         # send it if it isn't the except list and isn't isolated and still has a hop count
1517         # taking into account filtering and so on
1518         foreach $dxchan (@dxchan) {
1519                 next if $dxchan == $main::me;
1520                 next if $dxchan == $self;
1521
1522                 $dxchan->wcy($line, $self->{isolate}, @_, $self->{call}, $wcy_dxcc, $wcy_itu, $wcy_cq, $org_dxcc, $org_itu, $org_cq);
1523         }
1524 }
1525
1526 sub wcy
1527 {
1528         my $self = shift;
1529         my $line = shift;
1530         my $isolate = shift;
1531         my ($filter, $hops);
1532
1533         if ($self->{wcyfilter}) {
1534                 ($filter, $hops) = $self->{wcyfilter}->it(@_);
1535                 return unless $filter;
1536         }
1537         send_prot_line($self, $filter, $hops, $isolate, $line) if $self->is_clx || $self->is_spider || $self->is_dxnet;
1538 }
1539
1540 # send an announce
1541 sub send_announce
1542 {
1543         my $self = shift;
1544         my $line = shift;
1545         my @dxchan = DXChannel->get_all();
1546         my $dxchan;
1547         my $target;
1548         my $to = 'To ';
1549         my $text = unpad($_[2]);
1550                                 
1551         if ($_[3] eq '*') {     # sysops
1552                 $target = "SYSOP";
1553         } elsif ($_[3] gt ' ') { # speciality list handling
1554                 my ($name) = split /\./, $_[3]; 
1555                 $target = "$name"; # put the rest in later (if bothered) 
1556         } 
1557         
1558         if ($_[5] eq '1') {
1559                 $target = "WX"; 
1560                 $to = '';
1561         }
1562         $target = "ALL" if !$target;
1563
1564
1565         # obtain country codes etc 
1566         my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
1567         my ($ann_state, $org_state) = ("", "");
1568         my @dxcc = Prefix::extract($_[0]);
1569         if (@dxcc > 0) {
1570                 $ann_dxcc = $dxcc[1]->dxcc;
1571                 $ann_itu = $dxcc[1]->itu;
1572                 $ann_cq = $dxcc[1]->cq;                                         
1573                 $ann_state = $dxcc[1]->state;
1574         }
1575         @dxcc = Prefix::extract($_[4]);
1576         if (@dxcc > 0) {
1577                 $org_dxcc = $dxcc[1]->dxcc;
1578                 $org_itu = $dxcc[1]->itu;
1579                 $org_cq = $dxcc[1]->cq;                                         
1580                 $org_state = $dxcc[1]->state;
1581         }
1582
1583         if ($self->{inannfilter}) {
1584                 my ($filter, $hops) = 
1585                         $self->{inannfilter}->it(@_, $self->{call}, 
1586                                                                          $ann_dxcc, $ann_itu, $ann_cq,
1587                                                                          $org_dxcc, $org_itu, $org_cq, $ann_state, $org_state);
1588                 unless ($filter) {
1589                         dbg("PCPROT: Rejected by input announce filter") if isdbg('chanerr');
1590                         return;
1591                 }
1592         }
1593
1594         if (AnnTalk::dup($_[0], $_[1], $_[2])) {
1595                 dbg("PCPROT: Duplicate Announce ignored") if isdbg('chanerr');
1596                 return;
1597         }
1598
1599         Log('ann', $target, $_[0], $text);
1600
1601         # send it if it isn't the except list and isn't isolated and still has a hop count
1602         # taking into account filtering and so on
1603         foreach $dxchan (@dxchan) {
1604                 next if $dxchan == $main::me;
1605                 next if $dxchan == $self && $self->is_node;
1606                 $dxchan->announce($line, $self->{isolate}, $to, $target, $text, @_, $self->{call}, $ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq);
1607         }
1608 }
1609
1610 my $msgid = 0;
1611
1612 sub nextchatmsgid
1613 {
1614         $msgid++;
1615         $msgid = 1 if $msgid > 999;
1616         return $msgid;
1617 }
1618
1619 # send a chat line
1620 sub send_chat
1621 {
1622         my $self = shift;
1623         my $line = shift;
1624         my @dxchan = DXChannel->get_all();
1625         my $dxchan;
1626         my $target = $_[3];
1627         my $text = unpad($_[2]);
1628         my $ak1a_line;
1629                                 
1630         # munge the group and recast the line if required
1631         if ($target =~ s/\.LST$//) {
1632                 $ak1a_line = $line;
1633         }
1634         
1635         # obtain country codes etc 
1636         my ($ann_dxcc, $ann_itu, $ann_cq, $org_dxcc, $org_itu, $org_cq) = (0..0);
1637         my ($ann_state, $org_state) = ("", "");
1638         my @dxcc = Prefix::extract($_[0]);
1639         if (@dxcc > 0) {
1640                 $ann_dxcc = $dxcc[1]->dxcc;
1641                 $ann_itu = $dxcc[1]->itu;
1642                 $ann_cq = $dxcc[1]->cq;                                         
1643                 $ann_state = $dxcc[1]->state;
1644         }
1645         @dxcc = Prefix::extract($_[4]);
1646         if (@dxcc > 0) {
1647                 $org_dxcc = $dxcc[1]->dxcc;
1648                 $org_itu = $dxcc[1]->itu;
1649                 $org_cq = $dxcc[1]->cq;                                         
1650                 $org_state = $dxcc[1]->state;
1651         }
1652
1653         if ($self->{inannfilter}) {
1654                 my ($filter, $hops) = 
1655                         $self->{inannfilter}->it(@_, $self->{call}, 
1656                                                                          $ann_dxcc, $ann_itu, $ann_cq,
1657                                                                          $org_dxcc, $org_itu, $org_cq, $ann_state, $org_state);
1658                 unless ($filter) {
1659                         dbg("PCPROT: Rejected by input announce filter") if isdbg('chanerr');
1660                         return;
1661                 }
1662         }
1663
1664         if (AnnTalk::dup($_[0], $_[1], $_[2], $chatdupeage)) {
1665                 dbg("PCPROT: Duplicate Announce ignored") if isdbg('chanerr');
1666                 return;
1667         }
1668
1669
1670         Log('chat', $target, $_[0], $text);
1671
1672         # send it if it isn't the except list and isn't isolated and still has a hop count
1673         # taking into account filtering and so on
1674         foreach $dxchan (@dxchan) {
1675                 my $is_ak1a = $dxchan->is_ak1a;
1676                 
1677                 if ($dxchan->is_node) {
1678                         next if $dxchan == $main::me;
1679                         next if $dxchan == $self;
1680                         next unless $dxchan->is_spider || $is_ak1a;
1681                         next if $target eq 'LOCAL';
1682                         if (!$ak1a_line && $is_ak1a) {
1683                                 $ak1a_line = DXProt::pc12($_[0], $text, $_[1], "$target.LST");
1684                         }
1685                 }
1686                 
1687                 $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);
1688         }
1689 }
1690
1691 sub announce
1692 {
1693         my $self = shift;
1694         my $line = shift;
1695         my $isolate = shift;
1696         my $to = shift;
1697         my $target = shift;
1698         my $text = shift;
1699         my ($filter, $hops);
1700
1701         if ($self->{annfilter}) {
1702                 ($filter, $hops) = $self->{annfilter}->it(@_);
1703                 return unless $filter;
1704         }
1705         send_prot_line($self, $filter, $hops, $isolate, $line) unless $_[1] eq $main::mycall;
1706 }
1707
1708 sub chat
1709 {
1710         goto &announce;
1711 }
1712
1713
1714 sub send_local_config
1715 {
1716         my $self = shift;
1717         my $node;
1718         my @nodes;
1719         my @localnodes;
1720         my @remotenodes;
1721
1722         dbg('DXProt::send_local_config') if isdbg('trace');
1723         
1724         # send our nodes
1725         if ($self->{isolate}) {
1726                 @localnodes = ( $main::routeroot );
1727                 $self->send_route($main::mycall, \&pc19, 1, $main::routeroot);
1728         } else {
1729                 # create a list of all the nodes that are not connected to this connection
1730                 # and are not themselves isolated, this to make sure that isolated nodes
1731         # don't appear outside of this node
1732
1733                 # send locally connected nodes
1734                 my @dxchan = grep { $_->call ne $main::mycall && $_ != $self && !$_->{isolate} } DXChannel::get_all_nodes();
1735                 @localnodes = map { my $r = Route::Node::get($_->{call}); $r ? $r : () } @dxchan if @dxchan;
1736                 $self->send_route($main::mycall, \&pc19, scalar(@localnodes)+1, $main::routeroot, @localnodes);
1737
1738                 my $node;
1739                 my @rawintcalls = map { $_->nodes } @localnodes if @localnodes;
1740                 my @intcalls;
1741                 for $node (@rawintcalls) {
1742                         push @intcalls, $node unless grep $node eq $_, @intcalls; 
1743                 }
1744                 my $ref = Route::Node::get($self->{call});
1745                 my @rnodes = $ref->nodes;
1746                 for $node (@intcalls) {
1747                         push @remotenodes, Route::Node::get($node) unless grep $node eq $_, @rnodes, @remotenodes;
1748                 }
1749                 $self->send_route($main::mycall, \&pc19, scalar(@remotenodes), @remotenodes);
1750         }
1751         
1752         # get all the users connected on the above nodes and send them out
1753         foreach $node ($main::routeroot, @localnodes, @remotenodes) {
1754                 if ($node) {
1755                         my @rout = map {my $r = Route::User::get($_); $r ? ($r) : ()} $node->users;
1756                         $self->send_route($main::mycall, \&pc16, 1, $node, @rout) if @rout && $self->user->wantsendpc16;
1757                 } else {
1758                         dbg("sent a null value") if isdbg('chanerr');
1759                 }
1760         }
1761 }
1762
1763 #
1764 # route a message down an appropriate interface for a callsign
1765 #
1766 # is called route(to, pcline);
1767 #
1768 sub route
1769 {
1770         my ($self, $call, $line) = @_;
1771
1772         if (ref $self && $call eq $self->{call}) {
1773                 dbg("PCPROT: Trying to route back to source, dropped") if isdbg('chanerr');
1774                 return;
1775         }
1776
1777         # always send it down the local interface if available
1778         my $dxchan = DXChannel->get($call);
1779         unless ($dxchan) {
1780                 my $cl = Route::get($call);
1781                 $dxchan = $cl->dxchan if $cl;
1782                 if (ref $dxchan) {
1783                         if (ref $self && $dxchan eq $self) {
1784                                 dbg("PCPROT: Trying to route back to source, dropped") if isdbg('chanerr');
1785                                 return;
1786                         }
1787                 }
1788         }
1789         if ($dxchan) {
1790                 my $routeit = adjust_hops($dxchan, $line);   # adjust its hop count by node name
1791                 if ($routeit) {
1792                         $dxchan->send($routeit) unless $dxchan == $main::me;
1793                 }
1794         } else {
1795                 dbg("PCPROT: No route available, dropped") if isdbg('chanerr');
1796         }
1797 }
1798
1799 #
1800 # obtain the hops from the list for this callsign and pc no 
1801 #
1802
1803 sub get_hops
1804 {
1805         my $pcno = shift;
1806         my $hops = $DXProt::hopcount{$pcno};
1807         $hops = $DXProt::def_hopcount if !$hops;
1808         return "H$hops";       
1809 }
1810
1811
1812 # adjust the hop count on a per node basis using the user loadable 
1813 # hop table if available or else decrement an existing one
1814 #
1815
1816 sub adjust_hops
1817 {
1818         my $self = shift;
1819         my $s = shift;
1820         my $call = $self->{call};
1821         my $hops;
1822         
1823         if (($hops) = $s =~ /\^H(\d+)\^~?$/o) {
1824                 my ($pcno) = $s =~ /^PC(\d\d)/o;
1825                 confess "$call called adjust_hops with '$s'" unless $pcno;
1826                 my $ref = $nodehops{$call} if %nodehops;
1827                 if ($ref) {
1828                         my $newhops = $ref->{$pcno};
1829                         return "" if defined $newhops && $newhops == 0;
1830                         $newhops = $ref->{default} unless $newhops;
1831                         return "" if defined $newhops && $newhops == 0;
1832                         $newhops = $hops if !$newhops;
1833                         $s =~ s/\^H(\d+)(\^~?)$/\^H$newhops$2/ if $newhops;
1834                 } else {
1835                         # simply decrement it
1836                         $hops--;
1837                         return "" if !$hops;
1838                         $s =~ s/\^H(\d+)(\^~?)$/\^H$hops$2/ if $hops;
1839                 }
1840         }
1841         return $s;
1842 }
1843
1844
1845 # load hop tables
1846 #
1847 sub load_hops
1848 {
1849         my $self = shift;
1850         return $self->msg('lh1') unless -e "$main::data/hop_table.pl";
1851         do "$main::data/hop_table.pl";
1852         return $@ if $@;
1853         return ();
1854 }
1855
1856
1857 # add a ping request to the ping queues
1858 sub addping
1859 {
1860         my ($from, $to) = @_;
1861         my $ref = $pings{$to} || [];
1862         my $r = {};
1863         $r->{call} = $from;
1864         $r->{t} = [ gettimeofday ];
1865         route(undef, $to, pc51($to, $main::mycall, 1));
1866         push @$ref, $r;
1867         $pings{$to} = $ref;
1868 }
1869
1870 sub process_rcmd
1871 {
1872         my ($self, $tonode, $fromnode, $user, $cmd) = @_;
1873         if ($tonode eq $main::mycall) {
1874                 my $ref = DXUser->get_current($fromnode);
1875                 my $cref = Route::Node::get($fromnode);
1876                 Log('rcmd', 'in', $ref->{priv}, $fromnode, $cmd);
1877                 if ($cmd !~ /^\s*rcmd/i && $cref && $ref && $cref->call eq $ref->homenode) { # not allowed to relay RCMDS!
1878                         if ($ref->{priv}) {             # you have to have SOME privilege, the commands have further filtering
1879                                 $self->{remotecmd} = 1; # for the benefit of any command that needs to know
1880                                 my $oldpriv = $self->{priv};
1881                                 $self->{priv} = $ref->{priv}; # assume the user's privilege level
1882                                 my @in = (DXCommandmode::run_cmd($self, $cmd));
1883                                 $self->{priv} = $oldpriv;
1884                                 $self->send_rcmd_reply($main::mycall, $fromnode, $user, @in);
1885                                 delete $self->{remotecmd};
1886                         } else {
1887                                 $self->send_rcmd_reply($main::mycall, $fromnode, $user, "sorry...!");
1888                         }
1889                 } else {
1890                         $self->send_rcmd_reply($main::mycall, $fromnode, $user, "your attempt is logged, Tut tut tut...!");
1891                 }
1892         } else {
1893                 my $ref = DXUser->get_current($tonode);
1894                 if ($ref && $ref->is_clx) {
1895                         $self->route($tonode, pc84($fromnode, $tonode, $user, $cmd));
1896                 } else {
1897                         $self->route($tonode, pc34($fromnode, $tonode, $cmd));
1898                 }
1899         }
1900 }
1901
1902 sub process_rcmd_reply
1903 {
1904         my ($self, $tonode, $fromnode, $user, $line) = @_;
1905         if ($tonode eq $main::mycall) {
1906                 my $s = $rcmds{$fromnode};
1907                 if ($s) {
1908                         my $dxchan = DXChannel->get($s->{call});
1909                         my $ref = $user eq $tonode ? $dxchan : (DXChannel->get($user) || $dxchan);
1910                         $ref->send($line) if $ref;
1911                         delete $rcmds{$fromnode} if !$dxchan;
1912                 } else {
1913                         # send unsolicited ones to the sysop
1914                         my $dxchan = DXChannel->get($main::myalias);
1915                         $dxchan->send($line) if $dxchan;
1916                 }
1917         } else {
1918                 my $ref = DXUser->get_current($tonode);
1919                 if ($ref && $ref->is_clx) {
1920                         $self->route($tonode, pc85($fromnode, $tonode, $user, $line));
1921                 } else {
1922                         $self->route($tonode, pc35($fromnode, $tonode, $line));
1923                 }
1924         }
1925 }
1926
1927 sub send_rcmd_reply
1928 {
1929         my $self = shift;
1930         my $tonode = shift;
1931         my $fromnode = shift;
1932         my $user = shift;
1933         while (@_) {
1934                 my $line = shift;
1935                 $line =~ s/\s*$//;
1936                 Log('rcmd', 'out', $fromnode, $line);
1937                 if ($self->is_clx) {
1938                         $self->send(pc85($main::mycall, $fromnode, $user, "$main::mycall:$line"));
1939                 } else {
1940                         $self->send(pc35($main::mycall, $fromnode, "$main::mycall:$line"));
1941                 }
1942         }
1943 }
1944
1945 # add a rcmd request to the rcmd queues
1946 sub addrcmd
1947 {
1948         my ($self, $to, $cmd) = @_;
1949
1950         my $r = {};
1951         $r->{call} = $self->{call};
1952         $r->{t} = $main::systime;
1953         $r->{cmd} = $cmd;
1954         $rcmds{$to} = $r;
1955         
1956         my $ref = Route::Node::get($to);
1957         my $dxchan = $ref->dxchan;
1958         if ($dxchan && $dxchan->is_clx) {
1959                 route(undef, $to, pc84($main::mycall, $to, $self->{call}, $cmd));
1960         } else {
1961                 route(undef, $to, pc34($main::mycall, $to, $cmd));
1962         }
1963 }
1964
1965 sub disconnect
1966 {
1967         my $self = shift;
1968         my $pc39flag = shift;
1969         my $call = $self->call;
1970
1971         return if $self->{disconnecting}++;
1972         
1973         unless ($pc39flag && $pc39flag == 1) {
1974                 $self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op")));
1975         }
1976
1977         # get rid of any PC16/17/19
1978         eph_del_regex("^PC1[679]*$call");
1979
1980         # do routing stuff, remove me from routing table
1981         my $node = Route::Node::get($call);
1982         my @rout;
1983         if ($node) {
1984                 @rout = $node->del($main::routeroot);
1985                 
1986                 # and all my ephemera as well
1987                 for (@rout) {
1988                         my $c = $_->call;
1989                         eph_del_regex("^PC1[679].*$c");
1990                 }
1991         }
1992         
1993         # remove them from the pc19list as well
1994         while (my ($k,$v) = each %pc19list) {
1995                 my @l = grep {$_->[0] ne $call} @{$pc19list{$k}};
1996                 if (@l) {
1997                         $pc19list{$k} = \@l;
1998                 } else {
1999                         delete $pc19list{$k};
2000                 }
2001                 
2002                 # and the ephemera
2003                 eph_del_regex("^PC1[679].*$k");
2004         }
2005
2006         # unbusy and stop and outgoing mail
2007         my $mref = DXMsg::get_busy($call);
2008         $mref->stop_msg($call) if $mref;
2009         
2010         # broadcast to all other nodes that all the nodes connected to via me are gone
2011         unless ($pc39flag && $pc39flag == 2) {
2012                 $self->route_pc21($main::mycall, undef, @rout) if @rout;
2013         }
2014
2015         # remove outstanding pings
2016         delete $pings{$call};
2017         
2018         # I was the last node visited
2019     $self->user->node($main::mycall);
2020
2021         # send info to all logged in thingies
2022         $self->tell_login('logoutn');
2023
2024         Log('DXProt', $call . " Disconnected");
2025
2026         $self->SUPER::disconnect;
2027 }
2028
2029
2030
2031 # send a talk message to this thingy
2032 #
2033 sub talk
2034 {
2035         my ($self, $from, $to, $via, $line, $origin) = @_;
2036         
2037         $line =~ s/\^/\\5E/g;                   # remove any ^ characters
2038         $self->send(DXProt::pc10($from, $to, $via, $line, $origin));
2039         Log('talk', $to, $from, $via?$via:$self->call, $line) unless $origin && $origin ne $main::mycall;
2040 }
2041
2042 # send it if it isn't the except list and isn't isolated and still has a hop count
2043 # taking into account filtering and so on
2044
2045 sub send_route
2046 {
2047         my $self = shift;
2048         my $origin = shift;
2049         my $generate = shift;
2050         my $no = shift;     # the no of things to filter on 
2051         my $routeit;
2052         my ($filter, $hops);
2053         my @rin;
2054         
2055         for (; @_ && $no; $no--) {
2056                 my $r = shift;
2057                 
2058                 if (!$self->{isolate} && $self->{routefilter}) {
2059                         $filter = undef;
2060                         if ($r) {
2061                                 ($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});
2062                                 if ($filter) {
2063                                         push @rin, $r;
2064                                 } else {
2065                                         dbg("DXPROT: $self->{call}/" . $r->call . " rejected by output filter") if isdbg('chanerr');
2066                                 }
2067                         } else {
2068                                 dbg("was sent a null value") if isdbg('chanerr');
2069                         }
2070                 } else {
2071                         push @rin, $r unless $self->{isolate} && $r->call ne $main::mycall;
2072                 }
2073         }
2074         if (@rin) {
2075                 foreach my $line (&$generate(@rin, @_)) {
2076                         if ($hops) {
2077                                 $routeit = $line;
2078                                 $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
2079                         } else {
2080                                 $routeit = adjust_hops($self, $line);  # adjust its hop count by node name
2081                                 next unless $routeit;
2082                         }
2083                         
2084                         $self->send($routeit);
2085                 }
2086         }
2087 }
2088
2089 sub broadcast_route
2090 {
2091         my $self = shift;
2092         my $origin = shift;
2093         my $generate = shift;
2094         my $line = shift;
2095         my @dxchan = DXChannel::get_all_nodes();
2096         my $dxchan;
2097         
2098         unless ($self->{isolate}) {
2099                 foreach $dxchan (@dxchan) {
2100                         next if $dxchan == $self;
2101                         next if $dxchan == $main::me;
2102                         next unless $dxchan->isa('DXProt');
2103                         next if ($generate == \&pc16 || $generate==\&pc17) && !$dxchan->user->wantsendpc16;
2104  
2105                         $dxchan->send_route($origin, $generate, @_);
2106                 }
2107         }
2108 }
2109
2110 sub route_pc16
2111 {
2112         my $self = shift;
2113         return unless $self->user->wantpc16;
2114         my $origin = shift;
2115         my $line = shift;
2116         broadcast_route($self, $origin, \&pc16, $line, 1, @_);
2117 }
2118
2119 sub route_pc17
2120 {
2121         my $self = shift;
2122         return unless $self->user->wantpc16;
2123         my $origin = shift;
2124         my $line = shift;
2125         broadcast_route($self, $origin, \&pc17, $line, 1, @_);
2126 }
2127
2128 sub route_pc19
2129 {
2130         my $self = shift;
2131         my $origin = shift;
2132         my $line = shift;
2133         broadcast_route($self, $origin, \&pc19, $line, scalar @_, @_);
2134 }
2135
2136 sub route_pc21
2137 {
2138         my $self = shift;
2139         my $origin = shift;
2140         my $line = shift;
2141         broadcast_route($self, $origin, \&pc21, $line, scalar @_, @_);
2142 }
2143
2144 sub route_pc24
2145 {
2146         my $self = shift;
2147         my $origin = shift;
2148         my $line = shift;
2149         broadcast_route($self, $origin, \&pc24, $line, 1, @_);
2150 }
2151
2152 sub route_pc41
2153 {
2154         my $self = shift;
2155         my $origin = shift;
2156         my $line = shift;
2157         broadcast_route($self, $origin, \&pc41, $line, 1, @_);
2158 }
2159
2160 sub route_pc50
2161 {
2162         my $self = shift;
2163         my $origin = shift;
2164         my $line = shift;
2165         broadcast_route($self, $origin, \&pc50, $line, 1, @_);
2166 }
2167
2168 sub in_filter_route
2169 {
2170         my $self = shift;
2171         my $r = shift;
2172         my ($filter, $hops) = (1, 1);
2173         
2174         if ($self->{inroutefilter}) {
2175                 ($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);
2176                 dbg("PCPROT: $self->{call}/" . $r->call . ' rejected by in_filter_route') if !$filter && isdbg('chanerr');
2177         }
2178         return $filter;
2179 }
2180
2181 sub eph_dup
2182 {
2183         my $s = shift;
2184         my $t = shift || $eph_restime;
2185         my $r;
2186
2187         # chop the end off
2188         $s =~ s/\^H\d\d?\^?\~?$//;
2189         $r = 1 if exists $eph{$s};    # pump up the dup if it keeps circulating
2190         $eph{$s} = $main::systime + $t;
2191         return $r;
2192 }
2193
2194 sub eph_del_regex
2195 {
2196         my $regex = shift;
2197         my ($key, $val);
2198         while (($key, $val) = each %eph) {
2199                 if ($key =~ m{$regex}) {
2200                         delete $eph{$key};
2201                 }
2202         }
2203 }
2204
2205 sub eph_clean
2206 {
2207         my ($key, $val);
2208         
2209         while (($key, $val) = each %eph) {
2210                 if ($main::systime >= $val) {
2211                         delete $eph{$key};
2212                 }
2213         }
2214 }
2215
2216 sub eph_list
2217 {
2218         my ($key, $val);
2219         my @out;
2220
2221         while (($key, $val) = each %eph) {
2222                 push @out, $key, $val;
2223         }
2224         return @out;
2225 }
2226
2227 sub run_cmd
2228 {
2229         goto &DXCommandmode::run_cmd;
2230 }
2231 1;
2232 __END__