# #
#   @app                ConfigServer Security & Firewall (CSF)
#                       Login Failure Daemon (LFD)
#   @website            https://configserver.dev
#   @docs               https://docs.configserver.dev
#   @download           https://download.configserver.dev
#   @repo               https://github.com/Aetherinox/csf-firewall
#   @copyright          Copyright (C) 2025-2026 Aetherinox
#                       Copyright (C) 2006-2025 Jonathan Michaelson
#                       Copyright (C) 2006-2025 Way to the Web Ltd.
#   @license            GPLv3
#   @updated            02.19.2026
#   
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 3 of the License, or (at
#   your option) any later version.
#   
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#   General Public License for more details.
#   
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, see <https://www.gnu.org/licenses>.
# #

package ConfigServer::Config;

use strict;
use lib '/usr/local/csf/lib';
use version;
use Fcntl qw(:DEFAULT :flock);
use Carp;
use IPC::Open3;
use ConfigServer::Slurp qw(slurp);
use ConfigServer::JSON qw(decode_json);
use ConfigServer::Perl::URI qw(uri_escape);

use Exporter 	qw(import);
our $VERSION	= 1.05;
our @ISA		= qw(Exporter);
our @EXPORT_OK	= qw();

our $ipv4reg 	= qr/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/;
our $ipv6reg 	= qr/((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?/;

my %config;
my %configsetting;
my $warning;
my $version;

my $slurpreg 	= ConfigServer::Slurp->slurpreg;
my $cleanreg 	= ConfigServer::Slurp->cleanreg;
my $configfile	= "/etc/csf/csf.conf";

# #
#	load config
# #

sub loadconfig
{
	my $class 	= shift;
	my $self 	= {};

	bless $self, $class;

	if (%config)
	{
		$self->{warning} = $warning;
		return $self;
	}

	undef %configsetting;
	undef %config;
	undef $warning;

	my @file = slurp($configfile);
	foreach my $line ( @file )
	{
		$line =~ s/$cleanreg//g;
		if ($line =~ /^(\s|\#|$)/) { next }
		if ($line !~ /=/) { next }
		my ( $name,$value ) = split (/=/,$line,2);
		$name =~ s/\s//g;

		if ( $value =~ /\"(.*)\"/ )
		{
			$value = $1;
		}
		else
		{
			croak "*Error* Invalid configuration line [$line] in $configfile";
		}
	
		if ( $configsetting{$name} )
		{
			croak "*Error* Setting $name is repeated in $configfile - you must remove the duplicates and then restart csf and lfd";
		}
	
		$config{$name} = $value;
		$configsetting{$name} = 1;
	}

	if ( $config{LF_IPSET} )
	{
		unless ( $config{LF_IPSET_HASHSIZE} )
		{
			$config{LF_IPSET_HASHSIZE} 			= "1024";
			$configsetting{LF_IPSET_HASHSIZE} 	= 1;
		}
		unless ($config{LF_IPSET_MAXELEM})
		{
			$config{LF_IPSET_MAXELEM} 			= "65536";
			$configsetting{LF_IPSET_MAXELEM} 	= 1;
		}
	}

	if ( $config{USE_FTPHELPER} eq "1" )
	{
		$warning .= "USE_FTPHELPER should be set to your FTP server port (21), not 1. USE_FTPHELPER has been disabled\n";
		$config{USE_FTPHELPER} = 0;
	}

	if ( $config{IPTABLES} eq "" or !( -x $config{IPTABLES} ) )
	{
		croak "*Error* The path to iptables is either not set or incorrect for IPTABLES [$config{IPTABLES}] in /etc/csf/csf.conf";
	}

	if ( -e "/proc/sys/net/netfilter/nf_conntrack_helper" and !$config{USE_FTPHELPER} )
	{
		my $setting = slurp( "/proc/sys/net/netfilter/nf_conntrack_helper" );
		chomp $setting;

		if ( $setting == 0 )
		{
			open ( my $PROC, ">", "/proc/sys/net/netfilter/nf_conntrack_helper" );
			print $PROC "1\n";
			close $PROC;
		}
	}

	if ( $config{WAITLOCK} )
	{
		$config{IPTABLESWAIT} = "--wait";
	}

	my @results = &systemcmd( "$config{IPTABLES} $config{IPTABLESWAIT} --version" );
	if ( $results[0] =~ /iptables v(\d+\.\d+\.\d+)/ )
	{
		$version = $1;

		$config{IPTABLESWAIT} = "";
		if ( $config{WAITLOCK} )
		{
			my @ipdata;
			eval
			{
				local $SIG{__DIE__} 	= undef;
				local $SIG{'ALRM'} 		= sub { die "alarm\n" };
	
				alarm( $config{WAITLOCK_TIMEOUT} );
	
				my ( $childin, $childout );
				my $cmdpid = open3( $childin, $childout, $childout, "$config{IPTABLES} --wait -L OUTPUT -nv" );
				@ipdata = <$childout>;
				waitpid ( $cmdpid, 0 );
				chomp @ipdata;

				if ( $ipdata[0] =~ /# Warning: iptables-legacy tables present/ )
				{
					shift @ipdata
				}
				alarm(0);
			};
	
			alarm(0);
			if ( $@ eq "alarm\n" )
			{
				croak "*ERROR* Timeout after $config{WAITLOCK_TIMEOUT} seconds for iptables --wait - WAITLOCK\n";
			}

			if ( $ipdata[0] =~ /^Chain OUTPUT/ )
			{
				$config{IPTABLESWAIT} = "--wait";
			}
			else
			{
				$warning .= "*WARNING* This version of iptables does not support the --wait option - disabling WAITLOCK\n";
				$config{WAITLOCK} = 0;
			}
		}
	}
	else
	{
		$warning .= "*WARNING* Unable to detect iptables version [$results[0]]\n";
	}

	if ( $config{CC_LOOKUPS} and $config{CC_LOOKUPS} != 4 and $config{CC_SRC} eq "1" )
	{
		if ( $config{MM_LICENSE_KEY} eq "" )
		{
			$warning .= "*ERROR*: Country Code Lookups setting MM_LICENSE_KEY must be set in /etc/csf/csf.conf to continue using the MaxMind databases\n";
		}
	}

	foreach my $cclist ( "CC_DENY","CC_ALLOW","CC_ALLOW_FILTER","CC_ALLOW_PORTS","CC_DENY_PORTS","CC_ALLOW_SMTPAUTH" )
	{
		$config{$cclist} =~ s/\s//g;
		my $newcclist;
		foreach my $cc ( split( /\,/,$config{$cclist} ) )
		{
			if ( $cc ne "" and ( ( length( $cc ) == 2 and $cc =~ /^[a-zA-Z][a-zA-Z]$/i) or ( length( $cc ) > 2 and $cc =~ /^AS\d+$/i ) ) )
			{
				$cc = lc $cc;
				if ( $newcclist eq "" )
				{
					$newcclist = "$cc"
				}
				else
				{
					$newcclist .= ",$cc"
				}
			}
			else
			{
				$warning .= "*WARNING* $cclist contains an invalid entry [$cc]\n";
			}
		}
		$config{$cclist} = $newcclist;
	}

	if ($config{CC_DENY} or $config{CC_ALLOW} or $config{CC_ALLOW_FILTER} or $config{CC_ALLOW_PORTS} or $config{CC_DENY_PORTS} or $config{CC_ALLOW_SMTPAUTH})
	{
		if ($config{MM_LICENSE_KEY} eq "" and $config{CC_SRC} eq "1")
		{
			$warning .= "*ERROR*: Country Code Filters setting MM_LICENSE_KEY must be set in /etc/csf/csf.conf to continue updating the MaxMind databases\n";
		}
	}

	if ($config{DROP_OUT} ne "DROP")
	{
		my @data = &systemcmd("$config{IPTABLES} $config{IPTABLESWAIT} -N TESTDENY");
		unless ($data[0] =~ /^iptables/)
		{
			my @ipdata = &systemcmd("$config{IPTABLES} $config{IPTABLESWAIT} -I TESTDENY -j $config{DROP_OUT}");
			if ($ipdata[0] =~ /^iptables/)
			{
				$warning .= "*WARNING* Cannot use DROP_OUT value of [$config{DROP_OUT}] on this server, set to DROP\n";
				$config{DROP_OUT} = "DROP";
			}

			&systemcmd("$config{IPTABLES} $config{IPTABLESWAIT} -F TESTDENY");
			&systemcmd("$config{IPTABLES} $config{IPTABLESWAIT} -X TESTDENY");
		}
	}

	my @raw = &systemcmd("$config{IPTABLES} $config{IPTABLESWAIT} -L PREROUTING -t raw");
	if ($raw[0] =~ /^Chain PREROUTING/) {$config{RAW} = 1} else {$config{RAW} = 0}
	my @mangle = &systemcmd("$config{IPTABLES} $config{IPTABLESWAIT} -L PREROUTING -t mangle");
	if ($mangle[0] =~ /^Chain PREROUTING/) {$config{MANGLE} = 1} else {$config{MANGLE} = 0}

	if ($config{IPV6} and -x $config{IP6TABLES} and $version)
	{
		if ($config{USE_CONNTRACK} and version->parse($version) <= version->parse("1.3.5")) {$config{USE_CONNTRACK} = 0}
		if ($config{PORTFLOOD} and version->parse($version) >= version->parse("1.4.3")) {$config{PORTFLOOD6} = 1}
		if ($config{CONNLIMIT} and version->parse($version) >= version->parse("1.4.3")) {$config{CONNLIMIT6} = 1}
		if ($config{MESSENGER} and version->parse($version) >= version->parse("1.4.17")) {$config{MESSENGER6} = 1}
		if ($config{SMTP_REDIRECT} and version->parse($version) >= version->parse("1.4.17")) {$config{SMTP_REDIRECT6} = 1}
		my @ipdata = &systemcmd("$config{IP6TABLES} $config{IPTABLESWAIT} -t nat -L POSTROUTING -nv");

		if ($ipdata[0] =~ /^Chain POSTROUTING/)
		{
			$config{NAT6} = 1;
		}
		elsif (version->parse($version) >= version->parse("1.4.17"))
		{
			if ($config{SMTP_REDIRECT})
			{
				$warning .= "*WARNING* ip6tables nat table not present - disabling SMTP_REDIRECT for IPv6\n";
				$config{SMTP_REDIRECT6} = 0;
			}
			if ($config{MESSENGER})
			{
				$warning .= "*WARNING* ip6tables nat table not present - disabling MESSENGER Service for IPv6\n";
				$config{MESSENGER6} = 0;
			}
			if ($config{DOCKER} and $config{DOCKER_NETWORK6} ne "")
			{
				$warning .= "*WARNING* ip6tables nat table not present - disabling DOCKER for IPv6\n";
			}
		}

		my @raw = &systemcmd("$config{IP6TABLES} $config{IPTABLESWAIT} -L PREROUTING -t raw");
		if ($raw[0] =~ /^Chain PREROUTING/) {$config{RAW6} = 1} else {$config{RAW6} = 0}
		my @mangle = &systemcmd("$config{IP6TABLES} $config{IPTABLESWAIT} -L PREROUTING -t mangle");
		if ($mangle[0] =~ /^Chain PREROUTING/) {$config{MANGLE6} = 1} else {$config{MANGLE6} = 0}
	}
	elsif ($config{IPV6})
	{
		$warning .= "*WARNING* incorrect ip6tables binary location [$config{IP6TABLES}] - IPV6 disabled\n";
		$config{IPV6} = 0;
	}
	
	if (!$config{GENERIC} and -e "/var/cpanel/dnsonly")
	{
		$config{DNSONLY} = 1
	}

	if (-e "/var/cpanel/smtpgidonlytweak" and !$config{GENERIC}) {
		if ($config{DNSONLY})
		{
			$warning .= "*WARNING* The cPanel option to 'Restrict outgoing SMTP to root, exim, and mailman' is incompatible with this firewall. The option must be disabled using \"/usr/local/cpanel/scripts/smtpmailgidonly off\" and the SMTP_BLOCK alternative in csf used instead\n";
		}
		else
		{
			$warning .= "*WARNING* The option \"WHM > Tweak Settings > Restrict outgoing SMTP to root, exim, and mailman (FKA SMTP Tweak)\" is incompatible with this firewall. The option must be disabled in WHM and the SMTP_BLOCK alternative in csf used instead\n";
		}
	}

	if (-e "/proc/vz/veinfo")
	{
		$config{VPS} = 1
	}
	else
	{
		foreach my $line (slurp("/proc/self/status"))
		{
			$line =~ s/$cleanreg//g;
			if ($line =~ /^envID:\s*(\d+)\s*$/)
			{
				if ($1 > 0)
				{
					$config{VPS} = 1;
					last;
				}
			}
		}
	}

	if ($config{DROP_IP_LOGGING} and $config{PS_INTERVAL})
	{
		$warning .= "*WARNING* Cannot use PS_INTERVAL with DROP_IP_LOGGING enabled. DROP_IP_LOGGING disabled\n";
		$config{DROP_IP_LOGGING} = 0;
	}

	if ($config{FASTSTART})
	{
		unless (-x $config{IPTABLES_RESTORE})
		{
			$warning .= "*WARNING* Unable to use FASTSTART as [$config{IPTABLES_RESTORE}] is not executable or does not exist\n";
			$config{FASTSTART} = 0;
		}

		if ($config{IPV6})
		{
			unless (-x $config{IP6TABLES_RESTORE})
			{
				$warning .= "*WARNING* Unable to use FASTSTART as (IPv6) [$config{IP6TABLES_RESTORE}] is not executable or does not exist\n";
				$config{FASTSTART} = 0;
			}
		}
	}

	if ( $config{MESSENGER} )
	{
		if ( $config{MESSENGERV2} )
		{
			if ( !-e "/etc/cpanel/ea4/is_ea4" )
			{
				$warning .= "*WARNING* EA4 is not in use - disabling MESSENGERV2 and MESSENGER HTTPS Service\n";
				$config{MESSENGERV2} = "0";
				$config{MESSENGER_HTTPS_IN} = "";
				$config{MESSENGER_HTTPS_DISABLED} = "*WARNING* EA4 is not in use - disabling MESSENGERV2 and MESSENGER HTTPS Service";
			}
		}

		if ($config{MESSENGER_HTTPS_IN} and (!$config{MESSENGERV2} or $config{MESSENGER_HTTPS_DISABLED}))
		{
			eval
			{
				local $SIG{__DIE__} = undef;
				require IO::Socket::SSL;
			};
	
			if ( $@ )
			{
				$warning .= "*WARNING* Perl module IO::Socket::SSL missing - disabling MESSENGER HTTPS Service\n";
				$config{MESSENGER_HTTPS_IN} = "";
				$config{MESSENGER_HTTPS_DISABLED} = "*WARNING* Perl module IO::Socket::SSL missing - disabling MESSENGER HTTPS Service";
			}
			elsif (version->parse($IO::Socket::SSL::VERSION) < version->parse("1.83")) {
				$warning .= "*WARNING* Perl module IO::Socket::SSL v$IO::Socket::SSL::VERSION does not support SNI - disabling MESSENGER HTTPS Service\n";
				$config{MESSENGER_HTTPS_IN} = "";
				$config{MESSENGER_HTTPS_DISABLED} = "*WARNING* Perl module IO::Socket::SSL v$IO::Socket::SSL::VERSION does not support SNI - disabling MESSENGER HTTPS Service";
			}
		}

		my $pcnt = 0;
		foreach my $port ( split(/\,/,$config{MESSENGER_HTML_IN} ) )
		{
			$pcnt++;
		}

		if ( $pcnt > 15 )
		{
			$warning .= "*WARNING* MESSENGER_HTML_IN contains more than 15 ports - disabling MESSENGER Service\n";
			$config{MESSENGER} = 0;
		}
		else
		{
			$pcnt = 0;
			foreach my $port ( split(/\,/,$config{MESSENGER_TEXT_IN} ) )
			{
				$pcnt++;
			}

			if ( $pcnt > 15 )
			{
				$warning .= "*WARNING* MESSENGER_TEXT_IN contains more than 15 ports - disabling MESSENGER Service\n";
				$config{MESSENGER} = 0;
			}
			else
			{
				$pcnt = 0;
				foreach my $port ( split( /\,/,$config{MESSENGER_HTTPS_IN} ) )
				{
					$pcnt++;
				}

				if ($pcnt > 15)
				{
					$warning .= "*WARNING* MESSENGER_HTTPS_IN contains more than 15 ports - disabling MESSENGER Service\n";
					$config{MESSENGER} = 0;
				}
			}
		}
	}

	if ( $config{IPV6} and $config{IPV6_SPI} )
	{
		open (my $FH, "<", "/proc/sys/kernel/osrelease");
		flock ($FH, LOCK_SH);
		my @data = <$FH>;
		close ($FH);
		chomp @data;

		if ( $data[0] =~ /^(\d+)\.(\d+)\.(\d+)/ )
		{
			my $maj = $1;
			my $mid = $2;
			my $min = $3;
			if ( ( $maj > 2 ) or ( ( $maj > 1 ) and ( $mid > 6 ) ) or ( ( $maj > 1 ) and ( $mid > 5 ) and ( $min > 19 ) ) )
			{
			}
			else
			{
				$warning .=  "*WARNING* Kernel $data[0] may not support an ip6tables SPI firewall. You should set IPV6_SPI to \"0\" in /etc/csf/csf.conf\n\n";
			}
		}
	}

	if ( ( $config{CLUSTER_SENDTO} or $config{CLUSTER_RECVFROM} ) )
	{
		if ( -f $config{CLUSTER_SENDTO} )
		{
			if ($config{DEBUG} >= 1) { $warning .= "*DEBUG* CLUSTER_SENDTO retrieved from $config{CLUSTER_SENDTO} and set to: " }
			$config{CLUSTER_SENDTO} = join(",", slurp($config{CLUSTER_SENDTO}));
			if ($config{DEBUG} >= 1) { $warning .= "[$config{CLUSTER_SENDTO}]\n" }
		}

		if ( -f $config{CLUSTER_RECVFROM} )
		{
			if ($config{DEBUG} >= 1) { $warning .= "*DEBUG* CLUSTER_RECVFROM retrieved from $config{CLUSTER_RECVFROM} and set to: " }
			$config{CLUSTER_RECVFROM} = join(",", slurp($config{CLUSTER_RECVFROM}));
			if ($config{DEBUG} >= 1) { $warning .= "[$config{CLUSTER_RECVFROM}]\n" }
		}
	}

	my @ipdata = &systemcmd( "$config{IPTABLES} $config{IPTABLESWAIT} -t nat -L POSTROUTING -nv" );
	if ( $ipdata[0] =~ /^Chain POSTROUTING/ )
	{
		$config{NAT} = 1;
	}
	else
	{
		if ( $config{MESSENGER} )
		{
			$warning .= "*WARNING* iptables nat table not present - disabling MESSENGER Service\n";
			$config{MESSENGER} = 0;
		}
	}

	if ( $config{PT_USERKILL} )
	{
		$warning .= "*WARNING* PT_USERKILL should not normally be enabled as it can easily lead to legitimate processes being terminated, use csf.pignore instead\n";
	}

	$config{cc_src} 			= "MaxMind";
	$config{asn_src} 			= "MaxMind";
	$config{cc_country} 		= "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country-CSV&suffix=zip&license_key=$config{MM_LICENSE_KEY}";
	$config{cc_city} 			= "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&suffix=zip&license_key=$config{MM_LICENSE_KEY}";
	$config{cc_asn} 			= "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN-CSV&suffix=zip&license_key=$config{MM_LICENSE_KEY}";

	if ( $config{CC_SRC} eq "2" )
	{
		$config{cc_src} 		= "DB-IP";
		$config{asn_src} 		= "iptoasn.com";
		$config{ccl_src} 		= "ipdeny.com";
		my ($month,$year) 		= sub{ 1+shift, 1900+shift }->((localtime time)[4,5]);
		$month 					= sprintf("%02d", $month);
		$config{cc_country} 	= "http://download.db-ip.com/free/dbip-country-lite-$year-$month.csv.gz";
		$config{cc_city} 		= "http://download.db-ip.com/free/dbip-city-lite-$year-$month.csv.gz";
		$config{cc_asn} 		= "http://iptoasn.com/data/ip2asn-combined.tsv.gz";
		$config{cc_cc} 			= "http://download.geonames.org/export/dump/countryInfo.txt";
	}

	$config{DOWNLOADSERVER} 	= &getdownloadserver;
	$self->{warning} 			= $warning;

	return $self;
}

sub config
{
	return %config;
}

sub resetconfig
{
	undef %config;
	undef %configsetting;
	undef $warning;

	return;
}

sub configsetting
{
	return %configsetting;
}

sub ipv4reg
{
	return $ipv4reg;
}

sub ipv6reg
{
	return $ipv6reg;
}

sub systemcmd
{
	my @command = @_;
	my @result;

	eval
	{
		my ($childin, $childout);
		my $pid = open3($childin, $childout, $childout, @command);
		@result = <$childout>;
		waitpid ($pid, 0);
		chomp @result;

		if ($result[0] =~ /# Warning: iptables-legacy tables present/)
		{
			shift @result
		}
	};

	return @result;
}

# #
#	getLicense
#	
#	Returns the sponsor license key from config.
#	Uses internal %config hash; call after loadconfig().
#	
#	@return			str
# #

sub getLicense
{
	return $config{SPONSOR_LICENSE};
}

# #
#	License › Status
#	
#	Returns both the license status of a user, as well as any errors that
#	may return from the validation process.
#	
#	License fetching utilizes URLGET, which can be changed in csf.conf.
#	Three options available:
#   	1. Perl module HTTP::Tiny
#   	2. Perl module LWP::UserAgent
#   	3. CURL/WGET (set location at the bottom of csf.conf if installed)
#	
#	If using URLGET = "2"; requires LWP
#		yum install perl-libwww-perl.noarch perl-LWP-Protocol-https.noarch
#		apt-get install libwww-perl liblwp-protocol-https-perl
#	
#	Without these packages, license response only understands 200 and non-200 codes.
#	Will not return message.response from json.
#	
#	@return			int			isValid		0,1
#					str			errorMsg
# #

sub getLicenseStatus
{
	my $timeout 	= 3;
	my $license 	= getLicense();	
	return ( 0, 'No license configured' ) unless $license;

	# avoid circular dependency
	require ConfigServer::URLGet;

	my $urlget = ConfigServer::URLGet->new( $config{URLGET}, "csf/$VERSION", $config{URLPROXY} );
	unless ( defined $urlget )
	{
		$urlget = ConfigServer::URLGet->new( 1, "csf/$VERSION", $config{URLPROXY} );
	}
	return ( 0, 'Unable to initialize URL fetcher' ) unless $urlget;

	my $licenseEnc = uri_escape( $license );
	my ( $statusCode, $resp );

	eval
	{
		local $SIG{__DIE__} 	= undef;
		local $SIG{'ALRM'} 		= sub { die "timeout\n" };
		alarm( $timeout );
	
		( $statusCode, $resp ) = $urlget->urlget( "https://license.configserver.dev/?license=$licenseEnc" );
		alarm( 0 );
	};
	alarm( 0 );

	# #
	#	Add timeout in case something goes wrong, 
	#	the user should not be stuck waiting.
	# #

	if ( $@ )
	{
		if ( $@ eq "timeout\n" )
		{
			return ( 0, 'License server timeout' );
		}
		return ( 0, 'License check failed' );
	}

	return ( 0, 'Invalid license key' ) if $statusCode;

	my $data = decode_json( $resp );
	return ( 0, 'Invalid response from license server' ) unless defined $data;

	my $valid 	= $data->{message}{valid} ? 1 : 0;
	my $msg 	= $data->{message}{response} // 'Unknown error';

	$msg = "$msg";
	$msg =~ s/[\x00-\x1F\x7F]//g;
	$msg =~ s/\s+/ /g;
	$msg = substr( $msg, 0, 200 );

	return ( $valid, $msg );
}

# #
#	getCodename
#	
#	Returns the codename based on which control panel a user is running.
#	Uses internal %config hash; call after loadconfig().
#	
#	@return			str
# #

sub getCodename
{
	my $cname = "cpanel";

	if ($config{GENERIC})      { $cname = "generic" }
	if ($config{DIRECTADMIN})  { $cname = "directadmin" }
	if ($config{INTERWORX})    { $cname = "interworx" }
	if ($config{CYBERPANEL})   { $cname = "cyberpanel" }
	if ($config{CWP})          { $cname = "cwp" }
	if ($config{VESTA})        { $cname = "vestacp" }

	if ( -e "/usr/share/webmin/miniserv.pl" || -e "/usr/libexec/webmin/bin/webmin" || -e "/usr/bin/webmin" )
	{
		$cname = "webmin";
	}

	return $cname;
}

# #
#	fetches a list of csf download server endpoints
# #

sub getdownloadserver
{
	my @servers;
	my $downloadservers = "/etc/csf/downloadservers";
	my $chosen;

	if (-e $downloadservers)
	{
		open (my $DOWNLOAD, "<", $downloadservers);
		flock ($DOWNLOAD, LOCK_SH);
		my @data = <$DOWNLOAD>;
		close ($DOWNLOAD);
		chomp @data;
	
		foreach my $line (@data) 
		{
			# #
			#	whitelist of acceptable subdomains
			#		raw.github.com
			#		download.configserver.dev
			#		cdn.configserver.dev
			# #

			if ($line =~ /^(?:raw|download|cdn)\./) 
			{
				push @servers, $line;
			}
		}

		foreach my $line (slurp($downloadservers)) 
		{
			$line =~ s/$cleanreg//g;
			if ($line =~ /^(?:raw|download|cdn)\./) 
			{
				push @servers, $line;
			}
		}
	
		$chosen = $servers[rand @servers];
	}

	# #
	#	fallback
	# #

	if ($chosen eq "")
	{
		$chosen = "download.configserver.dev"
	}

	return $chosen;
}

1;