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
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/infobot.users";
24 if (! -e $f and -e "$bot_state_dir/blootbot.users") {
25 $f = "$bot_state_dir/blootbot.users";
29 &DEBUG("userfile not found; new fresh run detected.");
33 if ( -f $f and -f "$f~") {
38 &FIXME("rUF: backup file bigger than current file.");
43 &ERROR("Cannot read userfile ($f): $!");
48 undef %users; # clear on reload.
50 undef %ignore; # reset.
54 &ERROR("old or invalid user file found.");
67 if (/^--(\S+)[\s\t]+(.*)$/) { # user: middle entry.
68 my ($what,$val) = ($1,$2);
70 if (!defined $val or $val eq '') {
71 &WARN("$what: val == NULL.");
76 &WARN("DynaConfig: invalid line: $_");
81 if ($what eq 'HOSTS') {
82 $users{$nick}{$what}{$val} = 1;
84 $users{$nick}{$what} = $val;
87 } elsif (/^(\S+)$/) { # user: start entry.
90 } elsif (/^::(\S+) ignore$/) { # ignore: start entry.
94 } elsif (/^- (\S+):\+(\d+):\+(\d+):(\S+):(.*)$/ and $type eq 'ignore') {
95 ### ignore: middle entry.
97 my(@array) = ($2,$3,$4,$5);
98 ### DEBUG purposes only!
99 if ($mask !~ /^$mask{nuh}$/) {
100 &WARN("ignore: mask $mask is invalid.");
103 $ignore{$chan}{$mask} = \@array;
105 } elsif (/^::(\S+) bans$/) { # bans: start entry.
109 } elsif (/^- (\S+):\+(\d+):\+(\d+):(\d+):(\S+):(.*)$/ and $type eq 'bans') {
110 ### bans: middle entry.
111 # $btime, $atime, $count, $whoby, $reason.
112 my(@array) = ($2,$3,$4,$5,$6);
113 $bans{$chan}{$1} = \@array;
116 &WARN("unknown line: $_");
121 &status( sprintf("USERFILE: Loaded: %d users, %d bans, %d ignore",
122 scalar(keys %users)-1,
123 scalar(keys %bans), # ??
124 scalar(keys %ignore), # ??
130 if (!scalar keys %users) {
131 &DEBUG("wUF: nothing to write.");
135 if (!open OUT,">$bot_state_dir/infobot.users") {
136 &ERROR("Cannot write userfile ($bot_state_dir/infobot.users): $!");
140 my $time = scalar(gmtime);
142 print OUT "#v1: infobot -- $ident -- written $time\n\n";
146 foreach (sort keys %users) {
149 my $count = scalar keys %{ $users{$user} };
151 &WARN("user $user has no other attributes; skipping.");
157 foreach (sort keys %{ $users{$user} }) {
159 my $val = $users{$user}{$_};
161 if (ref($val) eq 'HASH') {
162 foreach (sort keys %{ $users{$user}{$_} }) {
163 print OUT "--$what\t\t$_\n";
166 } elsif ($_ eq 'FLAGS') {
167 print OUT "--$_\t\t" . join('', sort split('', $val)) . "\n";
169 print OUT "--$_\t\t$val\n";
177 foreach (keys %bans) {
181 my $count = scalar keys %{ $bans{$chan} };
183 &WARN("bans: chan $chan has no other attributes; skipping.");
187 print OUT "::$chan bans\n";
188 foreach (keys %{ $bans{$chan} }) {
189 # format: bans: mask expire time-added count who-added reason
190 my @array = @{ $bans{$chan}{$_} };
191 if (scalar @array != 5) {
192 &WARN("bans: $chan/$_ is corrupted.");
196 printf OUT "- %s:+%d:+%d:%d:%s:%s\n", $_, @array;
199 print OUT "\n" if ($cbans);
203 foreach (keys %ignore) {
207 my $count = scalar keys %{ $ignore{$chan} };
209 &WARN("ignore: chan $chan has no other attributes; skipping.");
213 ### TODO: use hash instead of array for flexibility?
214 print OUT "::$chan ignore\n";
215 foreach (keys %{ $ignore{$chan} }) {
216 # format: ignore: mask expire time-added who-added reason
217 my @array = @{ $ignore{$chan}{$_} };
218 if (scalar @array != 4) {
219 &WARN("ignore: $chan/$_ is corrupted.");
223 printf OUT "- %s:+%d:+%d:%s:%s\n", $_, @array;
229 $wtime_userfile = time();
230 &status("--- Saved USERFILE ($cusers users; $cbans bans; $cignore ignore) at $time");
231 if (defined $msgType and $msgType =~ /^chat$/) {
232 &performStrictReply("--- Writing user file...");
237 ##### CHANNEL CONFIGURATION READER/WRITER
241 my $f = "$bot_state_dir/infobot.chan";
242 if (-e "$bot_state_dir/infobot.chan" and -e "$bot_state_dir/blootbot.chan") {
243 $f = "$bot_state_dir/blootbot.chan";
245 if ( -f $f and -f "$f~") {
250 &FIXME("rCF: backup file bigger than current file.");
255 &ERROR("Cannot read chanfile ($f): $!");
259 undef %chanconf; # reset.
261 $_ = <IN>; # version string.
268 next if /^\// or /^\;/; # / or ; are comment lines.
274 next unless (defined $chan);
276 if (/^[\s\t]+\+(\S+)$/) { # bool, true.
277 $chanconf{$chan}{$1} = 1;
279 } elsif (/^[\s\t]+\-(\S+)$/) { # bool, false.
280 # although this is supported in run-time configuration.
281 $chanconf{$chan}{$1} = 0;
283 } elsif (/^[\s\t]+(\S+)[\s\t]+(.*)$/) {# what = val.
284 $chanconf{$chan}{$1} = $2;
287 &WARN("unknown line: $_") unless (/^#/);
292 # verify configuration
293 ### TODO: check against valid params.
294 foreach $chan (keys %chanconf) {
295 foreach (keys %{ $chanconf{$chan} }) {
298 &WARN("invalid param: chanconf{$chan}{$_}; removing.");
299 delete $chanconf{$chan}{$_};
300 undef $chanconf{$chan}{$_};
304 &status("CHANFILE: Loaded: ".(scalar(keys %chanconf)-1)." chans");
308 if (!scalar keys %chanconf) {
309 &DEBUG("wCF: nothing to write.");
313 if (!open OUT,">$bot_state_dir/infobot.chan") {
314 &ERROR("Cannot write chanfile ($bot_state_dir/infobot.chan): $!");
318 my $time = scalar(gmtime);
319 print OUT "#v1: infobot -- $ident -- written $time\n\n";
323 ### Process 1: if defined in _default, remove same definition
324 ### from non-default channels.
325 foreach (keys %{ $chanconf{_default} }) {
327 my $val = $chanconf{_default}{$opt};
330 foreach (keys %chanconf) {
333 next if ($chan eq "_default");
334 next unless (exists $chanconf{$chan}{$opt});
335 next unless ($val eq $chanconf{$chan}{$opt});
338 delete $chanconf{$chan}{$opt};
342 &DEBUG("Removed config $opt to @chans since it's defiend in '_default'");
346 ### Process 2: if defined in all chans but _default, set in
347 ### _default and remove all others.
348 my (%optsval, %opts);
349 foreach (keys %chanconf) {
351 next if ($chan eq "_default");
354 foreach (keys %{ $chanconf{$chan} }) {
356 if (exists $optsval{$opt} and $optsval{$opt} eq $chanconf{$chan}{$opt}) {
360 $optsval{$opt} = $chanconf{$chan}{$opt};
365 foreach (keys %opts) {
366 next unless ($opts{$_} > 2);
367 &DEBUG(" opts{$_} => $opts{$_}");
370 ### other optimizations are in UserDCC.pl
374 foreach (sort keys %chanconf) {
379 foreach (sort keys %{ $chanconf{$chan} }) {
380 my $val = $chanconf{$chan}{$_};
382 if ($val =~ /^0$/) { # bool, false.
385 } elsif ($val =~ /^1$/) { # bool, true.
388 } else { # what = val.
389 print OUT " $_ $val\n";
399 $wtime_chanfile = time();
400 &status("--- Saved CHANFILE (".scalar(keys %chanconf).
403 if (defined $msgType and $msgType =~ /^chat$/) {
404 &performStrictReply("--- Writing chan file...");
412 # TODO: support multiple flags.
413 # TODO: return all flags for opers
416 my ($ret, $f, $o) = '';
418 &verifyUser($who, $nuh);
420 foreach $f (split //, $users{$userHandle}{FLAGS}) {
421 foreach $o ( split //, $flags ) {
422 next unless ($f eq $o);
433 my ($nick, $lnuh) = @_;
436 if ($userHandle = $dcc{'CHATvrfy'}{$who}) {
437 &VERB("vUser: cached auth for $who.",2);
443 foreach $user (keys %users) {
444 next if ($user eq "_default");
446 foreach $m (keys %{ $users{$user}{HOSTS} }) {
449 $m =~ s/([\@\(\)\[\]])/\\$1/g;
451 next unless ($lnuh =~ /^$m$/i);
453 if ($user !~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
454 &status("vU: host matched but diff nick ($nick != $user).");
455 $cache{VUSERWARN}{$user} = 1;
462 last if ($userHandle ne '');
464 if ($user =~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user}) {
465 &status("vU: nick matched but host is not in list ($lnuh).");
466 $cache{VUSERWARN}{$user} = 1;
470 $userHandle ||= "_default";
471 # what's talkchannel for?
472 $talkWho{$talkchannel} = $who if (defined $talkchannel);
479 # returns true if arg1 encrypts to arg2
480 my ($plain, $encrypted) = @_;
481 if ($encrypted eq '') {
482 ($plain, $encrypted) = split(/\s+/, $plain, 2);
484 return 0 unless ($plain ne '' and $encrypted ne '');
486 # MD5 // DES. Bobby Billingsley++.
488 if ($encrypted =~ /^(\S{2})/ and length $encrypted == 13) {
490 } elsif ($encrypted =~ /^\$\d\$(\w\w)\$/) {
493 &DEBUG("unknown salt from $encrypted.");
497 return ($encrypted eq crypt($plain, $salt));
500 # mainly for dcc chat... hrm.
504 if (&IsFlag($flag) eq $flag) {
507 &status("DCC CHAT: <$who> $message -- not enough flags.");
508 &performStrictReply("error: you do not have enough flags for that. ($flag required)");
513 # expire is time in minutes
515 my($mask,$chan,$expire,$comment) = @_;
517 $chan ||= '*'; # global if undefined.
518 $comment ||= ''; # optional.
519 $expire ||= 0; # permament.
523 $expire = ($expire*60) + time();
529 $exist++ if (exists $ignore{$chan}{$mask});
531 $ignore{$chan}{$mask} = [$expire, time(), $who, $comment];
533 # TODO: improve this.
535 &status("ignore: Added $mask for $chan to NEVER expire, by $who, for $comment");
537 &status("ignore: Added $mask for $chan to expire $expire mins, by $who, for $comment");
541 $utime_userfile = time();
554 ### TODO: support wildcards.
555 foreach (keys %ignore) {
558 foreach (grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} }) {
559 delete $ignore{$chan}{$mask};
563 &DEBUG("iD: scalar => ".scalar(keys %{ $ignore{$chan} }) );
567 $utime_userfile = time();
575 my($nick,$mask) = @_;
577 if (exists $users{$nick}) {
581 $utime_userfile = time();
584 if (defined $mask and $mask !~ /^\s*$/) {
585 &DEBUG("userAdd: mask => $mask");
586 $users{$nick}{HOSTS}{$mask} = 1;
589 $users{$nick}{FLAGS} ||= $users{_default}{FLAGS};
597 if (!exists $users{$nick}) {
601 $utime_userfile = time();
604 delete $users{$nick};
610 my($mask,$chan,$expire,$reason) = @_;
616 $expire = $expire*60 + time();
620 $exist++ if (exists $bans{$chan}{$mask} or
621 exists $bans{'*'}{$mask});
622 $bans{$chan}{$mask} = [$expire, time(), 0, $who, $reason];
624 my @chans = ($chan eq '*') ? keys %channels : $chan;
630 foreach (keys %{ $channels{$chan}{''} }) {
631 next unless (exists $nuh{lc $_});
632 next unless ($nuh{lc $_} =~ /^$m$/i);
633 &FIXME("nuh{$_} =~ /$m/");
638 $utime_userfile = time();
649 foreach (keys %bans) {
652 foreach (grep /^\Q$mask\E$/i, keys %{ $bans{$chan} }) {
653 delete $bans{$chan}{$_};
657 &DEBUG("bans: scalar => ".scalar(keys %{ $bans{$chan} }) );
661 $utime_userfile = time();
671 if ( &getUser($user) ) {
681 if (!defined $user) {
682 &WARN("getUser: user == NULL.");
686 if (my @retval = grep /^\Q$user\E$/i, keys %users) {
687 if ($retval[0] ne $user) {
688 &WARN("getUser: retval[0] ne user ($retval[0] ne $user)");
690 my $count = scalar keys %{ $users{$retval[0]} };
691 &DEBUG("count => $count.");
700 my($cmd, $chan, $what, $val) = @_;
702 if ($cmd eq "+chan") {
703 if (exists $chanconf{$chan}) {
704 &performStrictReply("chan $chan already exists.");
707 $chanconf{$chan}{_time_added} = time();
708 $chanconf{$chan}{autojoin} = $conn->nick();
710 &performStrictReply("Joining $chan...");
716 if (!exists $chanconf{$chan}) {
717 &performStrictReply("no such channel $chan");
723 if (defined $what and $what =~ s/^([+-])(\S+)/$2/) {
725 ### ".chanset +blah 10" -- error.
727 my $set = ($1 eq "+") ? 1 : 0;
728 my $was = $chanconf{$chan}{$what};
730 if ($set) { # add/set.
731 if (defined $was and $was eq '1') {
732 &performStrictReply("setting $what for $chan already 1.");
738 } else { # delete/unset.
740 &performStrictReply("setting $what for $chan is not set.");
747 # alter for cosmetic (print out) reasons only.
748 $was = (defined $was) ? "; was '$was'" : '';
751 &performStrictReply("Unsetting $what for $chan$was.");
752 delete $chanconf{$chan}{$what};
753 delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
755 &performStrictReply("Setting $what for $chan to '$val'$was.");
756 $chanconf{$chan}{$what} = $val;
757 delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
762 } elsif (defined $val) {
763 ### ".chanset blah testing"
765 my $was = $chanconf{$chan}{$what};
766 if (defined $was and $was eq $val) {
767 &performStrictReply("setting $what for $chan already '$val'.");
770 $was = ($was) ? "; was '$was'" : '';
771 &performStrictReply("Setting $what for $chan to '$val'$was.");
773 $chanconf{$chan}{$what} = $val;
774 delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
778 } else { # read only.
782 if (!defined $what) {
783 &WARN("chanset/DC: what == undefine.");
787 if (exists $chanconf{$chan}{$what}) {
788 &performStrictReply("$what for $chan is '$chanconf{$chan}{$what}'");
790 &performStrictReply("$what for $chan is not set.");
795 $utime_chanfile = time();
803 # this is an attempt to fix where an option is enabled but the module
804 # has been not loaded. it also can be used for other things.
806 foreach (keys %{ $cache{confvars} }) {
807 my $i = $cache{confvars}{$_};
808 &DEBUG("rehashConfVars: _ => $_");
810 if (/^news$/ and $i) {
811 &loadMyModule('News');
812 delete $cache{confvars}{$_};
815 if (/^uptime$/ and $i) {
816 &loadMyModule('Uptime');
817 delete $cache{confvars}{$_};
820 if (/^rootwarn$/i and $i) {
821 &loadMyModule('RootWarn');
822 delete $cache{confvars}{$_};
825 if (/^onjoin$/i and $i) {
826 &loadMyModule('OnJoin');
827 delete $cache{confvars}{$_};
831 &DEBUG("end of rehashConfVars");
833 delete $cache{confvars};
837 # possible chars to include in FLAG
838 'A', # bot administration over /msg
839 # default is only via DCC CHAT
840 'O', # dynamic ops (as on channel). (automatic +o)
842 'a', # ask/request factoid.
843 'm', # modify factoid. (includes renaming)
844 'n', # bot owner, can 'reload'
845 'o', # master of bot (automatic +amrt)
846 # can search on factoid strings shorter than 2 chars
847 # can tell bot to join new channels
848 # can [un]lock factoids
849 'r', # remove factoid.
850 't', # teach/add factoid.
851 's', # Bypass +silent on channels
856 # vim:ts=4:sw=4:expandtab:tw=80