2 # DynaConfig.pl: Read/Write configuration files dynamically.
4 # Version: v0.1 (20010120)
6 # NOTE: Merged from User.pl
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);
19 ##### USERFILE CONFIGURATION READER/WRITER
23 my $f = "$bot_state_dir/blootbot.users";
26 &DEBUG("userfile not found; new fresh run detected.");
30 if ( -f $f and -f "$f~") {
35 &FIXME("rUF: backup file bigger than current file.");
40 &ERROR("Cannot read userfile ($f): $!");
45 undef %users; # clear on reload.
47 undef %ignore; # reset.
50 if ($ver !~ /^#v[12]/) {
51 &ERROR("old or invalid user file found.");
64 if (/^--(\S+)[\s\t]+(.*)$/) { # user: body
65 my ($what,$val) = ($1,$2);
67 if (!defined $val or $val eq "") {
68 &WARN("$what: val == NULL.");
73 &WARN("DynaConfig: invalid line: $_");
77 # hack for attribute support.
78 if ($what =~ /^attr$/) {
79 foreach (split / /, $val) {
80 $users{$nick}{$what}{$_} = 1;
85 if ($what =~ /^HOSTS$/) {
86 $users{$nick}{$what}{$val} = 1;
88 $users{$nick}{$what} = $val;
91 } elsif (/^(\S+)$/) { # user: start.
94 } elsif (/^::(\S+) ignore$/) { # ignore: start.
98 } elsif (/^- (\S+):\+(\d+):\+(\d+):(\S+):(.*)$/ and $type eq "ignore") {
101 my(@array) = ($2,$3,$4,$5);
102 ### DEBUG purposes only!
103 if ($mask !~ /^$mask{nuh}$/) {
104 &WARN("ignore: mask $mask is invalid.");
107 $ignore{$chan}{$mask} = \@array;
109 } elsif (/^::(\S+) bans$/) { # bans: start.
113 } elsif (/^- (\S+):\+(\d+):\+(\d+):(\d+):(\S+):(.*)$/ and $type eq "bans") {
115 # $btime, $atime, $count, $whoby, $reason.
116 my(@array) = ($2,$3,$4,$5,$6);
117 $bans{$chan}{$1} = \@array;
120 &WARN("unknown line: $_");
125 &status( sprintf("USERFILE: Loaded: %d users, %d bans, %d ignore",
126 scalar(keys %users)-1,
127 scalar(keys %bans), # ??
128 scalar(keys %ignore), # ??
134 if (!scalar keys %users) {
135 &DEBUG("wUF: nothing to write.");
139 if (!open OUT,">$bot_state_dir/blootbot.users") {
140 &ERROR("Cannot write userfile ($bot_state_dir/blootbot.users): $!");
144 my $time = scalar(gmtime);
146 print OUT "#v1: blootbot -- $ident -- written $time\n\n";
150 foreach (sort keys %users) {
153 my $count = scalar keys %{ $users{$user} };
155 &WARN("user $user has no other attributes; skipping.");
161 foreach (sort keys %{ $users{$user} }) {
162 # todo: rename what to key?
164 my $val = $users{$user}{$what};
166 if (ref($val) eq "SCALAR") {
167 print OUT "--$what\t\t$val\n";
171 next unless (ref($val) eq "HASH");
173 if ($what ne "attr") {
174 foreach (sort keys %{ $users{$user}{$what} }) {
175 print OUT "--$what\t\t$_\n";
180 # disabled until confirmed to work flawlessly.
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?
189 my $attr = shift(@attr);
190 my $_str = $str." ".$attr;
193 if (length $str < 60 && length $_str > 60) {
197 $print++ if (!@attr);
200 next unless ($print);
202 $str = "--$what\t\t";
210 foreach (keys %bans) {
214 my $count = scalar keys %{ $bans{$chan} };
216 &WARN("bans: chan $chan has no other attributes; skipping.");
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.");
229 printf OUT "- %s:+%d:+%d:%d:%s:%s\n", $_, @array;
232 print OUT "\n" if ($cbans);
236 foreach (keys %ignore) {
240 my $count = scalar keys %{ $ignore{$chan} };
242 &WARN("ignore: chan $chan has no other attributes; skipping.");
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.");
256 printf OUT "- %s:+%d:+%d:%s:%s\n", $_, @array;
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...");
270 ##### CHANNEL CONFIGURATION READER/WRITER
274 my $f = "$bot_state_dir/blootbot.chan";
275 if ( -f $f and -f "$f~") {
280 &FIXME("rCF: backup file bigger than current file.");
285 &ERROR("Cannot read chanfile ($f): $!");
289 undef %chanconf; # reset.
291 $_ = <IN>; # version string.
298 next if /^\// or /^\;/; # / or ; are comment lines.
304 next unless (defined $chan);
306 if (/^[\s\t]+\+(\S+)$/) { # bool, true.
307 $chanconf{$chan}{$1} = 1;
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;
315 } elsif (/^[\s\t]+(\S+)[\ss\t]+(.*)$/) {# what = val.
316 $chanconf{$chan}{$1} = $2;
319 &WARN("unknown line: $_") unless (/^#/);
324 # verify configuration
325 ### TODO: check against valid params.
326 foreach $chan (keys %chanconf) {
327 foreach (keys %{ $chanconf{$chan} }) {
330 &WARN("invalid param: chanconf{$chan}{$_}; removing.");
331 delete $chanconf{$chan}{$_};
332 undef $chanconf{$chan}{$_};
336 delete $cache{negative};
338 &status("CHANFILE: Loaded: ".(scalar(keys %chanconf)-1)." chans");
342 if (!scalar keys %chanconf) {
343 &DEBUG("wCF: nothing to write.");
347 if (!open OUT,">$bot_state_dir/blootbot.chan") {
348 &ERROR("Cannot write chanfile ($bot_state_dir/blootbot.chan): $!");
352 my $time = scalar(gmtime);
353 print OUT "#v1: blootbot -- $ident -- written $time\n\n";
357 ### Process 1: if defined in _default, remove same definition
358 ### from non-default channels.
359 foreach (keys %{ $chanconf{_default} }) {
361 my $val = $chanconf{_default}{$opt};
364 foreach (keys %chanconf) {
367 next if ($chan eq "_default");
368 next unless (exists $chanconf{$chan}{$opt});
369 next unless ($val eq $chanconf{$chan}{$opt});
372 delete $chanconf{$chan}{$opt};
376 &DEBUG("Removed config $opt to @chans since it's defiend in '_default'");
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) {
385 next if ($chan eq "_default");
388 foreach (keys %{ $chanconf{$chan} }) {
390 if (exists $optsval{$opt} and $optsval{$opt} eq $chanconf{$chan}{$opt}) {
394 $optsval{$opt} = $chanconf{$chan}{$opt};
399 foreach (keys %opts) {
400 next unless ($opts{$_} > 2);
401 &DEBUG(" opts{$_} => $opts{$_}");
404 ### other optimizations are in UserDCC.pl
408 foreach (sort keys %chanconf) {
413 foreach (sort keys %{ $chanconf{$chan} }) {
414 my $val = $chanconf{$chan}{$_};
416 if ($val =~ /^0$/) { # bool, false.
419 } elsif ($val =~ /^1$/) { # bool, true.
422 } else { # what = val.
423 print OUT " $_ $val\n";
433 $wtime_chanfile = time();
434 &status("--- Saved CHANFILE (".scalar(keys %chanconf).
437 if (defined $msgType and $msgType =~ /^chat$/) {
438 &pSReply("--- Writing chan file...");
446 # todo: support multiple flags, eg: "+o-m"
449 my ($ret, $f, $o) = "";
451 &verifyUser($who, $nuh);
453 # userfile v2 support:
454 if ($users{$userHandle}{attr}) {
455 # todo: fix for multiple flags/attr
456 my $attr = $flag2attr{$flags};
458 &WARN("IsFlag: !flag2attr{$flags}");
461 return 1 if ($users{$userHandle}{attr}{$attr});
465 foreach $f (split //, $users{$userHandle}{FLAGS}) {
466 foreach $o (split //, $flags) {
467 next unless ($f eq $o);
478 my ($nick, $lnuh) = @_;
481 if ($userHandle = $dcc{'CHATvrfy'}{$who}) {
482 &VERB("vUser: cached auth for $who.",2);
488 foreach $user (keys %users) {
489 next if ($user eq "_default");
491 foreach $m (keys %{ $users{$user}{HOSTS} }) {
494 $m =~ s/([\@\(\)\[\]])/\\$1/g;
496 next unless ($lnuh =~ /^$m$/i);
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;
507 last if ($userHandle ne "");
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;
515 $userHandle ||= "_default";
516 # what's talkchannel for?
517 $talkWho{$talkchannel} = $who if (defined $talkchannel);
524 # returns true if arg1 encrypts to arg2
525 my ($plain, $encrypted) = @_;
526 if ($encrypted eq "") {
527 ($plain, $encrypted) = split(/\s+/, $plain, 2);
529 return 0 unless ($plain ne "" and $encrypted ne "");
531 # MD5 // DES. Bobby Billingsley++.
533 if ($encrypted =~ /^(\S{2})/ and length $encrypted == 13) {
535 } elsif ($encrypted =~ /^\$\d\$(\w\w)\$/) {
538 &DEBUG("unknown salt from $encrypted.");
542 return ($encrypted eq crypt($plain, $salt));
545 # mainly for dcc chat... hrm.
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)");
558 # expire is time in minutes
560 my($mask,$chan,$expire,$comment) = @_;
562 $chan ||= "*"; # global if undefined.
563 $comment ||= ""; # optional.
564 $expire ||= 0; # permament.
568 $expire = ($expire*60) + time();
574 $exist++ if (exists $ignore{$chan}{$mask});
576 $ignore{$chan}{$mask} = [$expire, time(), $who, $comment];
578 # todo: improve this.
580 &status("ignore: Added $mask for $chan to NEVER expire, by $who, for $comment");
582 &status("ignore: Added $mask for $chan to expire $expire mins, by $who, for $comment");
586 $utime_userfile = time();
599 ### TODO: support wildcards.
600 foreach (keys %ignore) {
603 foreach (grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} }) {
604 delete $ignore{$chan}{$mask};
608 &DEBUG("iD: scalar => ".scalar(keys %{ $ignore{$chan} }) );
612 $utime_userfile = time();
620 my($nick,$mask) = @_;
622 return 0 if (exists $users{$nick});
624 $utime_userfile = time();
627 if (defined $mask and $mask !~ /^\s*$/) {
628 &DEBUG("userAdd: mask => $mask");
629 $users{$nick}{HOSTS}{$mask} = 1;
632 $users{$nick}{FLAGS} ||= $users{_default}{FLAGS};
640 return 0 if (!exists $users{$nick});
642 $utime_userfile = time();
645 delete $users{$nick};
651 my($mask,$chan,$expire,$reason) = @_;
654 $expire = $expire*60 + time() if ($expire > 0);
657 $exist++ if (exists $bans{$chan}{$mask} or exists $bans{'*'}{$mask});
658 $bans{$chan}{$mask} = [$expire, time(), 0, $who, $reason];
660 my @chans = ($chan eq "*") ? keys %channels : $chan;
666 foreach (keys %{ $channels{$chan}{''} }) {
667 next unless (exists $nuh{lc $_});
668 next unless ($nuh{lc $_} =~ /^$m$/i);
669 &FIXME("nuh{$_} =~ /$m/");
674 $utime_userfile = time();
685 foreach (keys %bans) {
688 foreach (grep /^\Q$mask\E$/i, keys %{ $bans{$chan} }) {
689 delete $bans{$chan}{$_};
693 &DEBUG("bans: scalar => ".scalar(keys %{ $bans{$chan} }) );
697 $utime_userfile = time();
707 if ( &getUser($user) ) {
717 if (!defined $user) {
718 &WARN("getUser: user == NULL.");
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)");
726 my $count = scalar keys %{ $users{$retval[0]} };
727 &DEBUG("count => $count.");
736 my($cmd, $chan, $what, $val) = @_;
738 if ($cmd eq "+chan") {
739 if (exists $chanconf{$chan}) {
740 &pSReply("chan $chan already exists.");
743 $chanconf{$chan}{_time_added} = time();
744 $chanconf{$chan}{autojoin} = 1;
746 &pSReply("Joining $chan...");
752 if (!exists $chanconf{$chan}) {
753 &pSReply("no such channel $chan");
759 if (defined $what and $what =~ s/^([+-])(\S+)/$2/) {
761 ### ".chanset +blah 10" -- error.
763 my $state = ($1 eq "+") ? 1 : 0;
764 my $was = $chanconf{$chan}{$what};
766 if ($state) { # add/set.
767 if (defined $was and $was eq "1") {
768 &pSReply("setting $what for $chan already 1.");
774 } else { # delete/unset.
776 &pSReply("setting $what for $chan is not set.");
781 &pSReply("setting $what for $chan already 0.");
788 # alter for cosmetic (print out) reasons only.
789 $was = ($was) ? "; was '$was'" : "";
792 &pSReply("Unsetting $what for $chan$was.");
793 delete $chanconf{$chan}{$what};
795 &pSReply("Setting $what for $chan to '$val'$was.");
796 $chanconf{$chan}{$what} = $val;
801 } elsif (defined $val) {
802 ### ".chanset blah testing"
804 my $was = $chanconf{$chan}{$what};
805 if (defined $was and $was eq $val) {
806 &pSReply("setting $what for $chan already '$val'.");
809 $was = ($was) ? "; was '$was'" : "";
810 &pSReply("Setting $what for $chan to '$val'$was.");
812 $chanconf{$chan}{$what} = $val;
816 } else { # read only.
820 if (!defined $what) {
821 &WARN("chanset/DC: what == undefine.");
825 if (exists $chanconf{$chan}{$what}) {
826 &pSReply("$what for $chan is '$chanconf{$chan}{$what}'");
828 &pSReply("$what for $chan is not set.");
833 $utime_chanfile = time();
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.
844 foreach (keys %{ $cache{confvars} }) {
845 my $i = $cache{confvars}{$_};
846 &DEBUG("rehashConfVars: _ => $_");
848 if (/^news$/ and $i) {
849 &loadMyModule("news");
850 delete $cache{confvars}{$_};
853 if (/^uptime$/ and $i) {
854 &loadMyModule("uptime");
855 delete $cache{confvars}{$_};
858 if (/^rootwarn$/i and $i) {
860 delete $cache{confvars}{$_};
864 &DEBUG("end of rehashConfVars");
866 delete $cache{confvars};
869 sub convertUserFileVer2 {
870 foreach (keys %users) {
872 my $flags = $users{$handle}{FLAGS};
874 &WARN("cUFV2: handle $handle has no flags!");
879 foreach (split //, $flags) {
881 my $attr = $flag2attr{$flag};
883 &DEBUG("cUFV2: handle=$handle: flag=$flag does not exist.");
887 $users{$handle}{attr}{$attr} = 1;
891 # uncomment to enable attribute conversion support.
892 # delete $users{$handle}{FLAGS};
896 # support more than one attribute?
898 m => "modify_factoid",
899 r => "delete_factoid",
904 A => "admin_over_msg",