]> git.donarmstrong.com Git - infobot.git/blob - src/DynaConfig.pl
f6f066668079f9d578ec046471de5dc29faf9a0f
[infobot.git] / src / DynaConfig.pl
1 #
2 # DynaConfig.pl: Read/Write configuration files dynamically.
3 #        Author: dms
4 #       Version: v0.1 (20010120)
5 #       Created: 20010119
6 #          NOTE: Merged from User.pl
7 #
8
9 use strict;
10
11 use vars qw(%chanconf %cache %bans %channels %nuh %users %ignore
12         %talkWho %dcc %mask %flag2attr);
13 use vars qw($utime_userfile $ucount_userfile $utime_chanfile $who
14         $ucount_chanfile $userHandle $chan $msgType $talkchannel
15         $ident $bot_state_dir $talkWho $flag_quit $wtime_userfile
16         $wcount_userfile $wtime_chanfile $nuh $message);
17
18 #####
19 ##### USERFILE CONFIGURATION READER/WRITER
20 #####
21
22 sub readUserFile {
23     my $f = "$bot_state_dir/blootbot.users";
24
25     if (! -f $f) {
26         &DEBUG("userfile not found; new fresh run detected.");
27         return;
28     }
29
30     if ( -f $f and -f "$f~") {
31         my $s1 = -s $f;
32         my $s2 = -s "$f~";
33
34         if ($s2 > $s1*3) {
35             &FIXME("rUF: backup file bigger than current file.");
36         }
37     }
38
39     if (!open IN, $f) {
40         &ERROR("Cannot read userfile ($f): $!");
41         &closeLog();
42         exit 1;
43     }
44
45     undef %users;       # clear on reload.
46     undef %bans;        # reset.
47     undef %ignore;      # reset.
48
49     my $ver = <IN>;
50     if ($ver !~ /^#v[12]/) {
51         &ERROR("old or invalid user file found.");
52         &closeLog();
53         exit 1; # correct?
54     }
55
56     my $nick;
57     my $type;
58     while (<IN>) {
59         chop;
60
61         next if /^$/;
62         next if /^#/;
63
64         if (/^--(\S+)[\s\t]+(.*)$/) {           # user: body
65             my ($what,$val) = ($1,$2);
66
67             if (!defined $val or $val eq "") {
68                 &WARN("$what: val == NULL.");
69                 next;
70             }
71
72             if (!defined $nick) {
73                 &WARN("DynaConfig: invalid line: $_");
74                 next;
75             }
76
77             # hack for attribute support.
78             if ($what =~ /^attr$/) {
79                 foreach (split / /, $val) {
80                     $users{$nick}{$what}{$_} = 1;
81                 }
82                 next;
83             }
84
85             if ($what =~ /^HOSTS$/) {
86                 $users{$nick}{$what}{$val} = 1;
87             } else {
88                 $users{$nick}{$what} = $val;
89             }
90
91         } elsif (/^(\S+)$/) {                   # user: start.
92             $nick       = $1;
93
94         } elsif (/^::(\S+) ignore$/) {          # ignore: start.
95             $chan       = $1;
96             $type       = "ignore";
97
98         } elsif (/^- (\S+):\+(\d+):\+(\d+):(\S+):(.*)$/ and $type eq "ignore") {
99             ### ignore: body.
100             my $mask = $1;
101             my(@array) = ($2,$3,$4,$5);
102             ### DEBUG purposes only!
103             if ($mask !~ /^$mask{nuh}$/) {
104                 &WARN("ignore: mask $mask is invalid.");
105                 next;
106             }
107             $ignore{$chan}{$mask} = \@array;
108
109         } elsif (/^::(\S+) bans$/) {            # bans: start.
110             $chan       = $1;
111             $type       = "bans";
112
113         } elsif (/^- (\S+):\+(\d+):\+(\d+):(\d+):(\S+):(.*)$/ and $type eq "bans") {
114             ### bans: body.
115             # $btime, $atime, $count, $whoby, $reason.
116             my(@array) = ($2,$3,$4,$5,$6);
117             $bans{$chan}{$1} = \@array;
118
119         } else {                                # unknown.
120             &WARN("unknown line: $_");
121         }
122     }
123     close IN;
124
125     &status( sprintf("USERFILE: Loaded: %d users, %d bans, %d ignore",
126                 scalar(keys %users)-1,
127                 scalar(keys %bans),             # ??
128                 scalar(keys %ignore),           # ??
129         )
130     );
131 }
132
133 sub writeUserFile {
134     if (!scalar keys %users) {
135         &DEBUG("wUF: nothing to write.");
136         return;
137     }
138
139     if (!open OUT,">$bot_state_dir/blootbot.users") {
140         &ERROR("Cannot write userfile ($bot_state_dir/blootbot.users): $!");
141         return;
142     }
143
144     my $time            = scalar(gmtime);
145
146     print OUT "#v1: blootbot -- $ident -- written $time\n\n";
147
148     ### USER LIST.
149     my $cusers  = 0;
150     foreach (sort keys %users) {
151         my $user = $_;
152         $cusers++;
153         my $count = scalar keys %{ $users{$user} };
154         if (!$count) {
155             &WARN("user $user has no other attributes; skipping.");
156             next;
157         }
158
159         print OUT "$user\n";
160
161         foreach (sort keys %{ $users{$user} }) {
162             # todo: rename what to key?
163             my $what    = $_;
164             my $val     = $users{$user}{$what};
165
166             if (ref($val) eq "SCALAR") {
167                 print OUT "--$what\t\t$val\n";
168                 next;
169             }
170
171             next unless (ref($val) eq "HASH");
172
173             if ($what ne "attr") {
174                 foreach (sort keys %{ $users{$user}{$what} }) {
175                     print OUT "--$what\t\t$_\n";
176                 }
177                 next;
178             }
179
180             # disabled until confirmed to work flawlessly.
181             next if (1);
182
183             # attr support.
184             my $str = "--$what\t\t";
185             my @attr = sort keys %{ $users{$user}{$what} };
186             # some fucking weird code ;) does it work?
187             # any room for improvement?
188             while (@attr) {
189                 my $attr = shift(@attr);
190                 my $_str = $str." ".$attr;
191                 my $print = 0;
192
193                 if (length $str < 60 && length $_str > 60) {
194                     $print++;
195                 } else {
196                     $str = $_str;
197                     $print++ if (!@attr);
198                 }
199
200                 next unless ($print);
201                 print OUT "$str\n";
202                 $str = "--$what\t\t";
203             }
204         }
205         print OUT "\n";
206     }
207
208     ### BAN LIST.
209     my $cbans   = 0;
210     foreach (keys %bans) {
211         my $chan = $_;
212         $cbans++;
213
214         my $count = scalar keys %{ $bans{$chan} };
215         if (!$count) {
216             &WARN("bans: chan $chan has no other attributes; skipping.");
217             next;
218         }
219
220         print OUT "::$chan bans\n";
221         foreach (keys %{ $bans{$chan} }) {
222 # format: bans: mask expire time-added count who-added reason
223             my @array = @{ $bans{$chan}{$_} };
224             if (scalar @array != 5) {
225                 &WARN("bans: $chan/$_ is corrupted.");
226                 next;
227             }
228
229             printf OUT "- %s:+%d:+%d:%d:%s:%s\n", $_, @array;
230         }
231     }
232     print OUT "\n" if ($cbans);
233
234     ### IGNORE LIST.
235     my $cignore = 0;
236     foreach (keys %ignore) {
237         my $chan = $_;
238         $cignore++;
239
240         my $count = scalar keys %{ $ignore{$chan} };
241         if (!$count) {
242             &WARN("ignore: chan $chan has no other attributes; skipping.");
243             next;
244         }
245
246         ### TODO: use hash instead of array for flexibility?
247         print OUT "::$chan ignore\n";
248         foreach (keys %{ $ignore{$chan} }) {
249 # format: ignore: mask expire time-added who-added reason
250             my @array = @{ $ignore{$chan}{$_} };
251             if (scalar @array != 4) {
252                 &WARN("ignore: $chan/$_ is corrupted.");
253                 next;
254             }
255
256             printf OUT "- %s:+%d:+%d:%s:%s\n", $_, @array;
257         }
258     }
259
260     close OUT;
261
262     $wtime_userfile = time();
263     &status("--- Saved USERFILE ($cusers users; $cbans bans; $cignore ignore) at $time");
264     if (defined $msgType and $msgType =~ /^chat$/) {
265         &pSReply("--- Writing user file...");
266     }
267 }
268
269 #####
270 ##### CHANNEL CONFIGURATION READER/WRITER
271 #####
272
273 sub readChanFile {
274     my $f = "$bot_state_dir/blootbot.chan";
275     if ( -f $f and -f "$f~") {
276         my $s1 = -s $f;
277         my $s2 = -s "$f~";
278
279         if ($s2 > $s1*3) {
280             &FIXME("rCF: backup file bigger than current file.");
281         }
282     }
283
284     if (!open IN, $f) {
285         &ERROR("Cannot read chanfile ($f): $!");
286         return;
287     }
288
289     undef %chanconf;    # reset.
290
291     $_ = <IN>;          # version string.
292
293     my $chan;
294     while (<IN>) {
295         chop;
296
297         next if /^\s*$/;
298         next if /^\// or /^\;/; # / or ; are comment lines.
299
300         if (/^(\S+)\s*$/) {
301             $chan       = $1;
302             next;
303         }
304         next unless (defined $chan);
305
306         if (/^[\s\t]+\+(\S+)$/) {               # bool, true.
307             $chanconf{$chan}{$1} = 1;
308
309         } elsif (/^[\s\t]+\-(\S+)$/) {          # bool, false.
310             &DEBUG("deprecated support of negative options.") unless ($cache{negative});
311             # although this is supported in run-time configuration.
312             $cache{negative} = 1;
313 #           $chanconf{$chan}{$1} = 0;
314
315         } elsif (/^[\s\t]+(\S+)[\ss\t]+(.*)$/) {# what = val.
316             $chanconf{$chan}{$1} = $2;
317
318         } else {
319             &WARN("unknown line: $_") unless (/^#/);
320         }
321     }
322     close IN;
323
324     # verify configuration
325     ### TODO: check against valid params.
326     foreach $chan (keys %chanconf) {
327         foreach (keys %{ $chanconf{$chan} }) {
328             next unless /^[+-]/;
329
330             &WARN("invalid param: chanconf{$chan}{$_}; removing.");
331             delete $chanconf{$chan}{$_};
332             undef $chanconf{$chan}{$_};
333         }
334     }
335
336     delete $cache{negative};
337
338     &status("CHANFILE: Loaded: ".(scalar(keys %chanconf)-1)." chans");
339 }
340
341 sub writeChanFile {
342     if (!scalar keys %chanconf) {
343         &DEBUG("wCF: nothing to write.");
344         return;
345     }
346
347     if (!open OUT,">$bot_state_dir/blootbot.chan") {
348         &ERROR("Cannot write chanfile ($bot_state_dir/blootbot.chan): $!");
349         return;
350     }
351
352     my $time            = scalar(gmtime);
353     print OUT "#v1: blootbot -- $ident -- written $time\n\n";
354
355     if ($flag_quit) {
356
357         ### Process 1: if defined in _default, remove same definition
358         ###             from non-default channels.
359         foreach (keys %{ $chanconf{_default} }) {
360             my $opt     = $_;
361             my $val     = $chanconf{_default}{$opt};
362             my @chans;
363
364             foreach (keys %chanconf) {
365                 $chan = $_;
366
367                 next if ($chan eq "_default");
368                 next unless (exists $chanconf{$chan}{$opt});
369                 next unless ($val eq $chanconf{$chan}{$opt});
370
371                 push(@chans,$chan);
372                 delete $chanconf{$chan}{$opt};
373             }
374
375             if (scalar @chans) {
376                 &DEBUG("Removed config $opt to @chans since it's defiend in '_default'");
377             }
378         }
379
380         ### Process 2: if defined in all chans but _default, set in
381         ###             _default and remove all others.
382         my (%optsval, %opts);
383         foreach (keys %chanconf) {
384             $chan = $_;
385             next if ($chan eq "_default");
386             my $opt;
387
388             foreach (keys %{ $chanconf{$chan} }) {
389                 $opt = $_;
390                 if (exists $optsval{$opt} and $optsval{$opt} eq $chanconf{$chan}{$opt}) {
391                     $opts{$opt}++;
392                     next;
393                 }
394                 $optsval{$opt}  = $chanconf{$chan}{$opt};
395                 $opts{$opt}     = 1;
396             }
397         }
398
399         foreach (keys %opts) {
400             next unless ($opts{$_} > 2);
401             &DEBUG("  opts{$_} => $opts{$_}");
402         }
403
404         ### other optimizations are in UserDCC.pl
405     }
406
407     ### lets do it...
408     foreach (sort keys %chanconf) {
409         $chan   = $_;
410
411         print OUT "$chan\n";
412
413         foreach (sort keys %{ $chanconf{$chan} }) {
414             my $val = $chanconf{$chan}{$_};
415
416             if ($val =~ /^0$/) {                # bool, false.
417                 print OUT "    -$_\n";
418
419             } elsif ($val =~ /^1$/) {           # bool, true.
420                 print OUT "    +$_\n";
421
422             } else {                            # what = val.
423                 print OUT "    $_ $val\n";
424
425             }
426
427         }
428         print OUT "\n";
429     }
430
431     close OUT;
432
433     $wtime_chanfile = time();
434     &status("--- Saved CHANFILE (".scalar(keys %chanconf).
435                 " chans) at $time");
436
437     if (defined $msgType and $msgType =~ /^chat$/) {
438         &pSReply("--- Writing chan file...");
439     }
440 }
441
442 #####
443 ##### USER COMMANDS.
444 #####
445
446 # todo: support multiple flags, eg: "+o-m"
447 sub IsFlag {
448     my $flags = shift;
449     my ($ret, $f, $o) = "";
450
451     &verifyUser($who, $nuh);
452
453     # userfile v2 support:
454     if ($users{$userHandle}{attr}) {
455         # todo: fix for multiple flags/attr
456         my $attr = $flag2attr{$flags};
457         if (!$attr) {
458             &WARN("IsFlag: !flag2attr{$flags}");
459             return 0;
460         }
461         return 1 if ($users{$userHandle}{attr}{$attr});
462         return 0;
463     }
464
465     foreach $f (split //, $users{$userHandle}{FLAGS}) {
466         foreach $o (split //, $flags) {
467             next unless ($f eq $o);
468
469             $ret = $f;
470             last;
471         }
472     }
473
474     $ret;
475 }
476
477 sub verifyUser {
478     my ($nick, $lnuh) = @_;
479     my ($user, $m);
480
481     if ($userHandle = $dcc{'CHATvrfy'}{$who}) {
482         &VERB("vUser: cached auth for $who.",2);
483         return $userHandle;
484     }
485
486     $userHandle = "";
487
488     foreach $user (keys %users) {
489         next if ($user eq "_default");
490
491         foreach $m (keys %{ $users{$user}{HOSTS} }) {
492             $m =~ s/\?/./g;
493             $m =~ s/\*/.*?/g;
494             $m =~ s/([\@\(\)\[\]])/\\$1/g;
495
496             next unless ($lnuh =~ /^$m$/i);
497
498             if ($user !~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
499                 &status("vU: host matched but diff nick ($nick != $user).");
500                 $cache{VUSERWARN}{$user} = 1;
501             }
502
503             $userHandle = $user;
504             last;
505         }
506
507         last if ($userHandle ne "");
508
509         if ($user =~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
510             &status("vU: nick matched but host is not in list ($lnuh).");
511             $cache{VUSERWARN}{$user} = 1;
512         }
513     }
514
515     $userHandle ||= "_default";
516     # what's talkchannel for?
517     $talkWho{$talkchannel} = $who if (defined $talkchannel);
518     $talkWho = $who;
519
520     return $userHandle;
521 }
522
523 sub ckpasswd {
524     # returns true if arg1 encrypts to arg2
525     my ($plain, $encrypted) = @_;
526     if ($encrypted eq "") {
527         ($plain, $encrypted) = split(/\s+/, $plain, 2);
528     }
529     return 0 unless ($plain ne "" and $encrypted ne "");
530
531     # MD5 // DES. Bobby Billingsley++.
532     my $salt;
533     if ($encrypted =~ /^(\S{2})/ and length $encrypted == 13) {
534         $salt = $1;
535     } elsif ($encrypted =~ /^\$\d\$(\w\w)\$/) {
536         $salt = $1;
537     } else {
538         &DEBUG("unknown salt from $encrypted.");
539         return 0;
540     }
541
542     return ($encrypted eq crypt($plain, $salt));
543 }
544
545 # mainly for dcc chat... hrm.
546 sub hasFlag {
547     my ($flag) = @_;
548
549     if (&IsFlag($flag) ne $flag) {
550         &status("DCC CHAT: <$who> $message -- not enough flags.");
551         &pSReply("error: you do not have enough flags for that. ($flag required)");
552         return 0;
553     }
554
555     return 1;
556 }
557
558 # expire is time in minutes
559 sub ignoreAdd {
560     my($mask,$chan,$expire,$comment) = @_;
561
562     $chan       ||= "*";        # global if undefined.
563     $comment    ||= "";         # optional.
564     $expire     ||= 0;          # permament.
565     my $count   ||= 0;
566
567     if ($expire > 0) {
568         $expire         = ($expire*60) + time();
569     } else {
570         $expire         = 0;
571     }
572
573     my $exist   = 0;
574     $exist++ if (exists $ignore{$chan}{$mask});
575
576     $ignore{$chan}{$mask} = [$expire, time(), $who, $comment];
577
578     # todo: improve this.
579     if ($expire == 0) {
580         &status("ignore: Added $mask for $chan to NEVER expire, by $who, for $comment");
581     } else {
582         &status("ignore: Added $mask for $chan to expire $expire mins, by $who, for $comment");
583     }
584
585     if ($exist) {
586         $utime_userfile = time();
587         $ucount_userfile++;
588
589         return 2;
590     } else {
591         return 1;
592     }
593 }
594
595 sub ignoreDel {
596     my($mask)   = @_;
597     my @match;
598
599     ### TODO: support wildcards.
600     foreach (keys %ignore) {
601         my $chan = $_;
602
603         foreach (grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} }) {
604             delete $ignore{$chan}{$mask};
605             push(@match,$chan);
606         }
607
608         &DEBUG("iD: scalar => ".scalar(keys %{ $ignore{$chan} }) );
609     }
610
611     if (scalar @match) {
612         $utime_userfile = time();
613         $ucount_userfile++;
614     }
615
616     return @match;
617 }
618
619 sub userAdd {
620     my($nick,$mask) = @_;
621
622     return 0 if (exists $users{$nick});
623
624     $utime_userfile = time();
625     $ucount_userfile++;
626
627     if (defined $mask and $mask !~ /^\s*$/) {
628         &DEBUG("userAdd: mask => $mask");
629         $users{$nick}{HOSTS}{$mask} = 1;
630     }
631
632     $users{$nick}{FLAGS} ||= $users{_default}{FLAGS};
633
634     return 1;
635 }
636
637 sub userDel {
638     my($nick)   = @_;
639
640     return 0 if (!exists $users{$nick});
641
642     $utime_userfile = time();
643     $ucount_userfile++;
644
645     delete $users{$nick};
646
647     return 1;
648 }
649
650 sub banAdd {
651     my($mask,$chan,$expire,$reason) = @_;
652     $chan       ||= "*";
653     $expire     ||= 0;
654     $expire     = $expire*60 + time() if ($expire > 0);
655
656     my $exist   = 1;
657     $exist++ if (exists $bans{$chan}{$mask} or exists $bans{'*'}{$mask});
658     $bans{$chan}{$mask} = [$expire, time(), 0, $who, $reason];
659
660     my @chans   = ($chan eq "*") ? keys %channels : $chan;
661     my $m       = $mask;
662     $m          =~ s/\?/\\./g;
663     $m          =~ s/\*/\\S*/g;
664     foreach (@chans) {
665         my $chan = $_;
666         foreach (keys %{ $channels{$chan}{''} }) {
667             next unless (exists $nuh{lc $_});
668             next unless ($nuh{lc $_} =~ /^$m$/i);
669             &FIXME("nuh{$_} =~ /$m/");
670         }
671     }
672
673     if ($exist == 1) {
674         $utime_userfile = time();
675         $ucount_userfile++;
676     }
677
678     return $exist;
679 }
680
681 sub banDel {
682     my($mask)   = @_;
683     my @match;
684
685     foreach (keys %bans) {
686         my $chan        = $_;
687
688         foreach (grep /^\Q$mask\E$/i, keys %{ $bans{$chan} }) {
689             delete $bans{$chan}{$_};
690             push(@match, $chan);
691         }
692
693         &DEBUG("bans: scalar => ".scalar(keys %{ $bans{$chan} }) );
694     }
695
696     if (scalar @match) {
697         $utime_userfile = time();
698         $ucount_userfile++;
699     }
700
701     return @match;
702 }
703
704 sub IsUser {
705     my($user) = @_;
706
707     if ( &getUser($user) ) {
708         return 1;
709     } else {
710         return 0;
711     }
712 }
713
714 sub getUser {
715     my($user) = @_;
716
717     if (!defined $user) {
718         &WARN("getUser: user == NULL.");
719         return;
720     }
721
722     if (my @retval = grep /^\Q$user\E$/i, keys %users) {
723         if ($retval[0] ne $user) {
724             &WARN("getUser: retval[0] ne user ($retval[0] ne $user)");
725         }
726         my $count = scalar keys %{ $users{$retval[0]} };
727         &DEBUG("count => $count.");
728
729         return $retval[0];
730     } else {
731         return;
732     }
733 }
734
735 sub chanSet {
736     my($cmd, $chan, $what, $val) = @_;
737
738     if ($cmd eq "+chan") {
739         if (exists $chanconf{$chan}) {
740             &pSReply("chan $chan already exists.");
741             return;
742         }
743         $chanconf{$chan}{_time_added}   = time();
744         $chanconf{$chan}{autojoin}      = 1;
745
746         &pSReply("Joining $chan...");
747         &joinchan($chan);
748
749         return;
750     }
751
752     if (!exists $chanconf{$chan}) {
753         &pSReply("no such channel $chan");
754         return;
755     }
756
757     my $update  = 0;
758
759     if (defined $what and $what =~ s/^([+-])(\S+)/$2/) {
760         ### ".chanset +blah"
761         ### ".chanset +blah 10"         -- error.
762
763         my $state       = ($1 eq "+") ? 1 : 0;
764         my $was         = $chanconf{$chan}{$what};
765
766         if ($state) {                   # add/set.
767             if (defined $was and $was eq "1") {
768                 &pSReply("setting $what for $chan already 1.");
769                 return;
770             }
771
772             $val        = 1;
773
774         } else {                        # delete/unset.
775             if (!defined $was) {
776                 &pSReply("setting $what for $chan is not set.");
777                 return;
778             }
779
780             if ($was eq "0") {
781                 &pSReply("setting $what for $chan already 0.");
782                 return;
783             }
784
785             $val        = 0;
786         }
787
788         # alter for cosmetic (print out) reasons only.
789         $was    = ($was) ? "; was '$was'" : "";
790
791         if ($val eq "0") {
792             &pSReply("Unsetting $what for $chan$was.");
793             delete $chanconf{$chan}{$what};
794         } else {
795             &pSReply("Setting $what for $chan to '$val'$was.");
796             $chanconf{$chan}{$what}     = $val;
797         }
798
799         $update++;
800
801     } elsif (defined $val) {
802         ### ".chanset blah testing"
803
804         my $was = $chanconf{$chan}{$what};
805         if (defined $was and $was eq $val) {
806             &pSReply("setting $what for $chan already '$val'.");
807             return;
808         }
809         $was    = ($was) ? "; was '$was'" : "";
810         &pSReply("Setting $what for $chan to '$val'$was.");
811
812         $chanconf{$chan}{$what} = $val;
813
814         $update++;
815
816     } else {                            # read only.
817         ### ".chanset"
818         ### ".chanset blah"
819
820         if (!defined $what) {
821             &WARN("chanset/DC: what == undefine.");
822             return;
823         }
824
825         if (exists $chanconf{$chan}{$what}) {
826             &pSReply("$what for $chan is '$chanconf{$chan}{$what}'");
827         } else {
828             &pSReply("$what for $chan is not set.");
829         }
830     }
831
832     if ($update) {
833         $utime_chanfile = time();
834         $ucount_chanfile++;
835     }
836
837     return;
838 }
839
840 sub rehashConfVars {
841     # this is an attempt to fix where an option is enabled but the module
842     # has been not loaded. it also can be used for other things.
843
844     foreach (keys %{ $cache{confvars} }) {
845         my $i = $cache{confvars}{$_};
846         &DEBUG("rehashConfVars: _ => $_");
847
848         if (/^news$/ and $i) {
849             &loadMyModule("news");
850             delete $cache{confvars}{$_};
851         }
852
853         if (/^uptime$/ and $i) {
854             &loadMyModule("uptime");
855             delete $cache{confvars}{$_};
856         }
857
858         if (/^rootwarn$/i and $i) {
859             &loadMyModule($_);
860             delete $cache{confvars}{$_};
861         }
862     }
863
864     &DEBUG("end of rehashConfVars");
865
866     delete $cache{confvars};
867 }
868
869 sub convertUserFileVer2 {
870     foreach (keys %users) {
871         my $handle = $_;
872         my $flags = $users{$handle}{FLAGS};
873         if (!$flags) {
874             &WARN("cUFV2: handle $handle has no flags!");
875             next;
876         }
877
878         my $fail = 0;
879         foreach (split //, $flags) {
880             my $flag = $_;
881             my $attr = $flag2attr{$flag};
882             if (!$attr) {
883                 &DEBUG("cUFV2: handle=$handle: flag=$flag does not exist.");
884                 $fail++;
885                 next;
886             }
887             $users{$handle}{attr}{$attr} = 1;
888         }
889
890         next if ($fail);
891 # uncomment to enable attribute conversion support.
892 #       delete $users{$handle}{FLAGS};
893     }
894 }
895
896 # support more than one attribute?
897 %flag2attr = (
898         m => "modify_factoid",
899         r => "delete_factoid",
900         t => "add_factoid",
901         a => "ask_factoid",
902         n => "bot_owner",
903         o => "bot_master",
904         A => "admin_over_msg",
905         T => "topic"
906 );
907
908 1;