shell bypass 403
<?php
/**
* @package admintools
* @copyright Copyright (c)2010-2025 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Component\AdminTools\Administrator\Model;
defined('_JEXEC') or die;
use Akeeba\Component\AdminTools\Administrator\Helper\CloudIPRanges;
use Akeeba\Component\AdminTools\Administrator\Helper\ServerTechnology;
use DateTimeZone;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
#[\AllowDynamicProperties]
class WebconfigmakerModel extends ServerconfigmakerModel
{
/**
* The current configuration of this feature
*
* @var object
*/
protected $configKey = 'wcconfig';
/**
* The base name of the configuration file being saved by this feature, e.g. ".htaccess". The file is always saved
* in the site's root. Any old files under that name are renamed with a .admintools suffix.
*
* @var string
*/
protected $configFileName = 'web.config';
/** @inheritdoc */
public function isSupported(): int
{
return ServerTechnology::isWebConfigSupported();
}
/**
* Compile and return the contents of the web.config configuration file
*
* @return string
*/
public function makeConfigFile()
{
// Make sure we are called by an expected caller
ServerTechnology::checkCaller($this->allowedCallersForMake);
$app = Factory::getApplication();
$timezone = 'UTC';
// Fetch the timezone from the user only if we're not in CLI
if (!$app->isClient('cli'))
{
$timezone = $app->getIdentity()->getParam('timezone', $app->get('offset', 'UTC'));
}
$date = clone Factory::getDate();
$tz = new DateTimeZone($timezone);
$date->setTimezone($tz);
$d = $date->format('Y-m-d H:i:s T', true);
$version = ADMINTOOLS_VERSION;
$webConfig = <<< XML
<?xml version="1.0" encoding="utf-8"?>
<!--
Security Enhanced & Highly Optimized .web.config File for Joomla!
automatically generated by Admin Tools $version on $d
Admin Tools is Free Software, distributed under the terms of the GNU
General Public License version 3 or, at your option, any later version
published by the Free Software Foundation.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! !!
!! If you get an Internal Server Error 500 or a blank page when trying !!
!! to access your site, remove this file and try tweaking its settings !!
!! in the back-end of the Admin Tools component. !!
!! !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-->
<configuration>
<system.webServer>
XML;
$config = (object) $this->loadConfiguration();
// Let's start with IP restriction
$restrictIP = $config->restrictip ?? 'none';
if ($restrictIP !== 'none')
{
$restrictIPList = CloudIPRanges::getIPRanges($restrictIP);
if ($restrictIPList === null)
{
throw new \RuntimeException(Text::_('COM_ADMINTOOLS_ERR_INVALID_RESTRICTIP'));
}
$ipRules = '';
foreach ($restrictIPList as $ipRange)
{
[$ip, $subNet] = $this->ipRangeToIPandNetmask($ipRange);
$ipRules .= "\t\t\t\t<add ipAddress=\"$ip\" subnetMask=\"$subNet\" allowed=\"true\" />\n";
}
$requestFilteringRules = '';
if ($config->notracetrack == 1)
{
$requestFilteringRules = <<<XML
<!-- Disable HTTP methods TRACE and TRACK (protect against XST) -->
<requestFiltering>
<verbs>
<add verb="TRACE" allowed="false" />
<add verb="TRACK" allowed="false" />
</verbs>
</requestFiltering>
XML;
}
$webConfig .= <<< END
<security>
<!-- Restricted access by IP address -->
<ipSecurity allowUnlisted="false" denyAction="Forbidden">
$ipRules
</ipSecurity>
$requestFilteringRules
</security>
END;
}
if ($config->fileorder == 1)
{
$webConfig .= <<< XML
<!-- File execution order -->
<defaultDocument enabled="true">
<files>
<clear />
<add value="index.php" />
<add value="index.html" />
<add value="index.htm" />
</files>
</defaultDocument>
XML;
}
if ($config->nodirlists == 1)
{
$webConfig .= <<< XML
<!-- No directory listings -->
<directoryBrowse enabled="false" />
XML;
}
if ($config->exptime != 0)
{
$setEtag = ($config->etagtype == 'none') ? 'setEtag="false"' : '';
$eTagInfo = ($config->etagtype == 'none') ? '// Send ETag: false (IIS only supports true/false for ETags)' : '';
// Expiration time of 1 week or 1 year (based on selection)
$expTime = ($config->exptime == 1) ? 604800 : 31708800;
$webConfig .= <<< XML
<!-- Optimal default expiration time $eTagInfo -->
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="$expTime" $setEtag />
</staticContent>
XML;
}
if ($config->autocompress == 1)
{
$webConfig .= <<<XML
<urlCompression doStaticCompression="false" doDynamicCompression="true" />
<httpCompression>
<dynamicTypes>
<clear />
<add mimeType="text/*" enabled="true" />
<add mimeType="message/*" enabled="true" />
<add mimeType="application/xml" enabled="true" />
<add mimeType="application/xhtml+xml" enabled="true" />
<add mimeType="application/rss+xml" enabled="true" />
<add mimeType="application/javascript" enabled="true" />
<add mimeType="text/javascript" enabled="true" />
<add mimeType="application/x-javascript" enabled="true" />
<add mimeType="image/svg+xml" enabled="true" />
<add mimeType="*/*" enabled="false" />
</dynamicTypes>
</httpCompression>
XML;
}
$webConfig .= <<< XML
<rewrite>
<rules>
<clear />
XML;
if (!empty($config->hoggeragents) && ($config->nohoggers == 1))
{
$conditions = '';
$patternCache = [];
foreach ($config->hoggeragents as $agent)
{
$patternCache[] = $agent;
if ((is_array($agent) || $agent instanceof \Countable ? count($agent) : 0) < 10)
{
continue;
}
$newPattern = implode('|', $patternCache);
$conditions .= <<< XML
<add input="{HTTP_USER_AGENT}" pattern="$newPattern" />
XML;
$patternCache = [];
}
if (count($patternCache))
{
$newPattern = implode('|', $patternCache);
$conditions .= <<< XML
<add input="{HTTP_USER_AGENT}" pattern="$newPattern" />
XML;
}
$webConfig .= <<< XML
<rule name="Common hacking tools and bandwidth hoggers block" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
$conditions
</conditions>
<action type="CustomResponse" statusCode="403" statusReason="Forbidden: Access is denied." statusDescription="You do not have permission to view this directory or page using the credentials that you supplied." />
</rule>
XML;
}
if ($config->autoroot)
{
$webConfig .= <<<XML
<rule name="Redirect index.php to /" stopProcessing="true">
<match url="^index\.php$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{THE_REQUEST}" pattern="^POST" ignoreCase="false" negate="true" />
<add input="{THE_REQUEST}" pattern="^[A-Z]{3,9}\ /index\.php\ HTTP/" ignoreCase="false" />
<add input="{HTTPS}>s" pattern="^(1>(s)|0>s)$" ignoreCase="false" />
</conditions>
<action type="Redirect" url="http{C:2}://{HTTP_HOST}:{SERVER_PORT }/" redirectType="Permanent" />
</rule>
XML;
}
switch ($config->wwwredir)
{
case 1:
// If I have a rewriteBase condition, I have to append it here
$subfolder = trim($config->rewritebase, '/') ? trim($config->rewritebase, '/') . '/' : '';
// non-www to www
$webConfig .= <<<END
<rule name="Redirect non-www to www" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{HTTP_HOST}" pattern="^www\." negate="true" />
</conditions>
<action type="Redirect" url="http://www.{HTTP_HOST}/$subfolder{R:1}" redirectType="Found" />
</rule>
END;
break;
case 2:
// www to non-www
$webConfig .= <<<END
<rule name="Redirect www to non-www" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{HTTP_HOST}" pattern="^www\.(.+)$" />
</conditions>
<action type="Redirect" url="http://{C:1}/{R:1}" redirectType="Found" />
</rule>
END;
break;
}
if (!empty($config->olddomain))
{
$domains = trim($config->olddomain);
$domains = explode(',', $domains);
$newdomain = $config->httphost;
foreach ($domains as $olddomain)
{
$olddomain = trim($olddomain);
$originalOldDomain = $olddomain;
if (empty($olddomain))
{
continue;
}
$olddomain = $this->escape_string_for_regex($olddomain);
$webConfig .= <<<END
<rule name="Redirect old to new domain ($originalOldDomain)" stopProcessing="true">
<match url="(.*)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{HTTP_HOST}" pattern="^$olddomain" />
</conditions>
<action type="Redirect" url="http://$newdomain/{R:1}" redirectType="Found" />
</rule>
END;
}
}
if (!empty($config->httpsurls))
{
$webConfig .= "<!-- Force HTTPS for certain pages -->\n";
foreach ($config->httpsurls as $url)
{
$urlesc = '^' . $this->escape_string_for_regex($url) . '$';
$webConfig .= <<<END
<rule name="Force HTTPS for $url" stopProcessing="true">
<match url="^$urlesc$" ignoreCase="false" />
<conditions logicalGrouping="MatchAny">
<add input="{HTTPS}" pattern="0" />
</conditions>
<action type="Redirect" url="https://{$config->httpshost}/$url" redirectType="Found" />
</rule>
END;
}
}
$webConfig .= <<<END
<rule name="Block out some common exploits">
<match url=".*" ignoreCase="false" />
<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
<add input="{QUERY_STRING}" pattern="proc/self/environ" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="mosConfig_[a-zA-Z_]{1,21}(=|\%3D)" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="base64_(en|de)code\(.*\)" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="(<|%3C).*script.*(>|%3E)" />
<add input="{QUERY_STRING}" pattern="GLOBALS(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="_REQUEST(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
</conditions>
<action type="CustomResponse" url="index.php" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
END;
if ($config->fileinj == 1)
{
$webConfig .= <<<END
<rule name="File injection protection" stopProcessing="true">
<match url=".*" ignoreCase="false" />
<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
<add input="{QUERY_STRING}" pattern="[a-zA-Z0-9_]=http://" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="[a-zA-Z0-9_]=(\.\.//?)+" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="[a-zA-Z0-9_]=/([a-z0-9_.]//?)+" />
</conditions>
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
END;
}
$webConfig .= " <!-- Advanced server protection rules exceptions -->\n";
if (!empty($config->exceptionfiles))
{
$ruleCounter = 0;
foreach ($config->exceptionfiles as $file)
{
$ruleCounter++;
$file = '^' . $this->escape_string_for_regex($file) . '$';
$webConfig .= <<<END
<rule name="Advanced server protection rules exception #$ruleCounter" stopProcessing="true">
<match url="$file" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
END;
}
}
if (!empty($config->exceptiondirs))
{
$ruleCounter = 0;
foreach ($config->exceptiondirs as $dir)
{
$ruleCounter++;
$dir = trim($dir, '/');
$dir = $this->escape_string_for_regex($dir);
$webConfig .= <<<END
<rule name="Allow access to folders except .php files #$ruleCounter" stopProcessing="true">
<match url="^$dir/" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{URL}" pattern="(\.php)$" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" />
</conditions>
<action type="None" />
</rule>
END;
}
}
if (!empty($config->fullaccessdirs))
{
$ruleCounter = 0;
foreach ($config->fullaccessdirs as $dir)
{
$ruleCounter++;
$dir = trim($dir, '/');
$dir = $this->escape_string_for_regex($dir);
$webConfig .= <<<END
<rule name="Allow access to folders, including .php files #$ruleCounter" stopProcessing="true">
<match url="^$dir/" ignoreCase="false" />
<action type="None" />
</rule>
END;
}
}
if ($config->backendprot == 1)
{
$directories = $this->bugfixBackendProtectionExclusionDirectories($config->bepexdirs ?: []);
$bedirs = implode('|', $directories);
$betypes = implode('|', $config->bepextypes);
$webConfig .= <<<END
<rule name="Back-end protection - allow administrator login" stopProcessing="true">
<match url="^administrator/?$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
<rule name="Back-end protection - allow administrator login, alternate" stopProcessing="true">
<match url="^administrator/index\.(php|html?)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
<rule name="Back-end protection - allow access to static media files" stopProcessing="true">
<match url="^administrator/($bedirs)/.*\.($betypes)$" ignoreCase="true" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
<rule name="Back-end protection - Catch all">
<match url="^administrator/" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
END;
}
if ($config->frontendprot == 1)
{
$fedirs = implode('|', $config->fepexdirs);
$fetypes = implode('|', $config->fepextypes);
$webConfig .= <<<END
<rule name="Front-end protection - allow access to additional TinyMCE plugins' HTML files" stopProcessing="true">
<match url="^media/plg_editors_tinymce/js/plugins/.*\.(htm|html)$" ignoreCase="true" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
<rule name="Front-end protection - allow access to static media files" stopProcessing="true">
<match url="^($fedirs)/.*\.($fetypes)$" ignoreCase="true" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
<rule name="Front-end protection - Do not block includes/js" stopProcessing="true">
<match url="^includes/js/" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="None" />
</rule>
<rule name="Front-end protection - Block access to certain folders">
<match url="^($fedirs)/" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
<rule name="Front-end protection - Block access to certain folders, part 2">
<match url="^(cache|includes|language|logs|log|tmp)/" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
<rule name="Front-end protection - Forbid access to leftover Joomla! files">
<match url="^(configuration\.php|CONTRIBUTING\.md|htaccess\.txt|joomla\.xml|LICENSE\.txt|phpunit\.xml|README\.txt|web\.config\.txt)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
<rule name="Front-end protection - Block access to all PHP files except index.php">
<match url="(.*\.php)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{URL}" pattern="(\.php)$" ignoreCase="false" />
<add input="{URL}" pattern="(/index?\.php)$" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" />
</conditions>
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
END;
}
if ($config->leftovers == 1)
{
$webConfig .= <<<END
<rule name="Front-end protection - Block access to common server configuration files">
<match url="^(htaccess\.txt|configuration\.php-dist|php\.ini|.user\.ini|web\.config|web\.config\.txt)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
END;
}
$webConfig .= <<< XML
<rule name="Joomla! Common Exploits Prevention" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
<add input="{QUERY_STRING}" pattern="base64_encode[^(]*\([^)]*\)" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="(>|%3C)([^s]*s)+cript.*(<|%3E)" />
<add input="{QUERY_STRING}" pattern="GLOBALS(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
<add input="{QUERY_STRING}" pattern="_REQUEST(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
</conditions>
<action type="CustomResponse" url="index.php" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule>
XML;
$webConfig .= <<< XML
<rule name="Joomla! API Application SEF URLs">
<match url="^api/(.*)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{URL}" pattern="^/api/index.php" ignoreCase="true" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="api/index.php" />
</rule>
XML;
$webConfig .= <<< XML
<rule name="Joomla! SEF Rule 2">
<match url="(.*)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{URL}" pattern="^/index.php" ignoreCase="true" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
XML;
if (($config->noserversignature == 1) || ($config->svgneutralize == 1))
{
$webConfig .= <<< XML
<outboundRules>
XML;
if ($config->noserversignature == 1)
{
$webConfig .= <<< XML
<rule name="Remove IIS version siganture">
<match serverVariable="RESPONSE_Server" pattern=".+" />
<action type="Rewrite" value="MYOB" />
</rule>
XML;
}
if ($config->svgneutralize == 1)
{
$webConfig .= <<< XML
<rule name="Neutralize SVG scritp execution">
<match serverVariable="RESPONSE_Content_Security_Policy" pattern="^$" />
<conditions>
<add input="{REQUEST_FILENAME}" pattern="\.svg$" />
</conditions>
<action type="Rewrite" value="script-src 'none'"/>
</rule>
XML;
}
$webConfig .= <<< XML
</outboundRules>
XML;
}
$webConfig .= <<< XML
</rewrite>
<httpProtocol>
<customHeaders>
XML;
if ($config->clickjacking == 1)
{
$webConfig .= <<< ENDCONF
<!-- Protect against clickjacking / Forbid displaying in FRAME -->
<add name="X-Frame-Options" value="SAMEORIGIN" />
ENDCONF;
}
if ($config->reducemimetyperisks == 1)
{
$webConfig .= <<< XML
<!-- Reduce MIME type security risks -->
<add name="X-Content-Type-Options" value="nosniff" />
XML;
}
if ($config->reflectedxss == 1)
{
$webConfig .= <<< XML
<!-- Reflected XSS prevention -->
<add name="X-XSS-Protection" value="1; mode=block" />
XML;
}
if ($config->noserversignature == 1)
{
$webConfig .= <<< XML
<!-- Remove IIS and PHP version signature -->
<remove name="X-Powered-By" />
<add name="X-Powered-By" value="MYOB" />
<remove name="X-Content-Powered-By" />
<add name="X-Content-Powered-By" value="MYOB" />
XML;
}
if ($config->notransform == 1)
{
$webConfig .= <<< XML
<!-- Prevent content transformation -->
<add name="Cache-Control" value="no-transform" />
XML;
}
if ($config->hstsheader == 1)
{
$webConfig .= <<<XML
<!-- HSTS Header - See http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security -->
<add name="Strict-Transport-Security" value="max-age=31536000" />
XML;
}
elseif ($config->hstsheader == 2)
{
$webConfig .= <<<XML
<!-- HSTS Header - See http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security -->
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains; preload" />
XML;
}
if ($config->cors == 1)
{
$webConfig .= <<<XML
<!-- Explicitly enable Cross-Origin Resource Sharing (CORS) - See http://enable-cors.org/ -->
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Timing-Allow-Origin" value="*" />
XML;
}
elseif ($config->cors == -1)
{
$webConfig .= <<<XML
<!-- Explicitly disable Cross-Origin Resource Sharing (CORS) - See http://enable-cors.org/ -->
<add name="Cross-Origin-Resource-Policy" value="same-origin" />
XML;
}
if ($config->referrerpolicy !== '-1')
{
$webConfig .= <<<XML
<!-- Referrer-policy -->
<add name="Referrer-Policy" value="{$config->referrerpolicy}" />
XML;
}
$webConfig .= <<< XML
</customHeaders>
</httpProtocol>
XML;
// If IP restriction is turned on, this block is moved towards the top of the web.config file.
if ($config->notracetrack == 1 && $restrictIP === 'none')
{
$webConfig .= <<<XML
<!-- Disable HTTP methods TRACE and TRACK (protect against XST) -->
<security>
<requestFiltering>
<verbs>
<add verb="TRACE" allowed="false" />
<add verb="TRACK" allowed="false" />
</verbs>
</requestFiltering>
</security>
XML;
}
$webConfig .= <<< XML
</system.webServer>
</configuration>
XML;
return $webConfig;
}
private function ipRangeToIPandNetmask(string $ipRange): array
{
$ipRange = explode('/', $ipRange);
$ip = $ipRange[0];
$cidr = $ipRange[1] ?? null;
$isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
$cidr ??= $isIPv4 ? 32 : 128;
$isNumeric = preg_match('/^\d+$/', $cidr);
if (!$isNumeric)
{
$netmask = $cidr;
}
elseif ($isIPv4)
{
$cidr = min(max($cidr, 0), 32);
$netmask = long2ip(~((1 << (32 - $cidr)) - 1));
}
else
{
$cidr = min(max($cidr, 0), 128);
$mask = str_repeat('1', $cidr) . str_repeat('0', 128 - $cidr);
$segments = str_split($mask, 16);
$hextet = array_map(
function ($segment) {
return str_pad(dechex(bindec($segment)), 4, '0', STR_PAD_LEFT);
}, $segments
);
$netmask = implode(':', $hextet);
}
return [$ip, $netmask];
}
}