#
# DynaConfig.pl: Read/Write configuration files dynamically.
#        Author: dms
#       Version: v0.1 (20010120)
#       Created: 20010119
#	   NOTE: Merged from User.pl
#

if (&IsParam("useStrict")) { use strict; }

#####
##### USERFILE CONFIGURATION READER/WRITER
#####

sub readUserFile {
    if (!open IN,"$bot_misc_dir/blootbot.users") {
	&ERROR("cannot read userfile.");
	&closeLog();
	exit 1;
    }

    my $ver = <IN>;
    if ($ver !~ /^#v1/) {
	&ERROR("old or invalid user file found.");
	&closeLog();
	exit 1;	# correct?
    }

    my $nick;
    while (<IN>) {
	chop;

	next if /^$/;
	next if /^#/;

	if (/^--(\S+)[\s\t]+(.*)$/) {		# user: middle entry.
	    my ($what,$val) = ($1,$2);

	    # nice little hack.
	    if ($what eq "HOSTS") {
		$users{$nick}{$1}{$2} = 1;
	    } else {
		$users{$nick}{$1} = $2;
	    }

	} elsif (/^(\S+)$/) {			# user: start entry.
	    $nick	= $1;

	} elsif (/^::(\S+) ignore$/) {		# ignore: start entry.
	    $chan	= $1;

	} elsif (/^- (\S+):+(\d+):(\S+):(\d+):(.*)$/) {
	    ### ignore: middle entry.
	    # $mask, $count?, $whoby, $atime, $comment.
	    my(@array) = ($2,$3,$4,$5);
	    $ignore{$chan}{$1} = \@array;

	} elsif (/^::(\S+) bans$/) {		# bans: start entry.
	    $chan	= $1;

	} elsif (/^- (\S+):+(\d+):+(\d+):(\d+):(\S+):(.*)$/) {
	    ### bans: middle entry.
	    # $btime, $atime, $count, $whoby, $reason.
	    my(@array) = ($2,$3,$4,$5,$6);
	    $bans{$chan}{$1} = \@array;

	} else {				# unknown.
	    &WARN("unknown line: $_");
	}
    }
    close IN;

    &status( sprintf("USERFILE: Loaded: %d users, %d bans, %d ignore",
		scalar(keys %users)-1,
		scalar(keys %bans),
		scalar(keys %ignore),
	)
    );
}

