2 # DynaConfig.pl: Read/Write configuration files dynamically.
4 # Version: v0.1 (20010120)
6 # NOTE: Merged from User.pl
12 ##### USERFILE CONFIGURATION READER/WRITER
16 my $f = "$bot_state_dir/blootbot.users";
19 &DEBUG("userfile not found; new fresh run detected.");
23 if ( -f $f and -f "$f~") {
28 &DEBUG("rUF: backup file bigger than current file. FIXME");
33 &ERROR("Cannot read userfile ($f): $!");
38 undef %users; # clear on reload.
40 undef %ignore; # reset.
44 &ERROR("old or invalid user file found.");
57 if (/^--(\S+)[\s\t]+(.*)$/) { # user: middle entry.
58 my ($what,$val) = ($1,$2);
60 if (!defined $val or $val eq "") {
61 &WARN("$what: val == NULL.");
66 &WARN("DynaConfig: invalid line: $_");
71 if ($what eq "HOSTS") {
72 $users{$nick}{$what}{$val} = 1;
74 $users{$nick}{$what} = $val;
77 } elsif (/^(\S+)$/) { # user: start entry.
80 } elsif (/^::(\S+) ignore$/) { # ignore: start entry.
84 } elsif (/^- (\S+):\+(\d+):\+(\d+):(\S+):(.*)$/ and $type eq "ignore") {
85 ### ignore: middle entry.
87 my(@array) = ($2,$3,$4,$5);
88 ### DEBUG purposes only!
89 if ($mask !~ /^$mask{nuh}$/) {
90 &WARN("ignore: mask $mask is invalid.");
93 $ignore{$chan}{$mask} = \@array;
95 } elsif (/^::(\S+) bans$/) { # bans: start entry.
99 } elsif (/^- (\S+):\+(\d+):\+(\d+):(\d+):(\S+):(.*)$/ and $type eq "bans") {
100 ### bans: middle entry.
101 # $btime, $atime, $count, $whoby, $reason.
102 my(@array) = ($2,$3,$4,$5,$6);
103 $bans{$chan}{$1} = \@array;
106 &WARN("unknown line: $_");
111 &status( sprintf("USERFILE: Loaded: %d users, %d bans, %d ignore",
112 scalar(keys %users)-1,
113 scalar(keys %bans), # ??
114 scalar(keys %ignore), # ??
120 if (!scalar keys %users) {
121 &DEBUG("wUF: nothing to write.");
125 if (!open OUT,">$bot_state_dir/blootbot.users") {
126 &ERROR("Cannot write userfile ($bot_state_dir/blootbot.users): $!");
130 my $time = scalar(localtime);
132 print OUT "#v1: blootbot -- $ident -- written $time\n\n";
136 foreach (sort keys %users) {
139 my $count = scalar keys %{ $users{$user} };
141 &WARN("user $user has no other attributes; skipping.");
147 foreach (sort keys %{ $users{$user} }) {
149 my $val = $users{$user}{$_};
151 if (ref($val) eq "HASH") {
152 foreach (sort keys %{ $users{$user}{$_} }) {
153 print OUT "--$what\t\t$_\n";
157 print OUT "--$_\t\t$val\n";
165 foreach (keys %bans) {
169 my $count = scalar keys %{ $bans{$chan} };
171 &WARN("bans: chan $chan has no other attributes; skipping.");
175 print OUT "::$chan bans\n";
176 foreach (keys %{ $bans{$chan} }) {
177 # format: bans: mask expire time-added count who-added reason
178 my @array = @{ $bans{$chan}{$_} };
179 if (scalar @array != 5) {
180 &WARN("bans: $chan/$_ is corrupted.");
184 printf OUT "- %s:+%d:+%d:%d:%s:%s\n", $_, @array;
187 print OUT "\n" if ($cbans);
191 foreach (keys %ignore) {
195 my $count = scalar keys %{ $ignore{$chan} };
197 &WARN("ignore: chan $chan has no other attributes; skipping.");
201 ### TODO: use hash instead of array for flexibility?
202 print OUT "::$chan ignore\n";
203 foreach (keys %{ $ignore{$chan} }) {
204 # format: ignore: mask expire time-added who-added reason
205 my @array = @{ $ignore{$chan}{$_} };
206 if (scalar @array != 4) {
207 &WARN("ignore: $chan/$_ is corrupted.");
211 printf OUT "- %s:+%d:+%d:%s:%s\n", $_, @array;
217 $wtime_userfile = time();
218 &status("--- Saved USERFILE ($cusers users; $cbans bans; $cignore ignore) at $time");
219 if (defined $msgType and $msgType =~ /^chat$/) {
220 &pSReply("--- Writing user file...");
225 ##### CHANNEL CONFIGURATION READER/WRITER
229 my $f = "$bot_state_dir/blootbot.chan";
230 if ( -f $f and -f "$f~") {
235 &DEBUG("rCF: backup file bigger than current file. FIXME");
240 &ERROR("Cannot read chanfile ($f): $!");
244 undef %chanconf; # reset.
246 $_ = <IN>; # version string.
253 next if /^\// or /^\;/; # / or ; are comment lines.
259 next unless (defined $chan);
261 if (/^[\s\t]+\+(\S+)$/) { # bool, true.
262 $chanconf{$chan}{$1} = 1;
264 } elsif (/^[\s\t]+\-(\S+)$/) { # bool, false.
265 &DEBUG("deprecated support of negative options.") unless ($cache{negative});
266 # although this is supported in run-time configuration.
267 $cache{negative} = 1;
268 # $chanconf{$chan}{$1} = 0;
270 } elsif (/^[\s\t]+(\S+)[\ss\t]+(.*)$/) {# what = val.
271 $chanconf{$chan}{$1} = $2;
274 &WARN("unknown line: $_") unless (/^#/);
279 # verify configuration
280 ### TODO: check against valid params.
281 foreach $chan (keys %chanconf) {
282 foreach (keys %{ $chanconf{$chan} }) {
285 &WARN("invalid param: chanconf{$chan}{$_}; removing.");
286 delete $chanconf{$chan}{$_};
287 undef $chanconf{$chan}{$_};
291 delete $cache{negative};
293 &status("CHANFILE: Loaded: ".(scalar(keys %chanconf)-1)." chans");
297 if (!scalar keys %chanconf) {
298 &DEBUG("wCF: nothing to write.");
302 if (!open OUT,">$bot_state_dir/blootbot.chan") {
303 &ERROR("Cannot write chanfile ($bot_state_dir/blootbot.chan): $!");
307 my $time = scalar(localtime);
308 print OUT "#v1: blootbot -- $ident -- written $time\n\n";
312 ### Process 1: if defined in _default, remove same definition
313 ### from non-default channels.
314 foreach (keys %{ $chanconf{_default} }) {
316 my $val = $chanconf{_default}{$opt};
319 foreach (keys %chanconf) {
322 next if ($chan eq "_default");
323 next unless (exists $chanconf{$chan}{$opt});
324 next unless ($val eq $chanconf{$chan}{$opt});
327 delete $chanconf{$chan}{$opt};
331 &DEBUG("Removed config $opt to @chans since it's defiend in '_default'");
335 ### Process 2: if defined in all chans but _default, set in
336 ### _default and remove all others.
337 my (%optsval, %opts);
338 foreach (keys %chanconf) {
340 next if ($chan eq "_default");
343 foreach (keys %{ $chanconf{$chan} }) {
345 if (exists $optsval{$opt} and $optsval{$opt} eq $chanconf{$chan}{$opt}) {
349 $optsval{$opt} = $chanconf{$chan}{$opt};
354 foreach (keys %opts) {
355 next unless ($opts{$_} > 2);
356 &DEBUG(" opts{$_} => $opts{$_}");
359 ### other optimizations are in UserDCC.pl
363 foreach (sort keys %chanconf) {
368 foreach (sort keys %{ $chanconf{$chan} }) {
369 my $val = $chanconf{$chan}{$_};
371 if ($val =~ /^0$/) { # bool, false.
374 } elsif ($val =~ /^1$/) { # bool, true.
377 } else { # what = val.
378 print OUT " $_ $val\n";
388 $wtime_chanfile = time();
389 &status("--- Saved CHANFILE (".scalar(keys %chanconf).
392 if (defined $msgType and $msgType =~ /^chat$/) {
393 &pSReply("--- Writing chan file...");
403 my ($ret, $f, $o) = "";
405 &verifyUser($who, $nuh);
407 foreach $f (split //, $users{$userHandle}{FLAGS}) {
408 foreach $o ( split //, $flags ) {
409 next unless ($f eq $o);
420 my ($nick, $lnuh) = @_;
423 if ($userHandle = $dcc{'CHATvrfy'}{$who}) {
424 &VERB("vUser: cached auth for $who.",2);
430 foreach $user (keys %users) {
431 next if ($user eq "_default");
433 foreach $m (keys %{ $users{$user}{HOSTS} }) {
436 $m =~ s/([\@\(\)\[\]])/\\$1/g;
438 next unless ($lnuh =~ /^$m$/i);
440 if ($user !~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
441 &status("vU: host matched but diff nick ($nick != $user).");
442 $cache{VUSERWARN}{$user} = 1;
449 last if ($userHandle ne "");
451 if ($user =~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
452 &status("vU: nick matched but host is not in list ($lnuh).");
453 $cache{VUSERWARN}{$user} = 1;
457 $userHandle ||= "_default";
458 # what's talkchannel for?
459 $talkWho{$talkchannel} = $who if (defined $talkchannel);
466 # returns true if arg1 encrypts to arg2
467 my ($plain, $encrypted) = @_;
468 if ($encrypted eq "") {
469 ($plain, $encrypted) = split(/\s+/, $plain, 2);
471 return 0 unless ($plain ne "" and $encrypted ne "");
473 # MD5 // DES. Bobby Billingsley++.
475 if ($encrypted =~ /^(\S{2})/ and length $encrypted == 13) {
477 } elsif ($encrypted =~ /^\$\d\$(\w\w)\$/) {
480 &DEBUG("unknown salt from $encrypted.");
484 return ($encrypted eq crypt($plain, $salt));
487 # mainly for dcc chat... hrm.
491 if (&IsFlag($flag) eq $flag) {
494 &status("DCC CHAT: <$who> $message -- not enough flags.");
495 &pSReply("error: you do not have enough flags for that. ($flag required)");
501 my($mask,$chan,$expire,$comment) = @_;
503 $chan ||= "*"; # global if undefined.
504 $comment ||= ""; # optional.
505 $expire ||= 0; # permament.
509 $expire = $expire*60 + time();
515 $exist++ if (exists $ignore{$chan}{$mask});
517 $ignore{$chan}{$mask} = [$expire, time(), $who, $comment];
519 # todo: improve this.
520 &status("ignore: Added $mask for $chan to expire $expire, by $who, for $comment");
523 $utime_userfile = time();
536 ### TODO: support wildcards.
537 foreach (keys %ignore) {
540 foreach (grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} }) {
541 delete $ignore{$chan}{$mask};
545 &DEBUG("iD: scalar => ".scalar(keys %{ $ignore{$chan} }) );
549 $utime_userfile = time();
557 my($nick,$mask) = @_;
559 if (exists $users{$nick}) {
563 $utime_userfile = time();
566 if (defined $mask and $mask !~ /^\s*$/) {
567 &DEBUG("userAdd: mask => $mask");
568 $users{$nick}{HOSTS}{$mask} = 1;
571 $users{$nick}{FLAGS} ||= $users{_default}{FLAGS};
579 if (!exists $users{$nick}) {
583 $utime_userfile = time();
586 delete $users{$nick};
592 my($mask,$chan,$expire,$reason) = @_;
598 $expire = $expire*60 + time();
602 $exist++ if (exists $bans{$chan}{$mask} or
603 exists $bans{'*'}{$mask});
604 $bans{$chan}{$mask} = [$expire, time(), 0, $who, $reason];
606 my @chans = ($chan eq "*") ? keys %channels : $chan;
612 foreach (keys %{ $channels{$chan}{''} }) {
613 next unless (exists $nuh{lc $_});
614 next unless ($nuh{lc $_} =~ /^$m$/i);
615 &FIXME("nuh{$_} =~ /$m/");
620 $utime_userfile = time();
631 foreach (keys %bans) {
634 foreach (grep /^\Q$mask\E$/i, keys %{ $bans{$chan} }) {
635 delete $bans{$chan}{$_};
639 &DEBUG("bans: scalar => ".scalar(keys %{ $bans{$chan} }) );
643 $utime_userfile = time();
653 if ( &getUser($user) ) {
663 if (!defined $user) {
664 &WARN("getUser: user == NULL.");
668 if (my @retval = grep /^\Q$user\E$/i, keys %users) {
669 if ($retval[0] ne $user) {
670 &WARN("getUser: retval[0] ne user ($retval[0] ne $user)");
672 my $count = scalar keys %{ $users{$retval[0]} };
673 &DEBUG("count => $count.");
682 my($cmd, $chan, $what, $val) = @_;
684 if ($cmd eq "+chan") {
685 if (exists $chanconf{$chan}) {
686 &pSReply("chan $chan already exists.");
689 $chanconf{$chan}{_time_added} = time();
690 $chanconf{$what}{autojoin} = 1;
692 &pSReply("Joining $chan...");
698 if (!exists $chanconf{$chan}) {
699 &pSReply("no such channel $chan");
706 ### ".chanset +blah 10" -- error.
707 if (defined $what and $what =~ s/^([+-])(\S+)/$2/) {
708 my $state = ($1 eq "+") ? 1 : 0;
709 my $was = $chanconf{$chan}{$what};
711 if ($state) { # add/set.
712 if (defined $was and $was eq "1") {
713 &pSReply("setting $what for $chan already 1.");
717 $was = ($was) ? "; was '$was'" : "";
720 } else { # delete/unset.
722 &pSReply("setting $what for $chan is not set.");
727 &pSReply("setting $what for $chan already 0.");
731 $was = ($was) ? "; was '$was'" : "";
736 &pSReply("Unsetting $what for $chan$was.");
737 delete $chanconf{$chan}{$what};
739 &pSReply("Setting $what for $chan to '$val'$was.");
740 $chanconf{$chan}{$what} = $val;
745 ### ".chanset blah testing"
746 } elsif (defined $val) {
747 my $was = $chanconf{$chan}{$what};
748 if (defined $was and $was eq $val) {
749 &pSReply("setting $what for $chan already '$val'.");
752 $was = ($was) ? "; was '$was'" : "";
753 &pSReply("Setting $what for $chan to '$val'$was.");
755 $chanconf{$chan}{$what} = $val;
761 } else { # read only.
762 if (!defined $what) {
763 &WARN("chanset/DC: what == undefine.");
767 if (exists $chanconf{$chan}{$what}) {
768 &pSReply("$what for $chan is '$chanconf{$chan}{$what}'");
770 &pSReply("$what for $chan is not set.");
775 $utime_chanfile = time();
783 # this is an attempt to fix where an option is loaded but the module
784 # has not loaded. it also can be used for other things.
786 foreach (keys %{ $cache{confvars} }) {
787 my $i = $cache{confvars}{$_};
788 &DEBUG("rehashConfVars: _ => $_");
790 if (/^news$/ and $i) {
791 &loadMyModule("news");
792 delete $cache{confvars}{$_};
795 if (/^uptime$/ and $i) {
796 &loadMyModule("uptime");
797 delete $cache{confvars}{$_};
800 if (/^rootwarn$/i and $i) {
802 delete $cache{confvars}{$_};
806 &DEBUG("end of rehashConfVars");
808 delete $cache{confvars};
814 "limitcheckInterval",
818 ### TODO: finish off this list.
831 # +r - ability to remove factoids
832 # +t - ability to teach factoids
833 # +m - ability to modify factoids
835 # +o - authorised user of bot (like +m on eggdrop)