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