sub writeUserFile {
    if (!open OUT,">$bot_misc_dir/blootbot.users") {
	&ERROR("cannot write to userfile.");
	return;
    }

    my $time		= scalar(localtime);

    print OUT "#v1: blootbot -- $ident -- written $time\n\n";

    ### USER LIST.
    my $cusers	= 0;
    foreach (sort keys %users) {
	my $user = $_;
	$cusers++;
	my $count = scalar keys %{ $users{$user} };
	if (!$count) {
	    &WARN("user $user has no other attributes; skipping.");
	    next;
	}

	print OUT "$user\n";

	foreach (sort keys %{ $users{$user} }) {
	    my $what	= $_;
	    my $val	= $users{$user}{$_};

	    if (ref($val) eq "HASH") {
		foreach (sort keys %{ $users{$user}{$_} }) {
		    print OUT "--$what\t\t$_\n";
		}

	    } else {
		print OUT "--$_\t\t$val\n";
	    }
	}
	print OUT "\n";
    }

    ### BAN LIST.
    my $cbans	= 0;
    foreach (keys %bans) {
	my $chan = $_;
	$cbans++;

	my $count = scalar keys %{ $bans{$chan} };
	if (!$count) {
	    &WARN("bans: chan $chan has no other attributes;
skipping.");
	    next;
	}

	print OUT "::$chan bans\n";
	&DEBUG("::$chan bans");
	foreach (keys %{ $bans{$chan} }) {
	    printf OUT "- %s:+%d:+%d:%d:%s:%s\n",
	    $_, @{ $bans{$chan}{$_} };
	    &DEBUG( sprintf("- %s:+%d:+%d:%d:%s:%s\n",
	    $_, @{ $bans{$chan}{$_} } ));
	}
    }
    print OUT "\n" if ($cbans);

    ### IGNORE LIST.
    my $cignore	= 0;
    foreach (keys %ignore) {
	my $chan = $_;
	$cignore++;

	my $count = scalar keys %{ $ignore{$chan} };
	if (!$count) {
	    &WARN("ignore: chan $chan has no other attributes; skipping.");
	    next;
	}

	print OUT "::$chan ignore\n";
	&DEBUG("::$chan ignore");
	foreach (keys %{ $ignore{$chan} }) {
	    printf OUT "- %s:+%d:%s:%d:%s\n",
	    $_, @{ $bans{$chan}{$_} };
	    &DEBUG( sprintf("- %s:+%d:%s:%d:%s\n",
		    $_, @{ $bans{$chan}{$_} } ));
	}
    }

    close OUT;

    $wtime_userfile = time();
    &status("--- Saved USERFILE ($cusers users; $cbans bans; $cignore ignore) at $time");
    if (defined $msgType and $msgType =~ /^chat$/) {
	&performStrictReply("--- Writing user file...");
    }
}

#####
##### CHANNEL CONFIGURATION READER/WRITER
#####

sub readChanFile {
    if (!open IN,"$bot_misc_dir/blootbot.chan") {
	&ERROR("cannot erad chanfile.");
	return;
    }

    $_ = <IN>;	# version string.

    my $chan;
    while (<IN>) {
	chop;

	next if /^$/;

	if (/^(\S+)\s*$/) {
	    $chan	= $1;
	    next;
	}
	next unless (defined $chan);

	if (/^[\s\t]+\+(\S+)$/) {		# bool, true.
	    $chanconf{$chan}{$1} = 1;

	} elsif (/^[\s\t]+\-(\S+)$/) {		# bool, false.
	    $chanconf{$chan}{$1} = 0;

	} elsif (/^[\s\t]+(\S+)[\ss\t]+(.*)$/) {# what = val.
	    $chanconf{$chan}{$1} = $2;

	} else {
	    &WARN("unknown line: $_") unless (/^#/);
	}
    }
    close IN;

    &status("CHANFILE: Loaded: ".(scalar(keys %chanconf)-1)." chans");
}

sub writeChanFile {
    if (!open OUT,">$bot_misc_dir/blootbot.chan") {
	&ERROR("cannot write chanfile.");
	return;
    }

    my $time		= scalar(localtime);
    print OUT "#v1: blootbot -- $ident -- written $time\n\n";

    foreach (sort keys %chanconf) {
	$chan	= $_;

	print OUT "$chan\n";

	foreach (sort keys %{ $chanconf{$chan} }) {
	    my $val = $chanconf{$chan}{$_};

	    if ($val =~ /^0$/) {		# bool, false.
		print OUT "    -$_\n";

	    } elsif ($val =~ /^1$/) {		# bool, true.
		print OUT "    +$_\n";

	    } else {				# what = val.
		print OUT "    $_ $val\n";

	    }

	}
	print OUT "\n";
    }

    close OUT;

    $wtime_chanfile = time();
    &status("--- Saved CHANFILE (".scalar(keys %chanconf).
		" chans) at $time");

    if (defined $msgType and $msgType =~ /^chat$/) {
	&performStrictReply("--- Writing chan file...");
    }
}

#####
##### USER COMMANDS.
#####

sub IsFlag {
    my $flags = shift;
    my ($ret, $f, $o) = "";

    foreach $f (split //, $users{$userHandle}{FLAGS}) {
	foreach $o ( split //, $flags ) {
	    next unless ($f eq $o);

	    $ret = $f;
	    last;
	}
    }

    $ret;
}

sub verifyUser {
    my ($nick, $lnuh) = @_;
    my ($user,$m);

    $userHandle = "";

    foreach $user (keys %users) {
	next if ($user eq "_default");

	foreach $m (keys %{$users{$user}{HOSTS}}) {
	    $m =~ s/\?/./g;
	    $m =~ s/\*/.*?/g;
	    $m =~ s/([\@\(\)\[\]])/\\$1/g;

	    next unless ($lnuh =~ /^$m$/i);

	    if ($user !~ /^\Q$nick\E$/i) {
		&status("vU: host matched but diff nick ($nick != $user).");
	    }

	    $userHandle = $user;
	    last;
	}

	last if ($userHandle ne "");

	if ($user =~ /^\Q$nick\E$/i) {
	    &status("vU: nick matched but host is not in list ($lnuh).");
	}
    }

    $userHandle ||= "_default";
    $talkWho{$talkchannel} = $who if (defined $talkchannel);
    $talkWho = $who;

    return $userHandle;
}

sub ckpasswd {
    # returns true if arg1 encrypts to arg2
    my ($plain, $encrypted) = @_;
    if ($encrypted eq "") {
	($plain, $encrypted) = split(/\s+/, $plain, 2);
    }
    return 0 unless ($plain ne "" and $encrypted ne "");

    # MD5 // DES. Bobby Billingsley++.
    my $salt = substr($encrypted, 0, 2);
    if ($encrypted =~ /^\$\d\$(\w\w)\$/) {
	$salt = $1;
    }

    return ($encrypted eq crypt($plain, $salt));
}

# mainly for dcc chat... hrm.
sub hasFlag {
    my ($flag) = @_;

    if (&IsFlag($flag) eq $flag) {
	return 1;
    } else {
	&status("DCC CHAT: <$who> $message -- not enough flags.");
	&performStrictReply("error: you do not have enough flags for that. ($flag required)");
	return 0;
    }
}

sub ignoreAdd {
    my($mask,$chan,$expire,$comment) = @_;

    $chan	||= "*";	# global if undefined.
    $comment	||= "";		# optional.
    $expire	||= 0;		# permament.
    my $count	||= 0;

    if ($expire > 0) {
	$expire		= $expire*60 + time();
    } else {
	$expire		= 0;
    }

    $ignore{$chan}{$mask} = [$expire, $count, $who, time(), $comment];

    if (exists $ignore{$chan}{$mask}) {
	return 0;
    } else {
	$utime_userfile = time();
	$ucount_userfile++;

	return 1;
    }
}

sub ignoreDel {
    my($mask)	= @_;
    my @match;

    ### TODO: support wildcards.
    foreach (keys %ignore) {
	my $chan = $_;

	foreach (grep /^\Q$mask\E$/i, keys %{ $ignore{$chan} }) {
	    delete $ignore{$chan}{$mask};
	    push(@match,$chan);
	}

	&DEBUG("iD: scalar => ".scalar(keys %{ $ignore{$chan} }) );
    }

    if (scalar @match) {
	$utime_userfile = time();
	$ucount_userfile++;
    }

    return @match;
}

sub userAdd {
    my($nick,$mask)	= @_;

    if (exists $users{$nick}) {
	return 0;
    }

    $utime_userfile = time();
    $ucount_userfile++;

    $users{$nick}{HOSTS}{$mask} = 1;
    $users{$nick}{FLAGS}	= $users{_default}{FLAGS};

    return 1;
}

sub userDel {
    my($nick)	= @_;

    if (!exists $users{$nick}) {
	return 0;
    }

    $utime_userfile = time();
    $ucount_userfile++;

    delete $users{$nick};

    return 1;
}

sub banAdd {
    my($mask,$chan,$expire,$reason) = @_;

    $chan	||= "*";
    $expire	||= 0;

    if ($expire > 0) {
	$expire		= $expire*60 + time();
    }

    my $exist	= 1;
    $exist++ if (exists $bans{$chan}{$mask} or
		exists $bans{_default}{$mask});
    $bans{$chan}{$mask} = [$expire, 0, $who, time(), $reason];

    if ($exist == 1) {
	$utime_userfile = time();
	$ucount_userfile++;
    }

    return $exist;
}

sub banDel {
    my($mask)	= @_;
    my @match;

    foreach (keys %bans) {
	my $chan	= $_;

	foreach (grep /^\Q$mask\E$/i, keys %{ $bans{$chan} }) {
	    delete $bans{$chan}{$_};
	    push(@match, $chan);
	}

	&DEBUG("bans: scalar => ".scalar(keys %{ $bans{$chan} }) );
    }

    if (scalar @match) {
	$utime_userfile = time();
	$ucount_userfile++;
    }

    return @match;
}

sub IsUser {
    my($user) = @_;

    if ( &getUser($user) ) {
	return 1;
    } else {
	return 0;
    }
}

sub getUser {
    my($user) = @_;

    if (!defined $user) {
	&WARN("getUser: user == NULL.");
	return;
    }

    if (my @retval = grep /^\Q$user\E$/i, keys %users) {
	if ($retval[0] ne $user) {
	    &WARN("getUser: retval[0] ne user ($retval[0] ne $user)");
	}
	my $count = scalar keys %{ $users{$retval[0]} };
	&DEBUG("count => $count.");

	return $retval[0];
    } else {
	return;
    }
}

my @regFlagsChan = (
	"autojoin",
	"freshmeat",
	"limitcheckInterval",
	"limitcheckPlus",
	"allowConv",
	"allowDNS",
### TODO: finish off this list.
);

my @regFlagsUser = ("mno");	# todo...

1;
