###############################################################################
# Security.pl                                                                 #
###############################################################################
# YaBB: Yet another Bulletin Board                                            #
# Open-Source Community Software for Webmasters                               #
# Version:        YaBB 2.2.1                                                  #
# Packaged:       March 5, 2008                                               #
# Distributed by: http://www.yabbforum.com                                    #
# =========================================================================== #
# Copyright (c) 2000-2008 YaBB (www.yabbforum.com) - All Rights Reserved.     #
# Software by:  The YaBB Development Team                                     #
#               with assistance from the YaBB community.                      #
# Sponsored by: Xnull Internet Media, Inc. - http://www.ximinc.com            #
#               Your source for web hosting, web design, and domains.         #
###############################################################################

$securityplver = 'YaBB 2.2.1 $Revision: 1.13.2.4 $';
if ($action eq 'detailedversion') { return 1; }

# The details of that silly code are irrelevant.
# - Tim Peters, 4 Mar 1992

$yabb = (-e "$boarddir/nph-YaBB.$yyext") ? "nph-YaBB" : "YaBB";
$scripturl = qq~$boardurl/$yabb.$yyext~;
$adminfile = (-e "$boarddir/nph-AdminIndex.$yyaext") ? "nph-AdminIndex" : "AdminIndex";
$adminurl = "$boardurl/$adminfile.$yyaext";

# BIG board check
if ($INFO{'board'}  =~ m~/~) { ($INFO{'board'},  $INFO{'start'}) = split('/', $INFO{'board'}); }
if ($INFO{'num'}    =~ m~/~) { ($INFO{'num'},    $INFO{'start'}) = split('/', $INFO{'num'}); }
if ($INFO{'letter'} =~ m~/~) { ($INFO{'letter'}, $INFO{'start'}) = split('/', $INFO{'letter'}); }
if ($INFO{'thread'} =~ m~/~) { ($INFO{'thread'}, $INFO{'start'}) = split('/', $INFO{'thread'}); }

if (-d "$uploaddir") { $fa_ok = 1; } else { $fa_ok = 0; }

$currentboard = $INFO{'board'};
$curnum       = $INFO{'num'};

# strip off any non numeric values to avoid exploitation
$curnum =~ s/(\D*)//g;
if (!$currentboard || ($currentboard && $curnum)) {
	&MessageTotals('load', $curnum);
	$currentboard = ${$curnum}{'board'};
}

# Updates profile with current IP, if changed from last IP
# Will only actually update the file when .vars is being updated anyway
# Saves extra load on server
if (${$uid.$username}{'lastips'} !~ /$user_ip/) {
	($ip_one, $ip_two, undef) = split(/\|/, ${$uid.$username}{'lastips'});
	${$uid.$username}{'lastips'} = "$user_ip|$ip_one|$ip_two";
}


if ($curnum != '' && !$action) {

	# Add 1 to the number of views of this thread.
	&MessageTotals("incview", $curnum);
	unless ($username eq "Guest") {
		$j = 0;

		fopen(VARLOG, "$vardir/log.txt");
		@onentries = <VARLOG>;
		fclose(VARLOG);
		foreach $oncurentry (@onentries) {
			chomp $oncurentry;
			($onname, undef) = split(/\|/, $oncurentry, 2);
			$viewer{$onname} = 1;
		}

		@tmprepliers = ();
		for ($i = 0; $i < @repliers; $i++) {
			chomp $repliers[$i];
			($reptime, $repuser, $isreplying) = split(/\|/, $repliers[$i]);
			$outtime = $date - $reptime;
			if ($outtime > 600 || !exists $viewer{$repuser}) { next; }
			elsif ($repuser eq $username) { $tmprepliers[$j] = qq~$date|$repuser|0~; $isrep = 1; }
			else { $tmprepliers[$j] = qq~$reptime|$repuser|$isreplying~; }
			$j++;
		}
		if (!$isrep) {
			$thisreplier = qq~$date|$username|0~;
			push(@tmprepliers, $thisreplier);
		}
		@repliers = @tmprepliers;
		&MessageTotals("update", $curnum);
	}
} elsif ($curnum != '' && $action) {
	# or load the global hash for this thread.
	&MessageTotals("load", $curnum);
}


