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);
20 # possible chars to include in FLAG
21 'A', # bot administration over /msg
22 # default is only via DCC CHAT
23 'O', # dynamic ops (as on channel). (automatic +o)
25 'a', # ask/request factoid.
26 'm', # modify all factoids. (includes renaming)
27 'M', # modify own factoids. (includes renaming)
28 'n', # bot owner, can 'reload'
29 'o', # master of bot (automatic +amrt)
30 # can search on factoid strings shorter than 2 chars
31 # can tell bot to join new channels
32 # can [un]lock factoids
33 'r', # remove factoid.
34 't', # teach/add factoid.
35 's', # Bypass +silent on channels
39 ##### USERFILE CONFIGURATION READER/WRITER
43 my $f = "$bot_state_dir/infobot.users";
44 if (! -e $f and -e "$bot_state_dir/blootbot.users") {
45 $f = "$bot_state_dir/blootbot.users";
49 &DEBUG('userfile not found; new fresh run detected.');
53 if ( -f $f and -f "$f~" ) {
57 if ( $s2 > $s1 * 3 ) {
58 &FIXME('rUF: backup file bigger than current file.');
63 &ERROR("Cannot read userfile ($f): $!");
68 undef %users; # clear on reload.
70 undef %ignore; # reset.
73 if ( $ver !~ /^#v1/ ) {
74 &ERROR('old or invalid user file found.');
87 if (/^--(\S+)[\s\t]+(.*)$/) { # user: middle entry.
88 my ( $what, $val ) = ( $1, $2 );
90 if ( !defined $val or $val eq '' ) {
91 &WARN("$what: val == NULL.");
95 if ( !defined $nick ) {
96 &WARN("DynaConfig: invalid line: $_");
101 if ( $what eq 'HOSTS' ) {
102 $users{$nick}{$what}{$val} = 1;
105 $users{$nick}{$what} = $val;
109 elsif (/^(\S+)$/) { # user: start entry.
113 elsif (/^::(\S+) ignore$/) { # ignore: start entry.
118 elsif ( /^- (\S+):\+(\d+):\+(\d+):(\S+):(.*)$/ and $type eq 'ignore' ) {
119 ### ignore: middle entry.
121 my (@array) = ( $2, $3, $4, $5 );
122 ### DEBUG purposes only!
123 if ( $mask !~ /^$mask{nuh}$/ ) {
124 &WARN("ignore: mask $mask is invalid.");
127 $ignore{$chan}{$mask} = \@array;
130 elsif (/^::(\S+) bans$/) { # bans: start entry.
135 elsif ( /^- (\S+):\+(\d+):\+(\d+):(\d+):(\S+):(.*)$/
136 and $type eq 'bans' )
138 ### bans: middle entry.
139 # $btime, $atime, $count, $whoby, $reason.
140 my (@array) = ( $2, $3, $4, $5, $6 );
141 $bans{$chan}{$1} = \@array;
145 &WARN("unknown line: $_");
152 'USERFILE: Loaded: %d users, %d bans, %d ignore',
153 scalar( keys %users ) - 1,
154 scalar( keys %bans ), # ??
155 scalar( keys %ignore ), # ??
161 if ( !scalar keys %users ) {
162 &DEBUG('wUF: nothing to write.');
166 if ( !open OUT, ">$bot_state_dir/infobot.users" ) {
167 &ERROR("Cannot write userfile ($bot_state_dir/infobot.users): $!");
171 my $time = scalar(gmtime);
173 print OUT "#v1: infobot -- $ident -- written $time\n\n";
177 foreach ( sort keys %users ) {
180 my $count = scalar keys %{ $users{$user} };
182 &WARN("user $user has no other attributes; skipping.");
188 foreach ( sort keys %{ $users{$user} } ) {
190 my $val = $users{$user}{$_};
192 if ( ref($val) eq 'HASH' ) {
193 foreach ( sort keys %{ $users{$user}{$_} } ) {
194 print OUT "--$what\t\t$_\n";
198 elsif ( $_ eq 'FLAGS' ) {
200 . join( '', sort split( '', $val ) ) . "\n";
203 print OUT "--$_\t\t$val\n";
211 foreach ( keys %bans ) {
215 my $count = scalar keys %{ $bans{$chan} };
217 &WARN("bans: chan $chan has no other attributes; skipping.");
221 print OUT "::$chan bans\n";
222 foreach ( keys %{ $bans{$chan} } ) {
224 # format: bans: mask expire time-added count who-added reason
225 my @array = @{ $bans{$chan}{$_} };
226 if ( scalar @array != 5 ) {
227 &WARN("bans: $chan/$_ is corrupted.");
231 printf OUT "- %s:+%d:+%d:%d:%s:%s\n", $_, @array;
234 print OUT "\n" if ($cbans);
238 foreach ( keys %ignore ) {
242 my $count = scalar keys %{ $ignore{$chan} };
244 &WARN("ignore: chan $chan has no other attributes; skipping.");
248 ### TODO: use hash instead of array for flexibility?
249 print OUT "::$chan ignore\n";
250 foreach ( keys %{ $ignore{$chan} } ) {
252 # format: ignore: mask expire time-added who-added reason
253 my @array = @{ $ignore{$chan}{$_} };
254 if ( scalar @array != 4 ) {
255 &WARN("ignore: $chan/$_ is corrupted.");
259 printf OUT "- %s:+%d:+%d:%s:%s\n", $_, @array;
265 $wtime_userfile = time();
267 "--- Saved USERFILE ($cusers users; $cbans bans; $cignore ignore) at $time"
269 if ( defined $msgType and $msgType =~ /^chat$/ ) {
270 &performStrictReply('--- Writing user file...');
275 ##### CHANNEL CONFIGURATION READER/WRITER
279 my $f = "$bot_state_dir/infobot.chan";
280 if (-e "$bot_state_dir/infobot.chan" and -e "$bot_state_dir/blootbot.chan") {
281 $f = "$bot_state_dir/blootbot.chan";
283 if ( -f $f and -f "$f~" ) {
287 if ( $s2 > $s1 * 3 ) {
288 &FIXME('rCF: backup file bigger than current file.');
292 if ( !open IN, $f ) {
293 &ERROR("Cannot read chanfile ($f): $!");
297 undef %chanconf; # reset.
299 $_ = <IN>; # version string.
306 next if /^\// or /^\;/; # / or ; are comment lines.
312 next unless ( defined $chan );
314 if (/^[\s\t]+\+(\S+)$/) { # bool, true.
315 $chanconf{$chan}{$1} = 1;
318 elsif (/^[\s\t]+\-(\S+)$/) { # bool, false.
319 # although this is supported in run-time configuration.
320 $chanconf{$chan}{$1} = 0;
323 elsif (/^[\s\t]+(\S+)[\s\t]+(.*)$/) { # what = val.
324 $chanconf{$chan}{$1} = $2;
328 &WARN("unknown line: $_") unless (/^#/);
333 # verify configuration
334 ### TODO: check against valid params.
335 foreach $chan ( keys %chanconf ) {
336 foreach ( keys %{ $chanconf{$chan} } ) {
339 &WARN("invalid param: chanconf{$chan}{$_}; removing.");
340 delete $chanconf{$chan}{$_};
341 undef $chanconf{$chan}{$_};
346 'CHANFILE: Loaded: ' . ( scalar( keys %chanconf ) - 1 ) . ' chans' );
350 if ( !scalar keys %chanconf ) {
351 &DEBUG('wCF: nothing to write.');
355 if ( !open OUT, ">$bot_state_dir/infobot.chan" ) {
356 &ERROR("Cannot write chanfile ($bot_state_dir/infobot.chan): $!");
360 my $time = scalar(gmtime);
361 print OUT "#v1: infobot -- $ident -- written $time\n\n";
365 ### Process 1: if defined in _default, remove same definition
366 ### from non-default channels.
367 foreach ( keys %{ $chanconf{_default} } ) {
369 my $val = $chanconf{_default}{$opt};
372 foreach ( keys %chanconf ) {
375 next if ( $chan eq '_default' );
376 next unless ( exists $chanconf{$chan}{$opt} );
377 next unless ( $val eq $chanconf{$chan}{$opt} );
379 push( @chans, $chan );
380 delete $chanconf{$chan}{$opt};
383 if ( scalar @chans ) {
385 "Removed config $opt to @chans since it's defiend in '_default'"
390 ### Process 2: if defined in all chans but _default, set in
391 ### _default and remove all others.
392 my ( %optsval, %opts );
393 foreach ( keys %chanconf ) {
395 next if ( $chan eq '_default' );
398 foreach ( keys %{ $chanconf{$chan} } ) {
400 if ( exists $optsval{$opt}
401 and $optsval{$opt} eq $chanconf{$chan}{$opt} )
406 $optsval{$opt} = $chanconf{$chan}{$opt};
411 foreach ( keys %opts ) {
412 next unless ( $opts{$_} > 2 );
413 &DEBUG(" opts{$_} => $opts{$_}");
416 ### other optimizations are in UserDCC.pl
420 foreach ( sort keys %chanconf ) {
425 foreach ( sort keys %{ $chanconf{$chan} } ) {
426 my $val = $chanconf{$chan}{$_};
428 if ( $val =~ /^0$/ ) { # bool, false.
432 elsif ( $val =~ /^1$/ ) { # bool, true.
437 print OUT " $_ $val\n";
447 $wtime_chanfile = time();
448 &status('--- Saved CHANFILE ('
449 . scalar( keys %chanconf )
450 . " chans) at $time" );
452 if ( defined $msgType and $msgType =~ /^chat$/ ) {
453 &performStrictReply('--- Writing chan file...');
461 # TODO: support multiple flags.
462 # TODO: return all flags for opers
465 my ( $ret, $f, $o ) = '';
467 &verifyUser( $who, $nuh );
469 foreach $f ( split //, $users{$userHandle}{FLAGS} ) {
470 foreach $o ( split //, $flags ) {
471 next unless ( $f eq $o );
482 my ( $nick, $lnuh ) = @_;
485 if ( $userHandle = $dcc{'CHATvrfy'}{$who} ) {
486 &VERB( "vUser: cached auth for $who.", 2 );
492 foreach $user ( keys %users ) {
493 next if ( $user eq '_default' );
495 foreach $m ( keys %{ $users{$user}{HOSTS} } ) {
498 $m =~ s/([\@\(\)\[\]])/\\$1/g;
500 next unless ( $lnuh =~ /^$m$/i );
502 if ( $user !~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user} )
504 &status("vU: host matched but diff nick ($nick != $user).");
505 $cache{VUSERWARN}{$user} = 1;
512 last if ( $userHandle ne '' );
514 if ( $user =~ /^\Q$nick\E$/i and !exists $cache{VUSERWARN}{$user} ) {
515 &status("vU: nick matched but host is not in list ($lnuh).");
516 $cache{VUSERWARN}{$user} = 1;
520 $userHandle ||= '_default';
522 # what's talkchannel for?
523 $talkWho{$talkchannel} = $who if ( defined $talkchannel );
531 # returns true if arg1 encrypts to arg2
532 my ( $plain, $encrypted ) = @_;
533 if ( $encrypted eq '' ) {
534 ( $plain, $encrypted ) = split( /\s+/, $plain, 2 );
536 return 0 unless ( $plain ne '' and $encrypted ne '' );
538 # MD5 // DES. Bobby Billingsley++.
540 if ( $encrypted =~ /^(\S{2})/ and length $encrypted == 13 ) {
543 elsif ( $encrypted =~ /^\$\d\$(\w\w)\$/ ) {
547 &DEBUG("unknown salt from $encrypted.");
551 return ( $encrypted eq crypt( $plain, $salt ) );
554 # mainly for dcc chat... hrm.
558 if ( &IsFlag($flag) eq $flag ) {
562 &status("DCC CHAT: <$who> $message -- not enough flags.");
564 "error: you do not have enough flags for that. ($flag required)");
569 # expire is time in minutes
571 my ( $mask, $chan, $expire, $comment ) = @_;
573 $chan ||= '*'; # global if undefined.
574 $comment ||= ''; # optional.
575 $expire ||= 0; # permament.
579 $expire = ( $expire * 60 ) + time();
586 $exist++ if ( exists $ignore{$chan}{$mask} );
588 $ignore{$chan}{$mask} = [ $expire, time(), $who, $comment ];
590 # TODO: improve this.
591 if ( $expire == 0 ) {
593 "ignore: Added $mask for $chan to NEVER expire, by $who, for $comment"
598 "ignore: Added $mask for $chan to expire $expire mins, by $who, for $comment"
603 $utime_userfile = time();
617 ### TODO: support wildcards.
618 foreach ( keys %ignore ) {
621 foreach ( grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} } ) {
622 delete $ignore{$chan}{$mask};
623 push( @match, $chan );
626 &DEBUG( 'iD: scalar => ' . scalar( keys %{ $ignore{$chan} } ) );
629 if ( scalar @match ) {
630 $utime_userfile = time();
638 my ( $nick, $mask ) = @_;
640 if ( exists $users{$nick} ) {
644 $utime_userfile = time();
647 if ( defined $mask and $mask !~ /^\s*$/ ) {
648 &DEBUG("userAdd: mask => $mask");
649 $users{$nick}{HOSTS}{$mask} = 1;
652 $users{$nick}{FLAGS} ||= $users{_default}{FLAGS};
660 if ( !exists $users{$nick} ) {
664 $utime_userfile = time();
667 delete $users{$nick};
673 my ( $mask, $chan, $expire, $reason ) = @_;
679 $expire = $expire * 60 + time();
683 $exist++ if ( exists $bans{$chan}{$mask}
684 or exists $bans{'*'}{$mask} );
685 $bans{$chan}{$mask} = [ $expire, time(), 0, $who, $reason ];
687 my @chans = ( $chan eq '*' ) ? keys %channels : $chan;
693 foreach ( keys %{ $channels{$chan}{''} } ) {
694 next unless ( exists $nuh{ lc $_ } );
695 next unless ( $nuh{ lc $_ } =~ /^$m$/i );
696 &FIXME("nuh{$_} =~ /$m/");
701 $utime_userfile = time();
712 foreach ( keys %bans ) {
715 foreach ( grep /^\Q$mask\E$/i, keys %{ $bans{$chan} } ) {
716 delete $bans{$chan}{$_};
717 push( @match, $chan );
720 &DEBUG( 'bans: scalar => ' . scalar( keys %{ $bans{$chan} } ) );
723 if ( scalar @match ) {
724 $utime_userfile = time();
734 if ( &getUser($user) ) {
745 if ( !defined $user ) {
746 &WARN('getUser: user == NULL.');
750 if ( my @retval = grep /^\Q$user\E$/i, keys %users ) {
751 if ( $retval[0] ne $user ) {
752 &WARN("getUser: retval[0] ne user ($retval[0] ne $user)");
754 my $count = scalar keys %{ $users{ $retval[0] } };
755 &DEBUG("count => $count.");
765 my ( $cmd, $chan, $what, $val ) = @_;
767 if ( $cmd eq 'chanadd' ) {
768 if ( exists $chanconf{$chan} ) {
769 &performStrictReply("chan $chan already exists.");
772 $chanconf{$chan}{_time_added} = time();
773 $chanconf{$chan}{autojoin} = $conn->nick();
775 &performStrictReply("Joining $chan...");
781 if ( !exists $chanconf{$chan} ) {
782 &performStrictReply("no such channel $chan");
788 if ( defined $what and $what =~ s/^([+-])(\S+)/$2/ ) {
790 ### '.chanset +blah 10' -- error.
792 my $set = ( $1 eq '+' ) ? 1 : 0;
793 my $was = $chanconf{$chan}{$what};
795 if ($set) { # add/set.
796 if ( defined $was and $was eq '1' ) {
797 &performStrictReply("setting $what for $chan already 1.");
804 else { # delete/unset.
805 if ( !defined $was ) {
806 &performStrictReply("setting $what for $chan is not set.");
813 # alter for cosmetic (print out) reasons only.
814 $was = ( defined $was ) ? "; was '$was'" : '';
817 &performStrictReply("Unsetting $what for $chan$was.");
818 delete $chanconf{$chan}{$what};
819 delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
822 &performStrictReply("Setting $what for $chan to '$val'$was.");
823 $chanconf{$chan}{$what} = $val;
824 delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
830 elsif ( defined $val ) {
831 ### '.chanset blah testing'
833 my $was = $chanconf{$chan}{$what};
834 if ( defined $was and $was eq $val ) {
835 &performStrictReply("setting $what for $chan already '$val'.");
838 $was = ($was) ? "; was '$was'" : '';
839 &performStrictReply("Setting $what for $chan to '$val'$was.");
841 $chanconf{$chan}{$what} = $val;
842 delete $cache{ircTextCounters} if $what eq 'ircTextCounters';
851 if ( !defined $what ) {
852 &WARN('chanset/DC: what == undefine.');
856 if ( exists $chanconf{$chan}{$what} ) {
857 &performStrictReply("$what for $chan is '$chanconf{$chan}{$what}'");
860 &performStrictReply("$what for $chan is not set.");
865 $utime_chanfile = time();
874 # this is an attempt to fix where an option is enabled but the module
875 # has been not loaded. it also can be used for other things.
877 foreach ( keys %{ $cache{confvars} } ) {
878 my $i = $cache{confvars}{$_};
879 &DEBUG("rehashConfVars: _ => $_");
881 if ( /^news$/ and $i ) {
882 &loadMyModule('News');
883 delete $cache{confvars}{$_};
886 if ( /^uptime$/ and $i ) {
887 &loadMyModule('Uptime');
888 delete $cache{confvars}{$_};
891 if ( /^rootwarn$/i and $i ) {
892 &loadMyModule('RootWarn');
893 delete $cache{confvars}{$_};
896 if ( /^onjoin$/i and $i ) {
897 &loadMyModule('OnJoin');
898 delete $cache{confvars}{$_};
902 &DEBUG('end of rehashConfVars');
904 delete $cache{confvars};
909 # vim:ts=4:sw=4:expandtab:tw=80