#!/usr/bin/perl

# (c) Copyright Kontantin Kuzov 2:5019/40@fidonet
# Distributes on GNU GPL v2, see http://www.gnu.org

# Version 1.02, Unfinished

use Switch;

my $gmt=4;					# Ваш часовой пояс.
my $timesetoutc=NO;				# Поставьте YES, если ваши часы, установлены по UTC
my $minhour=1;					# Нижняя граница времени.
my $maxhour=7;					# Верхняя граница времени.
# Примечание, часы задаются в 24 формате и не поддерживает переход на новые сутки.
my $outbound="/fido/outbound";			# Папка с аутбаундом.
my $timeaddrs="/fido/binkd/timeaddr.txt";	# Адреса узлов, которые впускать только в определенное время суток.
my $calladdrs="/fido/binkd/calladdr.txt";	# Адреса узлов, на которые нужно перезванивать.
my $allowaddrs="/fido/binkd/allowaddr.txt";	# Адреса узлов, на которые не действуют ограничения.
my $allownets="/fido/binkd/allownets.txt";	# Адреса сетей, на которые не действуют ограничения.
my $ourips="/fido/binkd/ourips.txt";		# Наши ипшники, на которые не действуют ограничения.
my $timecheck=YES;				# Проверять ли по времени.
my $reversetimecheck=NO;			# Если YES, то проверяются наоборот адреса, которых нет в списке $timeaddr
# Примечание, все списки и узлы должны располагаться по одному построчно.
my $myaddr="ganjanet.org.ru";			# Если указан, то в ошибке будет просьба перезвонить на по этому адресу.

#Функция перевода адреса в шестнадцатеричное представление (Нужно для BSO)
sub addrtohex ($)
{
	my($string)=@_;
	if ($string =~ /:(.*?)\//)
    	{
	        $part1="$1";
		if ($string =~ /\/(.*)/)
    		{
        		$part2="$1";
			$part1=addingnull($part1,4);
			$part2=addingnull($part2,4);
			return $part1.$part2;
   		} else {
			return "ERROR";
		}
   	} else {
		return "ERROR";
	}
}

#Функция добавления необходимого количества нулей
sub addingnull ($$)
{
	my($string,$length)=@_;
	$string=sprintf("%x", $string);
	for ($i=length($string); $i<$length;$i++)
	{
		$string="0".$string;
	}
	return $string;
}

#Удалениее лишних пробелов по краям
sub trim ($)
{
	my($string)=@_;
	for ($string)
	{
        s/^\s+//;
        s/\s+$//;
	}
	return $string;
}

sub check ($$)
{
	my($ftn,$file)=@_;
	open (LIST,$file);
	flock(LIST, LOCK_SH);
	@list=<LIST>;
	close(LIST);
	$found=NO;
	foreach $id (@list)
	{
		if (trim($id) eq $ftn)
		{
Z			$found=YES;
		}
	}
	return $found;
}

sub on_handshake()
{

 	# Если звонок входящий
	if ($call==0)
	{
		open (LIST,$ourips);
		flock(LIST, LOCK_SH);
		@list=<LIST>;
		close(LIST);
		$found=NO;
		foreach $id (@list) {
			if ($our_ip eq $id)
			{
				$found=YES;
			}
			$id++;
		}
		if ($found) {
			exit;
		}
		open (LIST,$allownets);
		flock(LIST, LOCK_SH);
		@list=<LIST>;
		close(LIST);
 		#Проверяем на принадлежность адреса к сетям, на которые не действуют ограничения.
		foreach $id (@list) {
			my ($net_ip, $net_mask) = split(/\//, trim($id));
			my ($ip1, $ip2, $ip3, $ip4) = split(/\./, $net_ip);
			my $net_ip_raw = pack ('CCCC', $ip1, $ip2, $ip3, $ip4);
			my $net_mask_raw = pack ('B32', (1 x $net_mask), (1 x (32 - $net_mask)));
			my ($ip_1, $ip_2, $ip_3, $ip_4) = split(/\./, $ip);
			my $some_ip_raw = pack ('CCCC', $ip_1, $ip_2, $ip_3, $ip_4);
			if (($some_ip_raw & $net_mask_raw) eq $net_ip_raw)
			{
				$found=YES;
			}
			$id++;
		}
		#Если не входит, то начинаем дополнительную проверку.
		if (!$found) {
			my $errorcode=0;
			# Перебираем все предъявленные акашки.
			foreach $id (@he)
			{
				# отрезаем 5D, делая адрес 4D.
				($tmp)=split("@",$id);
				$calladdrcheck=check($tmp,$calladdrs);
				$allowaddrcheck=check($tmp,$allowaddrs);
				$timeaddrcheck=check($tmp,$timeaddrs);
				$timeaddrcheck=check($tmp,$timeaddrs);
				# Если есть в списке узлов, на которые ограничения не распространяются, то далее не проверяем.
				if ($allowaddrcheck) {
					exit;
				}
				# Проверка по времени
				if ($timecheck) {
					my $nexttimecheck=NO;
					if ($minhour>$maxhour) {
						# Пример: с 22 до 2
						if ((substr(localtime(),11,2)<$minhour)or(substr(localtime(),11,2)<=$maxhour)) {
							$nexttimecheck=YES;
						}
					} else {
						# Пример: с 1 до 7
						if ((substr(localtime(),11,2)<$minhour)or(substr(localtime(),11,2)>=$maxhour)) {
							$nexttimecheck=YES;
						}
					}
					if ($nexttimecheck) {
						foreach $id (@he)
						{
							($tmp)=split("@",$id);
							# Работает только если включена проверка по времени.
		 					# Если адрес входит в список узлов, которым надо проверять время
							# или включена проверка всех узлов и узла нет в списке исключений.
							if (($timeaddrcheck && !$reversetimecheck) || (!$timeaddrcheck && $reversetimecheck))
							{
								$errorcode=3;
							}
						}
					}
				}
				# Проверка на нужность перезванивания
				if ($calladdrcheck)
				{	
					$string=addrtohex($tmp);
					# Не смогли перевести адрес в hex, ну мало что могло случиться? ^_^
					if ($string eq "ERROR") {
						$errorcode=1;
					} else {
						# Генерируем флошку, чтобы мейлер начал прозвонку до узла, если таковой ещё нет.
						open (FILE, "<$outbound/$string.flo") || open (FILE, ">$outbound/$string.flo");
						close (FILE);	
						$errorcode=2;
					}					
				}
				else {
					$errorcode=1;
				}
				# Генерация текста ошибок, посылаемых удаленной стороне в зависимости от кода ошибки.
				switch($errorcode) {
					case 1 {
						if ($myaddr) {
							return "Your IP ($ip) isn't local :-( Call to $myaddr instead...";
						} else {
							return "Your IP ($ip) isn't local :-( Access denied :-(";
						}
					}
					case 2 {
						return "Your IP ($ip) isn't local :-(, I'll recall to you soon or use $mydomain ;-)";
					}
					case 3 {
						return "Bad time for call, do it at $minhour:00 to $maxhour:00 GMT+$gmt";
					}
					default {
						return "Unexpected error, please contact the SysOp";
					}
				}
			}
		}
	}
}

# Для тестирования
&on_handshake;