if ($currentboard ne '') {
	if ($currentboard ne '' && $currentboard !~ /\A[\s0-9A-Za-z#%+,-\.:=?@^_]+\Z/) { &fatal_error("invalid_character","$maintxt{'board'}"); }
	if (!-e "$boardsdir/$currentboard.txt") { &fatal_error("cannot_open","$boardsdir/$currentboard.txt"); }
	($boardname, $boardperms, $boardview) = split(/\|/, $board{"$currentboard"});
	my $access = &AccessCheck($currentboard, '', $boardperms);
	if (!$iamadmin && $access ne "granted" && $boardview != 1) { &fatal_error("no_access"); }

	# Determine what category we are in.
	$catid = ${$uid.$currentboard}{'cat'};
	($cat, $catperms) = split(/\|/, $catinfo{"$catid"});
	$cataccess = &CatAccess($catperms);
	unless ($annboard ne "" && $currentboard eq $annboard) {
		if (!$cataccess) { &fatal_error("no_access"); }
	}

	$bdescrip = ${$uid.$currentboard}{'description'};

	# Create Hash %moderators and %moderatorgroups with all Moderators of the current board
	${$uid.$currentboard}{'mods'} =~ s/\, /\,/g;
	foreach (split(/\,/, ${$uid.$currentboard}{'mods'})) {
		&LoadUser($_);
		$moderators{$_} = ${$uid.$_}{'realname'};
	}
	${$uid.$currentboard}{'modgroups'} =~ s/\, /\,/g;
	foreach (split(/\,/, ${$uid.$currentboard}{'modgroups'})) {
		chomp $_;
		$moderatorgroups{$_} = $_;
	}

	unless ($iamadmin) {
		my $accesstype = "";
		if ($action eq "post") {
			if ($INFO{'title'} eq 'CreatePoll' || $INFO{'title'} eq 'AddPoll') {
				$accesstype = 3;    # Post Poll
			} elsif ($INFO{'num'}) {
				$accesstype = 2;    # Post Reply
			} else {
				$accesstype = 1;    # Post Thread
			}
		}
		my $access = &AccessCheck($currentboard, $accesstype);
		if ($access ne "granted") { &fatal_error("no_access"); }
	}
} else {
	### BIG category check
	$currentcat = $INFO{'cat'} || $INFO{'catselect'};
	if ($currentcat ne '') {
		if ($currentcat =~ m~/~)  { &fatal_error("no_cat_slash"); }
		if ($currentcat =~ m~\\~) { &fatal_error("no_cat_backslash"); }
		if ($currentcat ne '' && $currentcat !~ /\A[\s0-9A-Za-z#%+,-\.:=?@^_]+\Z/) { &fatal_error("invalid_character","$maintxt{'cat'}"); }
		if (!$cat{$currentcat}) { &fatal_error("cannot_open","$currentcat"); }

		#  and need cataccess check!
		$cataccess = &CatAccess($catperms);
		if (!$cataccess) { &fatal_error("no_access"); }
	}
}

# BIG thread check
my $threadid = $INFO{'num'} || $INFO{'thread'} || $FORM{'threadid'};

# strip off any non numeric values to avoid exploitation
$threadid =~ s/(\D*)//g;
if ($threadid ne '') {
	if ($threadid !~ /\d*(.\d*)?/)    { &fatal_error("only_numbers_allowed"); }
	if (!-e "$datadir/$threadid.txt") { &fatal_error("not_found","$datadir/$threadid.txt"); }
	if ($currentboard ne '') {
		$yyThreadPosition = -1;
		my $found;
		fopen(BOARDFILE, "$boardsdir/$currentboard.txt") || &fatal_error("not_found","$boardsdir/$currentboard.txt", 1);
		while ($yyThreadLine = <BOARDFILE>) {
			++$yyThreadPosition;
			if ($yyThreadLine =~ m~\A$threadid\|~o) { $found = 1; last; }
		}
		fclose(BOARDFILE);
		chomp $yyThreadLine;
	}
}

sub is_admin { if (!$iamadmin) { &fatal_error("no_access"); } }
sub is_admin2 { if (!$iamadmin) { &fatal_error("move_not_allowed"); } }
sub is_global { if (!$iamgmod || $allow_mod != 1) { &fatal_error("no_access"); } }

sub is_admin_or_gmod {
	if (!$iamadmin && !$iamgmod) { &fatal_error("no_access"); }

	if ($iamgmod && $action ne "") {
		require "$vardir/gmodsettings.txt";

		if ($gmod_access{"$action"} ne "on" && $gmod_access2{"$action"} ne "on") {
			&fatal_error("no_access");
		}
	}
}

sub banning {	
	$ban_user  = $_[0] || $username;
	$ban_email = $_[1] || ${$uid.$username}{'email'};
	if ($username eq "admin" && $iamadmin) { return 0; }
	my (@banlist, $banned, $ban_time, $dummy, $line);
	my $bansize = -s "$vardir/ban.txt";
	if ($bansize > 9) {
		fopen(BAN, "$vardir/ban.txt");
		@banlist = <BAN>;
		fclose(BAN);
	} else {
		return 0;
	}
	$ban_time = int(time);

	foreach $line (@banlist) {
		@banned = ();
		chomp $line;
		($dummy, $bannedlst) = split(/\|/, $line);
		@banned = split(/\,/, $bannedlst);
		if ($dummy eq "I") {    # IP BANNING
			foreach $ipbanned (@banned) {
				$str_len = length($ipbanned);
				$comp_ip = substr($user_ip, 0, $str_len);
				if ($ipbanned eq $comp_ip) {
					fopen(LOG, ">>$vardir/ban_log.txt");
					print LOG "$ban_time|$user_ip\n";
					fclose(LOG);
					&UpdateCookie("delete", $ban_user);
					$username = "Guest";
					&fatal_error("banned","$security_txt{'678'}$security_txt{'430'}!");
					&redirectinternal;
				}
			}
		} elsif ((!$iamguest || $action eq 'register2') && $dummy eq "E") {    # EMAIL BANNING
			foreach $emailbanned (@banned) {
				if (lc $emailbanned eq lc $ban_email) {
					fopen(LOG, ">>$vardir/ban_log.txt");
					print LOG "$ban_time|$emailbanned ($user_ip)\n";
					fclose(LOG);
					&UpdateCookie("delete", $ban_user);
					$username = "Guest";
					&fatal_error("banned","$security_txt{'678'}$security_txt{'430'}!");
					&redirectinternal;
				}
			}
		} elsif ((!$iamguest || $action eq 'register2') && $dummy eq "U") {    # USERNAME BANNING
			foreach $namebanned (@banned) {
				if (lc $namebanned eq lc $ban_user) {
					fopen(LOG, ">>$vardir/ban_log.txt");
					print LOG "$ban_time|$namebanned ($user_ip)\n";
					fclose(LOG);
					&UpdateCookie("delete", $ban_user);
					$username = "Guest";
					&fatal_error("banned","$security_txt{'678'}$security_txt{'430'}!");
					&redirectinternal;
				}
			}
		}
	}
}


sub check_banlist {
	# &check_banlist("email","IP","username"); - will return true if banned by any means
	# This sub can be passed email address, IP, unencoded username or any combination thereof

	# Returns E if banned by email address
	# Returns I if banned by IP address
	# Returns U if banned by username
	# Returns all banning methods, unseperated (eg "EIU" if banned by all methods)

	my ($e_ban, $ip_ban, $u_ban) = @_;
	$ban_rtn = '';

	if ( -s "$vardir/ban.txt" > 9) {
		fopen(BAN, "$vardir/ban.txt");
		@banlist = <BAN>;
		fclose(BAN);
	} else {
		return 0;
	}
	
	foreach $line (@banlist) {
		@banned = ();
		chomp $line;
		($bantype, $bannedlst) = split(/\|/, $line);
		@banned = split(/\,/, $bannedlst);
		if ($e_ban && $bantype eq 'E') {
			foreach my $banned (@banned) {
				if ($banned eq $e_ban) { $ban_rtn .= $bantype; }
			}
		}
		if ($ip_ban && $bantype eq 'I') {
			foreach my $banned (@banned) {
				if ($banned eq $ip_ban) { $ban_rtn .= $bantype; }
			}
		}
		if ($u_ban && $bantype eq 'U') {
			foreach my $banned (@banned) {
				if ($banned eq $u_ban) { $ban_rtn .= $bantype; }
			}
		}
	}
	return $ban_rtn;
}

sub CheckIcon {

	# Check the icon so HTML cannot be exploited.
	# Do it in 3 unless's because 1 is too long.
	$icon =~ s~\Ahttp://.*\/(.*?)\..*?\Z~$1~;
	$icon =~ s~[^A-Za-z]~~g;
	$icon =~ s~\\~~g;
	$icon =~ s~\/~~g;
	unless ($icon eq "xx" || $icon eq "thumbup" || $icon eq "thumbdown" || $icon eq "exclamation") {
		unless ($icon eq "question" || $icon eq "lamp" || $icon eq "smiley" || $icon eq "angry") {
			unless ($icon eq "cheesy" || $icon eq "grin" || $icon eq "sad" || $icon eq "wink") {
				$icon = "xx";
			}
		}
	}
}

sub AccessCheck {
	my ($curboard, $checktype, $boardperms) = @_;

	# Put whether it's a zero post count board in global variable
	# to save need to reopen file many times.
	unless (exists $memberunfo{$username}) { &LoadUser($username); }
	my $boardmod = 0;
	${$uid.$curboard}{'mods'} =~ s/\, /\,/g;
	@board_mods = split(/\,/, ${$uid.$curboard}{'mods'});
	foreach $curuser (@board_mods) {
		if ($username eq $curuser) { $boardmod = 1; }
	}
	${$uid.$curboard}{'modgroups'} =~ s/\, /\,/g;
	${$uid.$username}{'addgroups'} =~ s/\, /\,/g;
	@board_modgrps = split(/\,/, ${$uid.$curboard}{'modgroups'});
	@user_addgrps  = split(/\,/, ${$uid.$username}{'addgroups'});
	foreach $curgroup (@board_modgrps) {
		if (${$uid.$username}{'position'} eq $curgroup) { $boardmod = 1; }
		foreach $curaddgroup (@user_addgrps) {
			if ($curaddgroup eq $curgroup) { $boardmod = 1; }
		}
	}
	$INFO{'zeropost'} = ${$uid.$curboard}{'zero'};
	if ($iamadmin) { $access = "granted"; return $access; }
	my ($viewperms, $topicperms, $replyperms, $pollperms, $attachperms);
	if ($username ne "Guest") {
		($viewperms, $topicperms, $replyperms, $pollperms, $attachperms) = split(/\|/, ${$uid.$username}{'perms'});
	}
	if ($username eq "Guest" && !$enable_guestposting) {
		$viewperms   = 0;
		$topicperms  = 1;
		$replyperms  = 1;
		$pollperms   = 1;
		$attachperms = 1;
	}
	my $access = "denied";
	if ($checktype == 1) {    # Post access check
		${$uid.$curboard}{'topicperms'} =~ s/\, /\,/g;
		@allowed_groups = split(/\,/, ${$uid.$curboard}{'topicperms'});
		if (${$uid.$curboard}{'topicperms'} eq "") { $access = "granted"; }
		if ($topicperms == 1) { $access = "notgranted"; }
	} elsif ($checktype == 2) {    # Reply access check
		if ($iamgmod || $boardmod) { $access = "granted"; }
		else {
			${$uid.$curboard}{'replyperms'} =~ s/\, /\,/g;
			@allowed_groups = split(/\,/, ${$uid.$curboard}{'replyperms'});
			if (${$uid.$curboard}{'replyperms'} eq "") { $access = "granted"; }
			if ($replyperms == 1 && !$topicstart{$username}) { $access = "notgranted"; }
		}
	} elsif ($checktype == 3) {    # Poll access check
		${$uid.$curboard}{'pollperms'} =~ s/\, /\,/g;
		@allowed_groups = split(/\,/, ${$uid.$curboard}{'pollperms'});
		if (${$uid.$curboard}{'pollperms'} eq "") { $access = "granted"; }
		if ($pollperms == 1) { $access = "notgranted"; }
	} elsif ($checktype == 4) {    # Attachment access check
		if (${$uid.$curboard}{'attperms'} == 1) { $access = "granted"; }
		if ($attachperms == 1) { $access = "notgranted"; }
	} else {                       # Board access check
		$boardperms =~ s/\, /\,/g;
		@allowed_groups = split(/\,/, $boardperms);
		if ($boardperms eq "") { $access = "granted"; }
		if ($viewperms == 1) { $access = "notgranted"; }
	}

	# age and gender check
	unless ($iamadmin || $iamgmod || $boardmod) {
		if ((${$uid.$curboard}{'minageperms'} || ${$uid.$curboard}{'maxageperms'}) && (!$age || $age == 0)) {
			$access = "notgranted";
		} elsif (${$uid.$curboard}{'minageperms'} && $age < ${$uid.$curboard}{'minageperms'}) {
			$access = "notgranted";
		} elsif (${$uid.$curboard}{'maxageperms'} && $age > ${$uid.$curboard}{'maxageperms'}) {
			$access = "notgranted";
		}
		if (${$uid.$curboard}{'genderperms'} && !${$uid.$username}{'gender'}) {
			$access = "notgranted";
		} elsif (${$uid.$curboard}{'genderperms'} eq "M" && ${$uid.$username}{'gender'} eq "Female") {
			$access = "notgranted";
		} elsif (${$uid.$curboard}{'genderperms'} eq "F" && ${$uid.$username}{'gender'} eq "Male") {
			$access = "notgranted";
		}
	}
	unless ($access eq "granted" || $access eq "notgranted") {
		$memberinform = $memberunfo{$username};
		foreach $element (@allowed_groups) {
			chomp $element;
			if ($element eq $memberinform) { $access = "granted"; }
			$memberaddgroup{$username} =~ s/\, /\,/g;
			foreach $memberaddgroups (split(/\,/, $memberaddgroup{$username})) {
				chomp $memberaddgroups;
				if ($element eq $memberaddgroups) { $access = "granted"; last; }
			}
			if ($element eq $topicstart{$username}) { $access = "granted"; }
			if ($element eq "Global Moderator" && ($iamadmin || $iamgmod)) { $access = "granted"; }
			if ($element eq "Moderator" && ($iamadmin || $iamgmod || $boardmod)) { $access = "granted"; }
			if ($access eq "granted") { last; }
		}
	}

	return ($access);
}

sub CatAccess {
	my ($cataccess) = @_;
	if ($iamadmin || $cataccess eq "") { return 1; }

	my $access = 0;
	$cataccess =~ s/\, /\,/g;
	@allow_groups = split(/\,/, $cataccess);
	unless (exists $memberunfo{$username}) { &LoadUser($username); }
	$memberinform = $memberunfo{$username};
	foreach $element (@allow_groups) {
		chomp $element;
		if ($element eq $memberinform) { $access = 1; }
		$memberaddgroup{$username} =~ s/\, /\,/g;
		foreach $memberaddgroups (split(/\,/, $memberaddgroup{$username})) {
			chomp $memberaddgroups;
			if ($element eq $memberaddgroups) { $access = 1; last; }
		}
		if ($element eq "Moderator" && ($iamgmod || exists $moderators{$username})) { $access = 1; }
		if ($element eq "Global Moderator" && $iamgmod) { $access = 1; }
		if ($access == 1) { last; }
	}
	return $access;
}


sub email_domain_check {
	### Based upon Distilled Email Domains mod by AstroPilot ###
	my $checkdomain = $_[0];	
	if (-e "$vardir/email_domain_filter.txt" ) { require "$vardir/email_domain_filter.txt"; }
	if ($bdomains){
		@domains = split (/\,/, $bdomains);
		foreach (@domains) {
			if ($_ !~ /\@/) {$_ = "\@$_";}
			elsif ($_ !~ /^./) {$_ = ".$_";}
			&fatal_error("domain_not_allowed","$_") if $checkdomain =~ m/$_/i;
		}
	}
	### Distilled Email Domains mod end ###
}

1;
