added PC90 stuff
[spider.git] / perl / DXUser.pm
1 #
2 # DX cluster user routines
3 #
4 # Copyright (c) 1998 - Dirk Koopman G1TLH
5 #
6 # $Id$
7 #
8
9 package DXUser;
10
11 use DXLog;
12 use DB_File;
13 use Data::Dumper;
14 use Fcntl;
15 use IO::File;
16 use DXDebug;
17
18 use strict;
19
20 use vars qw($VERSION $BRANCH);
21 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
22 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/ ) || 0;
23 $main::build += $VERSION;
24 $main::branch += $BRANCH;
25
26 use vars qw(%u $dbm $filename %valid $lastoperinterval $lasttime);
27
28 %u = ();
29 $dbm = undef;
30 $filename = undef;
31 $lastoperinterval = 60*24*60*60;
32 $lasttime = 0;
33
34 # hash of valid elements and a simple prompt
35 %valid = (
36                   call => '0,Callsign',
37                   alias => '0,Real Callsign',
38                   name => '0,Name',
39                   qth => '0,Home QTH',
40                   lat => '0,Latitude,slat',
41                   long => '0,Longitude,slong',
42                   qra => '0,Locator',
43                   email => '0,E-mail Address,parray',
44                   priv => '9,Privilege Level',
45                   lastin => '0,Last Time in,cldatetime',
46                   passwd => '9,Password,yesno',
47                   addr => '0,Full Address',
48                   'sort' => '0,Type of User', # A - ak1a, U - User, S - spider cluster, B - BBS
49                   xpert => '0,Expert Status,yesno',
50                   bbs => '0,Home BBS',
51                   node => '0,Last Node',
52                   homenode => '0,Home Node',
53                   lockout => '9,Locked out?,yesno',     # won't let them in at all
54                   dxok => '9,Accept DX Spots?,yesno', # accept his dx spots?
55                   annok => '9,Accept Announces?,yesno', # accept his announces?
56                   lang => '0,Language',
57                   hmsgno => '0,Highest Msgno',
58                   group => '0,Access Group,parray',     # used to create a group of users/nodes for some purpose or other
59                   isolate => '9,Isolate network,yesno',
60                   wantbeep => '0,Req Beep,yesno',
61                   wantann => '0,Req Announce,yesno',
62                   wantwwv => '0,Req WWV,yesno',
63                   wantwcy => '0,Req WCY,yesno',
64                   wantecho => '0,Req Echo,yesno',
65                   wanttalk => '0,Req Talk,yesno',
66                   wantwx => '0,Req WX,yesno',
67                   wantdx => '0,Req DX Spots,yesno',
68                   wantemail => '0,Req Msgs as Email,yesno',
69                   pagelth => '0,Current Pagelth',
70                   pingint => '9,Node Ping interval',
71                   nopings => '9,Ping Obs Count',
72                   wantlogininfo => '9,Login info req,yesno',
73           wantgrid => '0,DX Grid Info,yesno',
74                   wantann_talk => '0,Talklike Anns,yesno',
75                   wantpc90 => '1,Req PC90,yesno',
76                   lastoper => '9,Last for/oper,cldatetime',
77                   nothere => '0,Not Here Text',
78                   registered => '9,Registered?,yesno',
79                   prompt => '0,Required Prompt',
80                  );
81
82 no strict;
83 sub AUTOLOAD
84 {
85         my $self = shift;
86         my $name = $AUTOLOAD;
87   
88         return if $name =~ /::DESTROY$/;
89         $name =~ s/.*:://o;
90   
91         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
92         # this clever line of code creates a subroutine which takes over from autoload
93         # from OO Perl - Conway
94         *{$AUTOLOAD} = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}} ;
95         if (@_) {
96                 $self->{$name} = shift;
97         }
98         return $self->{$name};
99 }
100
101 use strict;
102
103 #
104 # initialise the system
105 #
106 sub init
107 {
108         my ($pkg, $fn, $mode) = @_;
109   
110         confess "need a filename in User" if !$fn;
111         $fn .= ".v2";
112         if ($mode) {
113                 $dbm = tie (%u, 'DB_File', $fn, O_CREAT|O_RDWR, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!)";
114         } else {
115                 $dbm = tie (%u, 'DB_File', $fn, O_RDONLY, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!)";
116         }
117         
118         $filename = $fn;
119 }
120
121 sub del_file
122 {
123         my ($pkg, $fn) = @_;
124   
125         confess "need a filename in User" if !$fn;
126         $fn .= ".v2";
127         unlink $fn;
128 }
129
130 #
131 # periodic processing
132 #
133 sub process
134 {
135         if ($main::systime > $lasttime + 15) {
136                 $dbm->sync;
137                 $lasttime = $main::systime;
138         }
139 }
140
141 #
142 # close the system
143 #
144
145 sub finish
146 {
147         undef $dbm;
148         untie %u;
149 }
150
151 #
152 # new - create a new user
153 #
154
155 sub new
156 {
157         my $pkg = shift;
158         my $call = uc shift;
159         #  $call =~ s/-\d+$//o;
160   
161 #       confess "can't create existing call $call in User\n!" if $u{$call};
162
163         my $self = bless {}, $pkg;
164         $self->{call} = $call;
165         $self->{'sort'} = 'U';
166         $self->put;
167         return $self;
168 }
169
170 #
171 # get - get an existing user - this seems to return a different reference everytime it is
172 #       called - see below
173 #
174
175 sub get
176 {
177         my $pkg = shift;
178         my $call = uc shift;
179         my $data;
180         unless ($dbm->get($call, $data)) {
181                 return decode($data);
182         }
183         return undef;
184 }
185
186 #
187 # get an existing either from the channel (if there is one) or from the database
188 #
189 # It is important to note that if you have done a get (for the channel say) and you
190 # want access or modify that you must use this call (and you must NOT use get's all
191 # over the place willy nilly!)
192 #
193
194 sub get_current
195 {
196         my $pkg = shift;
197         my $call = uc shift;
198   
199         my $dxchan = DXChannel->get($call);
200         return $dxchan->user if $dxchan;
201         my $data;
202         unless ($dbm->get($call, $data)) {
203                 return decode($data);
204         }
205         return undef;
206 }
207
208 #
209 # get all callsigns in the database 
210 #
211
212 sub get_all_calls
213 {
214         return (sort keys %u);
215 }
216
217 #
218 # put - put a user
219 #
220
221 sub put
222 {
223         my $self = shift;
224         confess "Trying to put nothing!" unless $self && ref $self;
225         my $call = $self->{call};
226         # delete all instances of this 
227 #       for ($dbm->get_dup($call)) {
228 #               $dbm->del_dup($call, $_);
229 #       }
230         $dbm->del($call);
231         delete $self->{annok} if $self->{annok};
232         delete $self->{dxok} if $self->{dxok};
233         $dbm->put($call, $self->encode);
234 }
235
236
237 # create a string from a user reference
238 #
239 sub encode
240 {
241         my $self = shift;
242         my $dd = new Data::Dumper([$self]);
243         $dd->Indent(0);
244         $dd->Terse(1);
245     $dd->Quotekeys($] < 5.005 ? 1 : 0);
246         return $dd->Dumpxs;
247 }
248
249 #
250 # create a hash from a string
251 #
252 sub decode
253 {
254         my $s = shift;
255         my $ref;
256         eval '$ref = ' . $s;
257         if ($@) {
258                 dbg($@);
259                 Log('err', $@);
260                 $ref = undef;
261         }
262         return $ref;
263 }
264
265 #
266 # del - delete a user
267 #
268
269 sub del
270 {
271         my $self = shift;
272         my $call = $self->{call};
273         # delete all instances of this 
274 #       for ($dbm->get_dup($call)) {
275 #               $dbm->del_dup($call, $_);
276 #       }
277         $dbm->del($call);
278 }
279
280 #
281 # close - close down a user
282 #
283
284 sub close
285 {
286         my $self = shift;
287         $self->{lastin} = time;
288         $self->put();
289 }
290
291 #
292 # sync the database
293 #
294
295 sub sync
296 {
297         $dbm->sync;
298 }
299
300 #
301 # return a list of valid elements 
302
303
304 sub fields
305 {
306         return keys(%valid);
307 }
308
309
310 #
311 # export the database to an ascii file
312 #
313
314 sub export
315 {
316         my $fn = shift;
317         
318         # save old ones
319         rename "$fn.oooo", "$fn.ooooo" if -e "$fn.oooo";
320         rename "$fn.ooo", "$fn.oooo" if -e "$fn.ooo";
321         rename "$fn.oo", "$fn.ooo" if -e "$fn.oo";
322         rename "$fn.o", "$fn.oo" if -e "$fn.o";
323         rename "$fn", "$fn.o" if -e "$fn";
324
325         my $count = 0;
326         my $fh = new IO::File ">$fn" or return "cannot open $fn ($!)";
327         if ($fh) {
328                 my $ref = 0;
329                 my $key = 0;
330                 my $action;
331                 my $t = scalar localtime;
332                 print $fh q{#!/usr/bin/perl
333 #
334 # The exported userfile for a DXSpider System
335 #
336 # Input file: $filename
337 #       Time: $t
338 #
339                         
340 package main;
341                         
342 # search local then perl directories
343 BEGIN {
344         umask 002;
345                                 
346         # root of directory tree for this system
347         $root = "/spider"; 
348         $root = $ENV{'DXSPIDER_ROOT'} if $ENV{'DXSPIDER_ROOT'};
349         
350         unshift @INC, "$root/perl";     # this IS the right way round!
351         unshift @INC, "$root/local";
352         
353         # try to detect a lockfile (this isn't atomic but 
354         # should do for now
355         $lockfn = "$root/perl/cluster.lck";       # lock file name
356         if (-e $lockfn) {
357                 open(CLLOCK, "$lockfn") or die "Can't open Lockfile ($lockfn) $!";
358                 my $pid = <CLLOCK>;
359                 chomp $pid;
360                 die "Lockfile ($lockfn) and process $pid exists - cluster must be stopped first\n" if kill 0, $pid;
361                 close CLLOCK;
362         }
363 }
364
365 package DXUser;
366
367 use DXVars;
368 use DXUser;
369
370 if (@ARGV) {
371         $main::userfn = shift @ARGV;
372         print "user filename now $userfn\n";
373 }
374
375 DXUser->del_file($main::userfn);
376 DXUser->init($main::userfn, 1);
377
378 %u = (
379   };
380
381         for ($action = R_FIRST; !$dbm->seq($key, $ref, $action); $action = R_NEXT) {
382                         print $fh "'$key' => q{$ref},\n";
383                         ++$count;
384                 } 
385         print $fh ");\n#\nprint \"there were $count records\\n\";\n#\n";
386         print $fh "DXUser->sync; DXUser->finish;\n#\n";
387         $fh->close;
388     } 
389         return $count;
390 }
391
392 #
393 # group handling
394 #
395
396 # add one or more groups
397 sub add_group
398 {
399         my $self = shift;
400         my $ref = $self->{group} || [ 'local' ];
401         $self->{group} = $ref if !$self->{group};
402         push @$ref, @_ if @_;
403 }
404
405 # remove one or more groups
406 sub del_group
407 {
408         my $self = shift;
409         my $ref = $self->{group} || [ 'local' ];
410         my @in = @_;
411         
412         $self->{group} = $ref if !$self->{group};
413         
414         @$ref = map { my $a = $_; return (grep { $_ eq $a } @in) ? () : $a } @$ref;
415 }
416
417 # does this thing contain all the groups listed?
418 sub union
419 {
420         my $self = shift;
421         my $ref = $self->{group};
422         my $n;
423         
424         return 0 if !$ref || @_ == 0;
425         return 1 if @$ref == 0 && @_ == 0;
426         for ($n = 0; $n < @_; ) {
427                 for (@$ref) {
428                         my $a = $_;
429                         $n++ if grep $_ eq $a, @_; 
430                 }
431         }
432         return $n >= @_;
433 }
434
435 # simplified group test just for one group
436 sub in_group
437 {
438         my $self = shift;
439         my $s = shift;
440         my $ref = $self->{group};
441         
442         return 0 if !$ref;
443         return grep $_ eq $s, $ref;
444 }
445
446 # set up a default group (only happens for them's that connect direct)
447 sub new_group
448 {
449         my $self = shift;
450         $self->{group} = [ 'local' ];
451 }
452
453 #
454 # return a prompt for a field
455 #
456
457 sub field_prompt
458
459         my ($self, $ele) = @_;
460         return $valid{$ele};
461 }
462
463 # some variable accessors
464 sub sort
465 {
466         my $self = shift;
467         @_ ? $self->{'sort'} = shift : $self->{'sort'} ;
468 }
469
470 # some accessors
471 sub _want
472 {
473         my $n = shift;
474         my $self = shift;
475         my $val = shift;
476         my $s = "want$n";
477         $self->{$s} = $val if defined $val;
478         return exists $self->{$s} ? $self->{$s} : 1;
479 }
480
481 sub wantbeep
482 {
483         return _want('beep', @_);
484 }
485
486 sub wantann
487 {
488         return _want('ann', @_);
489 }
490
491 sub wantwwv
492 {
493         return _want('wwv', @_);
494 }
495
496 sub wantwcy
497 {
498         return _want('wcy', @_);
499 }
500
501 sub wantecho
502 {
503         return _want('echo', @_);
504 }
505
506 sub wantwx
507 {
508         return _want('wx', @_);
509 }
510
511 sub wantdx
512 {
513         return _want('dx', @_);
514 }
515
516 sub wanttalk
517 {
518         return _want('talk', @_);
519 }
520
521 sub wantgrid
522 {
523         return _want('grid', @_);
524 }
525
526 sub wantemail
527 {
528         return _want('email', @_);
529 }
530
531 sub wantann_talk
532 {
533         return _want('ann_talk', @_);
534 }
535
536 sub wantlogininfo
537 {
538         my $self = shift;
539         my $val = shift;
540         $self->{wantlogininfo} = $val if defined $val;
541         return $self->{wantlogininfo};
542 }
543
544 sub is_node
545 {
546         my $self = shift;
547         return $self->{sort} =~ /[ACRSX]/;
548 }
549
550 sub is_user
551 {
552         my $self = shift;
553         return $self->{sort} eq 'U';
554 }
555
556 sub is_bbs
557 {
558         my $self = shift;
559         return $self->{sort} eq 'B';
560 }
561
562 sub is_spider
563 {
564         my $self = shift;
565         return $self->{sort} eq 'S';
566 }
567
568 sub is_clx
569 {
570         my $self = shift;
571         return $self->{sort} eq 'C';
572 }
573
574 sub is_dxnet
575 {
576         my $self = shift;
577         return $self->{sort} eq 'X';
578 }
579
580 sub is_arcluster
581 {
582         my $self = shift;
583         return $self->{sort} eq 'R';
584 }
585
586 sub is_ak1a
587 {
588         my $self = shift;
589         return $self->{sort} eq 'A';
590 }
591
592 sub unset_passwd
593 {
594         my $self = shift;
595         delete $self->{passwd};
596 }
597 1;
598 __END__
599
600
601
602
603