start work on NP stuff seriously
[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                   passphrase => '9,Pass Phrase,yesno',
48                   addr => '0,Full Address',
49                   'sort' => '0,Type of User', # A - ak1a, U - User, S - spider cluster, B - BBS
50                   xpert => '0,Expert Status,yesno',
51                   bbs => '0,Home BBS',
52                   node => '0,Last Node',
53                   homenode => '0,Home Node',
54                   lockout => '9,Locked out?,yesno',     # won't let them in at all
55                   dxok => '9,Accept DX Spots?,yesno', # accept his dx spots?
56                   annok => '9,Accept Announces?,yesno', # accept his announces?
57                   lang => '0,Language',
58                   hmsgno => '0,Highest Msgno',
59                   group => '0,Access Group,parray',     # used to create a group of users/nodes for some purpose or other
60                   isolate => '9,Isolate network,yesno',
61                   wantbeep => '0,Req Beep,yesno',
62                   wantann => '0,Req Announce,yesno',
63                   wantwwv => '0,Req WWV,yesno',
64                   wantwcy => '0,Req WCY,yesno',
65                   wantecho => '0,Req Echo,yesno',
66                   wanttalk => '0,Req Talk,yesno',
67                   wantwx => '0,Req WX,yesno',
68                   wantdx => '0,Req DX Spots,yesno',
69                   wantemail => '0,Req Msgs as Email,yesno',
70                   pagelth => '0,Current Pagelth',
71                   pingint => '9,Node Ping interval',
72                   nopings => '9,Ping Obs Count',
73                   wantlogininfo => '9,Login info req,yesno',
74           wantgrid => '0,DX Grid Info,yesno',
75                   wantann_talk => '0,Talklike Anns,yesno',
76                   wantpc90 => '1,Req PC90,yesno',
77                   wantnp => '1,Req New Protocol,yesno',
78                   lastoper => '9,Last for/oper,cldatetime',
79                   nothere => '0,Not Here Text',
80                   registered => '9,Registered?,yesno',
81                   prompt => '0,Required Prompt',
82                   version => '1,Version',
83                   build => '1,Build',
84                  );
85
86 no strict;
87 sub AUTOLOAD
88 {
89         my $self = shift;
90         my $name = $AUTOLOAD;
91   
92         return if $name =~ /::DESTROY$/;
93         $name =~ s/.*:://o;
94   
95         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
96         # this clever line of code creates a subroutine which takes over from autoload
97         # from OO Perl - Conway
98         *{$AUTOLOAD} = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}} ;
99         if (@_) {
100                 $self->{$name} = shift;
101         }
102         return $self->{$name};
103 }
104
105 use strict;
106
107 #
108 # initialise the system
109 #
110 sub init
111 {
112         my ($pkg, $fn, $mode) = @_;
113   
114         confess "need a filename in User" if !$fn;
115         $fn .= ".v2";
116         if ($mode) {
117                 $dbm = tie (%u, 'DB_File', $fn, O_CREAT|O_RDWR, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!)";
118         } else {
119                 $dbm = tie (%u, 'DB_File', $fn, O_RDONLY, 0666, $DB_BTREE) or confess "can't open user file: $fn ($!)";
120         }
121         
122         $filename = $fn;
123 }
124
125 sub del_file
126 {
127         my ($pkg, $fn) = @_;
128   
129         confess "need a filename in User" if !$fn;
130         $fn .= ".v2";
131         unlink $fn;
132 }
133
134 #
135 # periodic processing
136 #
137 sub process
138 {
139         if ($main::systime > $lasttime + 15) {
140                 $dbm->sync;
141                 $lasttime = $main::systime;
142         }
143 }
144
145 #
146 # close the system
147 #
148
149 sub finish
150 {
151         undef $dbm;
152         untie %u;
153 }
154
155 #
156 # new - create a new user
157 #
158
159 sub new
160 {
161         my $pkg = shift;
162         my $call = uc shift;
163         #  $call =~ s/-\d+$//o;
164   
165 #       confess "can't create existing call $call in User\n!" if $u{$call};
166
167         my $self = bless {}, $pkg;
168         $self->{call} = $call;
169         $self->{'sort'} = 'U';
170         $self->put;
171         return $self;
172 }
173
174 #
175 # get - get an existing user - this seems to return a different reference everytime it is
176 #       called - see below
177 #
178
179 sub get
180 {
181         my $pkg = shift;
182         my $call = uc shift;
183         my $data;
184         unless ($dbm->get($call, $data)) {
185                 return decode($data);
186         }
187         return undef;
188 }
189
190 #
191 # get an existing either from the channel (if there is one) or from the database
192 #
193 # It is important to note that if you have done a get (for the channel say) and you
194 # want access or modify that you must use this call (and you must NOT use get's all
195 # over the place willy nilly!)
196 #
197
198 sub get_current
199 {
200         my $pkg = shift;
201         my $call = uc shift;
202   
203         my $dxchan = DXChannel->get($call);
204         return $dxchan->user if $dxchan;
205         my $rref = Route::get($call);
206         return $rref->user if $rref && exists $rref->{user};
207         return $pkg->get($call);
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
600 sub unset_passphrase
601 {
602         my $self = shift;
603         delete $self->{passphrase};
604 }
605 1;
606 __END__
607
608
609
610
611