Match a whitelist of IP addresses in PHP - php

I have a list of IP ranges like
$whitelist=array('50*','202.16*','123.168*',');
I want to block all other traffic from seeing the page.
I've tried
if(in_array($_SERVER['REMOTE_ADDR'],$whitelist)){
//display page
}

in_array doesn't use regexs to compare. Also your regex is incorrect the * is a quantifier. That allows zero or more of the previous character.
Try:
$whitelist=array('50\..*','202\.16\..*','123\.168\..*');
if(preg_match('/^(' . implode('|', $whitelist) . ')/', $_SERVER['REMOTE_ADDR'])){
The .* is allowing anything (pretty much(see s modifier http://php.net/manual/en/reference.pcre.pattern.modifiers.php), . is any character and then paired with that quantifier previously mentioned). The ^ is the start of the string. \. is a literal .. The | is an or.
Demo: https://eval.in/571019
Regex Demo: https://regex101.com/r/dC5uI0/1

This should work for you:
you can loop through white list ip and trim space and * (if found from right).
There after using substr you can cut the IP address of the same length of whitelist ip in loop, and compare both.
$whitelists = array('50*','202.16*','123.168*');
foreach($whitelists as $whitelist){
$whitelist = rtrim($whitelist, "*\r\n\t\0 ");
if(substr($_SERVER['REMOTE_ADDR'], 0, strlen($whitelist)) == $whitelist) {
$match = true;
break;
}
}
echo $match ? 'Match' : 'Not Match';

as stated by #chris85, in_array doesn't use regexs.
To do such a thing, you could simply use a loop like this:
if(preg_match('/^(' . implode('|', $whitelist) . ')/i', $_SERVER['REMOTE_ADDR'])){
// Do your stuff
}
Your '*' doesn't work as you thought.. That's ok:
$whitelist=array('50\.*','202.16\.*','123.168\.*');

Hope this helps someone. Seeing as how you didn't specifically ask about regular expressions, and since the topic is about matching IP addresses, I thought I'd put this out there so that it may help someone with a similar issue.
Server software usually strives to be as quick and efficient as possible; matching IP addresses is usually done arithmetically. That being said, I'll go over a fast way to do exactly what you're trying to do before going over a possible alternative.
If you're simply performing a wild card match on IP address strings, I would suggest that you use this method. It has been tailored to your use case, but I will include the simple matching function by itself.
For contrast, I've also included the sample output with execution times using this method compared to using PHP's RegEx functions (which are the way to go for more complex pattern matching)
NOTES:
I use the functions referenced herein for very specific purposes. They are applicable to this scenario because IP addresses don't have '*' characters in them. As they are written, if the variable you're testing has a '*' character in it, it will only match against a wild card character so there is a slight possibility for loss of information there.
If you're writing a command-line daemon or your process will use the same list of IPs to check against multiple times during its lifetime, it is beneficial to use the RegEx library. The small speed benefit to using my method here is only won when having to initially load and prepare the IP list RegEx for first use.
You can move the code from "wildcard_match()" to inside of "match_ip() for further benefit, avoiding the overhead of another function call.
Code (you can copy and paste):
<?php
/**
* This function compares a string ("$test") to see if it is
* equal to another string ("$wild"). An '*' in "$wild" will
* match any characters in "$test".
*
* #param string $wild - The string to compare against. This may
* be either an exact character string to match, or a string
* with a wild card ('*') character that will match any character(s)
* found in "$test".
*
* #param string $test - A character string we're comparing against
* "$wild" to determine if there is a match.
*
* #return bool Returns TRUE if "$test" is either an exact match to
* "$wild", or it fits the bill taking any wild card characters into
* consideration.
*
**/
function wildcard_match( $pattern, $test ) {
$p = 0;
$a_name = explode("*", $pattern);
$segs = count($a_name);
$max_seg = ($segs-1);
$plen = 0;
$test_len = strlen($test);
for ($i = 0; $i < $segs; $i++) {
$part = $a_name[$i];
$plen = strlen($part);
if ($plen === 0) {
if ($i === $max_seg) return true;
continue;
}
$p = strpos($test, $part, $p);
if ($p === false) {
return false;
}
$p+=$plen;
}
if ($p===$test_len) {
return true;
}
return false;
}
/**
* Function to quickly traverse an array of whole, or
* wild card IPv4 addresses given in "$whitelist" and
* determine if they match the given whole IPv4
* address in "$test".
*
* #param array $whitelist - An array of IPv4 addresses, either
* whole, or containing an '*' character wherein any character(s)
* in "$test" will match.
*
* #param string $test - A complete string (dot-decimal) IPv4
* address to compare against the contents of the array given in
* parameter one ("$whitelist").
*
* #return bool Returns TRUE, if the IPv4 address given in "$test" was
* matched to an IPv4 address or IPv4 wild card pattern in the array
* given in parameter one ("$whitelist").
*
**/
function match_ip( $whitelist, $test ) {
foreach ($whitelist as $w) {
if (wildcard_match($w, $test)) return true;
}
return false;
}
/* The array of IP addresses we're going to validate */
$check_array = array("50.245.1.9", "35.125.25.255", "202.16.15.25");
/* The array as given in your example (minus the extra ' at the end) */
$whitelist1=array('50*','202.16*','123.168*');
/* An array for RegEx matching */
$whitelist2=array('50\..*','202\.16\..*','123\.168\..*');
microtime(true); /* Execute this once to make sure its module is loaded */
echo "Giving PHP a second to get its ducks in a row...\n";
usleep(1000000); /** Give PHP a second to load and prepare */
$st = microtime(true);
foreach ($check_array as $c) {
if (match_ip($whitelist1, $c)) {
echo "$c....Match!\n";
} else {
echo "$c....No match!\n";
}
}
$dt = microtime(true)-$st;
echo "Time: $dt\n\n\n\n";
$st = microtime(true);
foreach ($check_array as $c) {
if(preg_match('/^(' . implode('|', $whitelist2) . ')/', $c)){
echo "$c....Match!\n";
} else {
echo "$c....No Match!\n";
}
}
$dt = microtime(true)-$st;
echo "Time: $dt\n\n";
The output from this is:
Giving PHP a second to get its ducks in a row...
50.245.1.9....Match!
35.125.25.255....No match!
202.16.15.25....Match!
Time 1: 0.00027704238891602
50.245.1.9....Match!
35.125.25.255....No Match!
202.16.15.25....Match!
Time 2: 0.00040698051452637
The first result set is from the function "match_ip()", the second result set is from firing up the RegEx library.
Now, a possible better solution to wild card matching IPs would be to employ an array of IP addresses in CIDR notation in your array. Often times, you want IP traffic to be allowed from a specific network or range of IP addresses.
There are quite a few assumptions here, but for example (using your "$whitelist" array):
'50*' might be construed as "I want all IP addressess from 50.xxx.xxx.xxx to be allowed access.
In that case, you'd specify the format "50.0.0.0/8". (The "0"s after the "50." can be any number. They will be completely ignored because of the "/8".)
xxx.xxx.xxx.xxx
| | | |
8 16 24 32
An IPv4 address is computationally 32 bits, so above, you're saying that all you care about is the first 8 bits matching.
'123.168*' would be "123.168.0.0/16"
"101.23.54.0/24" would allow all IP addresses starting with "101.23.54" access.
"44.32.240.10/32" would only allow the IP address "44.32.240.10" access. No range.
So you could do something like this:
<?php
/**
* Determines if the two given IPv4 addresses
* are equal, or are on the same network using
* the given number of "$mask" bits.
*
* #param string $ip1 - The first string dot-decimal IPv4
* address.
*
* #param string $ip1 - The second string dot-decimal IPv4
* address.
*
* #param int $mask - The number of bits in the mask.
*
* #return bool Returns TRUE if they match after being
* masked, or FALSE if not.
*
*/
function ip_match( $ip1, $ip2, $mask) {
$mask = (int)$mask;
$ip1 = ip2long($ip1);
$ip2 = ip2long($ip2);
if ($ip1 === false || $ip2 === false) return false;
if ($mask < 1 || $mask > 32) return false;
$mask = (0x00000000FFFFFFFF & 0x00000000FFFFFFFF << (32-$mask));
if ( ($ip1 & $mask) === ($ip2 & $mask) ) {
return true;
}
return false;
}
/**
* Takes an array of string (CIDR) network representations and
* sorts them into an array used later for checking against IP
* addresses.
*
* #param array $cidr_array - An array of IP addressess in
* CIDR notation e.g. 192.168.1.1/24
*
* #return array Returns an array of objects with the following
* properties:
* 'ip' - The string (dot-decimal) IP address that
* has been numerically verified for use
* in comparisons later.
*
* 'mask' - The number of bits used for creating
* the subnet mask against which IP
* addresses will be compared.
*
**/
function make_whitelist( $cidr_array ) {
$wl = array();
$lip = 0;
$bm = 0;
$spos = 0;
if (!is_array($cidr_array)) return false;
foreach ($cidr_array as $ip) {
$spos = strpos($ip, "/");
if ($spos === false) {
$bm = 32; /* If there's no "/", assume
* that we want an EXACT IP
* address. Hence the 32 bit
* mask
**/
} else {
$bm = (int)substr($ip, ($spos+1));
$ip = substr($ip, 0, $spos++);
}
$lip = ip2long($ip); /* Using this here to check IP validity
* before storing it in the array...
* We use ip2long() later for comparisons.
*
* You can store it this way - as a long -
* instead of as a string (I do) to
* use less memory if you wish.
*
**/
if ($bm === 0) continue; /* A bit mask of ZERO will block
* ALL IP addresses, skip it
* for the example.
**/
if ($lip === false) continue; /* If it's an invalid IP, skip it,
* you could optionally try to
* resolve it as a hostname using
* gethostbyname() or gethostbynamel()
* here...
**/
array_push($wl, (object)array('ip'=>$ip, 'mask'=>$bm));
}
return $wl;
}
$whitelist = make_whitelist(array("50.0.0.0/8", "202.16.0.0/16", "123.168.0.0/16", "1.1.1.1"));
$ips_to_check = array("50.1.174.41", "42.123.100.23", "123.168.4.79", "1.1.1.2", "1.1.1.1");
foreach ($ips_to_check as $ip) {
foreach ($whitelist as $w) {
if (ip_match($ip, $w->ip, $w->mask)) {
echo "\"$ip\" is allowed!\n";
continue 2;
}
}
echo "\"$ip\" is NOT allowed!\n";
}
I know this is a lot, but there is plenty here for people to think about, find while searching, and hopefully be able to use to make their lives easier!

Related

PHP - Get range boundaries from IPv6 and cidr [duplicate]

I am able to do this with IPv4 using code snippets from various online sources. I was wondering if there was a way to do it with IPv6.
Basically I just need a form that I can enter an IPv6 address and prefix (ex: f080:42d2:581a::0/68) and it calculates the network address, first useable address, last useable address, and broadcast address. Then just prints to screen. Not looking to store it in a database or anything yet.
First of all: IPv6 doesn't have network and broadcast addresses. You can use all addresses in a prefix. Second: On a LAN the prefix length is always (well, 99.x% of the time) a /64. Routing a /68 would break IPv6 features like stateless auto configuration.
Below is a verbose implementation of an IPv6 prefix calculator:
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1400::/54';
// Split in address and prefix length
list($firstaddrstr, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$firstaddrbin = inet_pton($firstaddrstr);
// Convert the binary string to a string with hexadecimal characters
# unpack() can be replaced with bin2hex()
# unpack() is used for symmetry with pack() below
$firstaddrhex = reset(unpack('H*', $firstaddrbin));
// Overwriting first address string to make sure notation is optimal
$firstaddrstr = inet_ntop($firstaddrbin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal string of the last address
$lastaddrhex = $firstaddrhex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the character at this position
$orig = substr($lastaddrhex, $pos, 1);
// Convert it to an integer
$origval = hexdec($orig);
// OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$newval = $origval | (pow(2, min(4, $flexbits)) - 1);
// Convert it back to a hexadecimal character
$new = dechex($newval);
// And put that character back in the string
$lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal string to a binary string
# Using pack() here
# Newer PHP version can use hex2bin()
$lastaddrbin = pack('H*', $lastaddrhex);
// And create an IPv6 address from the binary string
$lastaddrstr = inet_ntop($lastaddrbin);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $firstaddrstr\n";
echo "Last: $lastaddrstr\n";
?>
It should output:
Prefix: 2001:db8:abc:1400::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff
This is a fix to the accepted answer, which incorrectly assumes the "first address" should be identical to the inputted string. Rather, it needs to have its value modified via an AND operator against its mask.
To demonstrate the problem, consider this example input: 2001:db8:abc:1403::/54
Expected result:
First: 2001:db8:abc:1400::
Actual result:
First: 2001:db8:abc:1403::
The relevant math to calculate the mask for a given 4-bit sequence is:
// Calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$newval = $origval & $mask;
Full code
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1403::/54';
// Split in address and prefix length
list($addr_given_str, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$addr_given_bin = inet_pton($addr_given_str);
// Convert the binary string to a string with hexadecimal characters
$addr_given_hex = bin2hex($addr_given_bin);
// Overwriting first address string to make sure notation is optimal
$addr_given_str = inet_ntop($addr_given_bin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal strings of the first and last addresses
$addr_hex_first = $addr_given_hex;
$addr_hex_last = $addr_given_hex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the characters at this position
$orig_first = substr($addr_hex_first, $pos, 1);
$orig_last = substr($addr_hex_last, $pos, 1);
// Convert them to an integer
$origval_first = hexdec($orig_first);
$origval_last = hexdec($orig_last);
// First address: calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$new_val_first = $origval_first & $mask;
// Last address: OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$new_val_last = $origval_last | (pow(2, min(4, $flexbits)) - 1);
// Convert them back to hexadecimal characters
$new_first = dechex($new_val_first);
$new_last = dechex($new_val_last);
// And put those character back in their strings
$addr_hex_first = substr_replace($addr_hex_first, $new_first, $pos, 1);
$addr_hex_last = substr_replace($addr_hex_last, $new_last, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal strings to a binary string
$addr_bin_first = hex2bin($addr_hex_first);
$addr_bin_last = hex2bin($addr_hex_last);
// And create an IPv6 address from the binary string
$addr_str_first = inet_ntop($addr_bin_first);
$addr_str_last = inet_ntop($addr_bin_last);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $addr_str_first\n";
echo "Last: $addr_str_last\n";
Outputs:
Prefix: 2001:db8:abc:1403::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff
For those who stumble upon this question, you can do this more effectively using the dtr_pton and dtr_ntop functions and dTRIP class found on GitHub.
We also have noticed a lack of focus and tools with IPv6 in PHP, and put together this article, http://www.highonphp.com/5-tips-for-working-with-ipv6-in-php, which may be of help to others.
Function Source
This converts and IP to a binary representation:
/**
* dtr_pton
*
* Converts a printable IP into an unpacked binary string
*
* #author Mike Mackintosh - mike#bakeryphp.com
* #param string $ip
* #return string $bin
*/
function dtr_pton( $ip ){
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
return current( unpack( "A4", inet_pton( $ip ) ) );
}
elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return current( unpack( "A16", inet_pton( $ip ) ) );
}
throw new \Exception("Please supply a valid IPv4 or IPv6 address");
return false;
}
This converts a binary representation to printable IP:
/**
* dtr_ntop
*
* Converts an unpacked binary string into a printable IP
*
* #author Mike Mackintosh - mike#bakeryphp.com
* #param string $str
* #return string $ip
*/
function dtr_ntop( $str ){
if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
return inet_ntop( pack( "A".strlen( $str ) , $str ) );
}
throw new \Exception( "Please provide a 4 or 16 byte string" );
return false;
}
Examples
Using the dtr_pton function you can:
$ip = dtr_pton("fe80:1:2:3:a:bad:1dea:dad");
$mask = dtr_pton("ffff:ffff:ffff:ffff:ffff:fff0::");
Get your Network and Broadcast:
var_dump( dtr_ntop( $ip & $mask ) );
var_dump( dtr_ntop( $ip | ~ $mask ) );
And your output would be:
string(18) "fe80:1:2:3:a:ba0::"
string(26) "fe80:1:2:3:a:baf:ffff:ffff"
Well, for posterity, I'm adding my code here. And also as a thanks to you guys who helped me nail this down as I needed it for an ipv6/ip2country script.
It's slightly inspired by code posted here by #mikemacintosh and #Sander Steffann, slightly improved (whishful thinking) and returns a nice object packing all the data you do/don't need:
/**
* This:
* <code>
* Ipv6_Prefix2Range('2001:43f8:10::/48');
* </code>
* returns this:
* <code>
* object(stdClass)#2 (4) {
* ["Prefix"]=>
* string(17) "2001:43f8:10::/48"
* ["FirstHex"]=>
* string(32) "200143f8001000000000000000000000"
* ["LastHex"]=>
* string(32) "200143f80010ffffffffffffffffffff"
* ["MaskHex"]=>
* string(32) "ffffffffffff00000000000000000000"
* // Optional bin equivalents available
* }
* </code>
*
* Tested against:
* #link https://www.ultratools.com/tools/ipv6CIDRToRange
*
* #param string $a_Prefix
* #param bool $a_WantBins
* #return object
*/
function Ipv6_Prefix2Range($a_Prefix, $a_WantBins = false){
// Validate input superficially with a RegExp and split accordingly
if(!preg_match('~^([0-9a-f:]+)[[:punct:]]([0-9]+)$~i', trim($a_Prefix), $v_Slices)){
return false;
}
// Make sure we have a valid ipv6 address
if(!filter_var($v_FirstAddress = $v_Slices[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return false;
}
// The /## end of the range
$v_PrefixLength = intval($v_Slices[2]);
if($v_PrefixLength > 128){
return false; // kind'a stupid :)
}
$v_SuffixLength = 128 - $v_PrefixLength;
// Convert the binary string to a hexadecimal string
$v_FirstAddressBin = inet_pton($v_FirstAddress);
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Build the hexadecimal string of the network mask
// (if the manually formed binary is too large, base_convert() chokes on it... so we split it up)
$v_NetworkMaskHex = str_repeat('1', $v_PrefixLength) . str_repeat('0', $v_SuffixLength);
$v_NetworkMaskHex_parts = str_split($v_NetworkMaskHex, 8);
foreach($v_NetworkMaskHex_parts as &$v_NetworkMaskHex_part){
$v_NetworkMaskHex_part = base_convert($v_NetworkMaskHex_part, 2, 16);
$v_NetworkMaskHex_part = str_pad($v_NetworkMaskHex_part, 2, '0', STR_PAD_LEFT);
}
$v_NetworkMaskHex = implode(null, $v_NetworkMaskHex_parts);
unset($v_NetworkMaskHex_part, $v_NetworkMaskHex_parts);
$v_NetworkMaskBin = inet_pton(implode(':', str_split($v_NetworkMaskHex, 4)));
// We have the network mask so we also apply it to First Address
$v_FirstAddressBin &= $v_NetworkMaskBin;
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Convert the last address in hexadecimal
$v_LastAddressBin = $v_FirstAddressBin | ~$v_NetworkMaskBin;
$v_LastAddressHex = bin2hex($v_LastAddressBin);
// Return a neat object with information
$v_Return = array(
'Prefix' => "{$v_FirstAddress}/{$v_PrefixLength}",
'FirstHex' => $v_FirstAddressHex,
'LastHex' => $v_LastAddressHex,
'MaskHex' => $v_NetworkMaskHex,
);
// Bins are optional...
if($a_WantBins){
$v_Return = array_merge($v_Return, array(
'FirstBin' => $v_FirstAddressBin,
'LastBin' => $v_LastAddressBin,
'MaskBin' => $v_NetworkMaskBin,
));
}
return (object)$v_Return;
}
I like functions and classes and dislike non-reusable code where reusable functionality is implemented.
PS: If you find issues with it, please get back to me. I'm far from an expert in IPv6.

Code generator with Laravel

I am using Laravel 4 and I am looking for a system to generate simple short alphanumeric codes, kinda like what Steam does when you login from a new machine, they send you an email with some text like this:
Someone has tried to log in into your account from an unknown device.
If this is really you, please input this code to authenticate the
access:
ZT27K
Thank you
Is there some sort of random, short codes generator in Laravel?
Yup! A bit down on the helpers documentation page is the str_random() function, which can be used anywhere like so:
$string = str_random(5);
(Note: it defaults to 16 if its length argument is left out.)
This is how I implemented mine, thoughStr::random($length) would be better:
/**
* #param string prefix Any desired character to prepend on the generated code
* #param int length a number indicating the number of characters in the code
* #param string factor a string of characters to be mixed to generate the code
* #return string string with random characters based the provided length
*/
function generateCode($prefix = '', $length = 8, $factor = null)
{
$s = "9/A/B/0/C/2/D/E/3/F/G/H/I//J/4/K/L/M/N/O/5/P/7/R/S/8/T/U/V/6/W/X/Y/Z";
if ($factor === null)
$factor = $s;
$rdm_text_arr = explode("/", $factor, strlen($factor));
$code_array = array();
array_push($code_array, $prefix);
for ($i = 1; $i <= $length; $i++) :
array_push($code_array, $rdm_text_arr[mt_rand(0, count($rdm_text_arr) - 1)]);
endfor;
return implode("", $code_array);
}
and this is a sample output: WJH3Z0MX
You may also try Str::random(8) which gives 8 random characters.

Calculate an IPv6 range from a CIDR prefix?

I am able to do this with IPv4 using code snippets from various online sources. I was wondering if there was a way to do it with IPv6.
Basically I just need a form that I can enter an IPv6 address and prefix (ex: f080:42d2:581a::0/68) and it calculates the network address, first useable address, last useable address, and broadcast address. Then just prints to screen. Not looking to store it in a database or anything yet.
First of all: IPv6 doesn't have network and broadcast addresses. You can use all addresses in a prefix. Second: On a LAN the prefix length is always (well, 99.x% of the time) a /64. Routing a /68 would break IPv6 features like stateless auto configuration.
Below is a verbose implementation of an IPv6 prefix calculator:
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1400::/54';
// Split in address and prefix length
list($firstaddrstr, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$firstaddrbin = inet_pton($firstaddrstr);
// Convert the binary string to a string with hexadecimal characters
# unpack() can be replaced with bin2hex()
# unpack() is used for symmetry with pack() below
$firstaddrhex = reset(unpack('H*', $firstaddrbin));
// Overwriting first address string to make sure notation is optimal
$firstaddrstr = inet_ntop($firstaddrbin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal string of the last address
$lastaddrhex = $firstaddrhex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the character at this position
$orig = substr($lastaddrhex, $pos, 1);
// Convert it to an integer
$origval = hexdec($orig);
// OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$newval = $origval | (pow(2, min(4, $flexbits)) - 1);
// Convert it back to a hexadecimal character
$new = dechex($newval);
// And put that character back in the string
$lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal string to a binary string
# Using pack() here
# Newer PHP version can use hex2bin()
$lastaddrbin = pack('H*', $lastaddrhex);
// And create an IPv6 address from the binary string
$lastaddrstr = inet_ntop($lastaddrbin);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $firstaddrstr\n";
echo "Last: $lastaddrstr\n";
?>
It should output:
Prefix: 2001:db8:abc:1400::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff
This is a fix to the accepted answer, which incorrectly assumes the "first address" should be identical to the inputted string. Rather, it needs to have its value modified via an AND operator against its mask.
To demonstrate the problem, consider this example input: 2001:db8:abc:1403::/54
Expected result:
First: 2001:db8:abc:1400::
Actual result:
First: 2001:db8:abc:1403::
The relevant math to calculate the mask for a given 4-bit sequence is:
// Calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$newval = $origval & $mask;
Full code
<?php
/*
* This is definitely not the fastest way to do it!
*/
// An example prefix
$prefix = '2001:db8:abc:1403::/54';
// Split in address and prefix length
list($addr_given_str, $prefixlen) = explode('/', $prefix);
// Parse the address into a binary string
$addr_given_bin = inet_pton($addr_given_str);
// Convert the binary string to a string with hexadecimal characters
$addr_given_hex = bin2hex($addr_given_bin);
// Overwriting first address string to make sure notation is optimal
$addr_given_str = inet_ntop($addr_given_bin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal strings of the first and last addresses
$addr_hex_first = $addr_given_hex;
$addr_hex_last = $addr_given_hex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the characters at this position
$orig_first = substr($addr_hex_first, $pos, 1);
$orig_last = substr($addr_hex_last, $pos, 1);
// Convert them to an integer
$origval_first = hexdec($orig_first);
$origval_last = hexdec($orig_last);
// First address: calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));
// AND the original against its mask
$new_val_first = $origval_first & $mask;
// Last address: OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$new_val_last = $origval_last | (pow(2, min(4, $flexbits)) - 1);
// Convert them back to hexadecimal characters
$new_first = dechex($new_val_first);
$new_last = dechex($new_val_last);
// And put those character back in their strings
$addr_hex_first = substr_replace($addr_hex_first, $new_first, $pos, 1);
$addr_hex_last = substr_replace($addr_hex_last, $new_last, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal strings to a binary string
$addr_bin_first = hex2bin($addr_hex_first);
$addr_bin_last = hex2bin($addr_hex_last);
// And create an IPv6 address from the binary string
$addr_str_first = inet_ntop($addr_bin_first);
$addr_str_last = inet_ntop($addr_bin_last);
// Report to user
echo "Prefix: $prefix\n";
echo "First: $addr_str_first\n";
echo "Last: $addr_str_last\n";
Outputs:
Prefix: 2001:db8:abc:1403::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff
For those who stumble upon this question, you can do this more effectively using the dtr_pton and dtr_ntop functions and dTRIP class found on GitHub.
We also have noticed a lack of focus and tools with IPv6 in PHP, and put together this article, http://www.highonphp.com/5-tips-for-working-with-ipv6-in-php, which may be of help to others.
Function Source
This converts and IP to a binary representation:
/**
* dtr_pton
*
* Converts a printable IP into an unpacked binary string
*
* #author Mike Mackintosh - mike#bakeryphp.com
* #param string $ip
* #return string $bin
*/
function dtr_pton( $ip ){
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
return current( unpack( "A4", inet_pton( $ip ) ) );
}
elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return current( unpack( "A16", inet_pton( $ip ) ) );
}
throw new \Exception("Please supply a valid IPv4 or IPv6 address");
return false;
}
This converts a binary representation to printable IP:
/**
* dtr_ntop
*
* Converts an unpacked binary string into a printable IP
*
* #author Mike Mackintosh - mike#bakeryphp.com
* #param string $str
* #return string $ip
*/
function dtr_ntop( $str ){
if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
return inet_ntop( pack( "A".strlen( $str ) , $str ) );
}
throw new \Exception( "Please provide a 4 or 16 byte string" );
return false;
}
Examples
Using the dtr_pton function you can:
$ip = dtr_pton("fe80:1:2:3:a:bad:1dea:dad");
$mask = dtr_pton("ffff:ffff:ffff:ffff:ffff:fff0::");
Get your Network and Broadcast:
var_dump( dtr_ntop( $ip & $mask ) );
var_dump( dtr_ntop( $ip | ~ $mask ) );
And your output would be:
string(18) "fe80:1:2:3:a:ba0::"
string(26) "fe80:1:2:3:a:baf:ffff:ffff"
Well, for posterity, I'm adding my code here. And also as a thanks to you guys who helped me nail this down as I needed it for an ipv6/ip2country script.
It's slightly inspired by code posted here by #mikemacintosh and #Sander Steffann, slightly improved (whishful thinking) and returns a nice object packing all the data you do/don't need:
/**
* This:
* <code>
* Ipv6_Prefix2Range('2001:43f8:10::/48');
* </code>
* returns this:
* <code>
* object(stdClass)#2 (4) {
* ["Prefix"]=>
* string(17) "2001:43f8:10::/48"
* ["FirstHex"]=>
* string(32) "200143f8001000000000000000000000"
* ["LastHex"]=>
* string(32) "200143f80010ffffffffffffffffffff"
* ["MaskHex"]=>
* string(32) "ffffffffffff00000000000000000000"
* // Optional bin equivalents available
* }
* </code>
*
* Tested against:
* #link https://www.ultratools.com/tools/ipv6CIDRToRange
*
* #param string $a_Prefix
* #param bool $a_WantBins
* #return object
*/
function Ipv6_Prefix2Range($a_Prefix, $a_WantBins = false){
// Validate input superficially with a RegExp and split accordingly
if(!preg_match('~^([0-9a-f:]+)[[:punct:]]([0-9]+)$~i', trim($a_Prefix), $v_Slices)){
return false;
}
// Make sure we have a valid ipv6 address
if(!filter_var($v_FirstAddress = $v_Slices[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
return false;
}
// The /## end of the range
$v_PrefixLength = intval($v_Slices[2]);
if($v_PrefixLength > 128){
return false; // kind'a stupid :)
}
$v_SuffixLength = 128 - $v_PrefixLength;
// Convert the binary string to a hexadecimal string
$v_FirstAddressBin = inet_pton($v_FirstAddress);
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Build the hexadecimal string of the network mask
// (if the manually formed binary is too large, base_convert() chokes on it... so we split it up)
$v_NetworkMaskHex = str_repeat('1', $v_PrefixLength) . str_repeat('0', $v_SuffixLength);
$v_NetworkMaskHex_parts = str_split($v_NetworkMaskHex, 8);
foreach($v_NetworkMaskHex_parts as &$v_NetworkMaskHex_part){
$v_NetworkMaskHex_part = base_convert($v_NetworkMaskHex_part, 2, 16);
$v_NetworkMaskHex_part = str_pad($v_NetworkMaskHex_part, 2, '0', STR_PAD_LEFT);
}
$v_NetworkMaskHex = implode(null, $v_NetworkMaskHex_parts);
unset($v_NetworkMaskHex_part, $v_NetworkMaskHex_parts);
$v_NetworkMaskBin = inet_pton(implode(':', str_split($v_NetworkMaskHex, 4)));
// We have the network mask so we also apply it to First Address
$v_FirstAddressBin &= $v_NetworkMaskBin;
$v_FirstAddressHex = bin2hex($v_FirstAddressBin);
// Convert the last address in hexadecimal
$v_LastAddressBin = $v_FirstAddressBin | ~$v_NetworkMaskBin;
$v_LastAddressHex = bin2hex($v_LastAddressBin);
// Return a neat object with information
$v_Return = array(
'Prefix' => "{$v_FirstAddress}/{$v_PrefixLength}",
'FirstHex' => $v_FirstAddressHex,
'LastHex' => $v_LastAddressHex,
'MaskHex' => $v_NetworkMaskHex,
);
// Bins are optional...
if($a_WantBins){
$v_Return = array_merge($v_Return, array(
'FirstBin' => $v_FirstAddressBin,
'LastBin' => $v_LastAddressBin,
'MaskBin' => $v_NetworkMaskBin,
));
}
return (object)$v_Return;
}
I like functions and classes and dislike non-reusable code where reusable functionality is implemented.
PS: If you find issues with it, please get back to me. I'm far from an expert in IPv6.

What is the best way to validate a credit card in codeigniter

hi i am using codeigniter . i want to validate my credit card details . i saw there are classes in php to validate credit card numbers . i saw a helper in codeigniter to validate credit cards
http://codeigniter.com/wiki/Credit_Card_Helper
/**
* Truncates a card number retaining only the first 4 and the last 4 digits. It then returns the truncated form.
*
* #param string The card number to truncate.
* #return string The truncated card number.
*/
function truncate_card($card_num) {
$padsize = (strlen($card_num) < 7 ? 0 : strlen($card_num) - 7);
return substr($card_num, 0, 4) . str_repeat('X', $padsize). substr($card_num, -3);
}
/**
* Validates a card expiry date. Finds the midnight on first day of the following
* month and ensures that is greater than the current time (cards expire at the
* end of the printed month). Assumes basic sanity checks have already been performed
* on month/year (i.e. length, numeric, etc).
*
* #param integer The expiry month shown on the card.
* #param integer The expiry year printed on the card.
* #return boolean Returns true if the card is still valid, false if it has expired.
*/
function card_expiry_valid($month, $year) {
$expiry_date = mktime(0, 0, 0, ($month + 1), 1, $year);
return ($expiry_date > time());
}
/**
* Strips all non-numerics from the card number.
*
* #param string The card number to clean up.
* #return string The stripped down card number.
*/
function card_number_clean($number) {
return ereg_replace("[^0-9]", "", $number);
}
/**
* Uses the Luhn algorithm (aka Mod10) <http://en.wikipedia.org/wiki/Luhn_algorithm>
* to perform basic validation of a credit card number.
*
* #param string The card number to validate.
* #return boolean True if valid according to the Luhn algorith, false otherwise.
*/
function card_number_valid ($card_number) {
$card_number = strrev(card_number_clean($card_number));
$sum = 0;
for ($i = 0; $i < strlen($card_number); $i++) {
$digit = substr($card_number, $i, 1);
// Double every second digit
if ($i % 2 == 1) {
$digit *= 2;
}
// Add digits of 2-digit numbers together
if ($digit > 9) {
$digit = ($digit % 10) + floor($digit / 10);
}
$sum += $digit;
}
// If the total has no remainder it's OK
return ($sum % 10 == 0);
}
?>
it uses a common validation . but i want a validation according to card type like this
http://www.braemoor.co.uk/software/creditcard.php
is there any libraries or helpers in codeigniter . please help.....................
As people already told you, CodeIgniter is a php framework, coded using php, works in a php environment and makes use of..,php classes and functions :).
What's more, the file you linked to is a simple function. One function. You know what you can do? Take the file as it is, name it creditcard_helper.php, put it inside the helpers folder, open it and place the whole code inside this snippet (ugly but necessary, as whenever you'll load the helper a second time it would give you error otherwise):
if(!function_exists('checkCreditCard')
{
//the whole content goes here untouched;
}
And you're set. Just use:
$this->load->helper('creditcard');
if(checkCreditCard($cardnumber, $cardname, &$errornumber, &$errortext))
{
echo 'card OK';
}
else
{
echo 'wrong card type/number';
}
I found this helper inside the CodeIgniter Github wiki page. When dropped inside your helpers folder, you can then use the functions from the file in a controller or model you load it in.

Generate random string, check it against database, then use it

This is an issue which keeps coming up for me when using random strings.
This is basically the process.
Generate random string
Check if it already exists in the database
If it doesn't use it, else generate another one
So how would I do this using PHP?
Why not just use a GUID and let the database maintain this. You could then just call a stored proc.
I would use this function, very simple:
/**
* Generates a string with random characters taken from the given string
* of the desided length
*
* #param int $length the resulting string length
* #param string $chars the chars; defaults to all alphanumerics
* #return string the random string
* #author Andrea Baron
*/
function rand_string($length,
$chars = 'qwertyuiopasdfghjklzxcvbnm0123456789') {
$r = '';
$cm = strlen($chars)-1;
for($i=1; $i<=$length; ++$i) {
$r .= $chars[mt_rand(0, $cm)];
}
return $r;
}
then you can do something like this
$used = $db->prepare('SELECT EXISTS(SELECT string FROM table WHERE string = ?)');
do {
$string = rand_string(32);
$used->execute(array($string));
$exists = $used->fetch(PDO::FETCH_NUM);
$used->closeCursor();
} while($exists && $exists[0]);
$ins = PP::$db->prepare('INSERT INTO table SET string=?');
$ins->execute(array($string));
if you use PDO and MySQL; I would set the string field as Primary Key, or a HASH index if on a secondary field; or more concise and, maybe, not as strong:
do {
$string = rand_string(32);
$exists = PP::$db->exec('INSERT INTO a SET string='.PP::$db->quote($string));
} while(!$exists);
this because exec() returns the number of affected rows on no error, or false if there's an error, in this case a duplicate key value.
Or, as an alternative, you can add a timestamp and forget about checking at all. Like:
$string = rand_string(8).time();
The probability of having a duplicate identifier resets each second, and, with 8 characters, as you see is 1 in 37^8, or 3.5E12.

Categories