Basic Fun with IPs in PHP

Posted by Nessa | Posted in uncategorized | Posted on 07-08-2012

1

I was working on an application a couple weeks ago that required the ability to extract data about an IP range when only a CIDR is provider.  Of course I checked the oracle first because I’m too lazy to write my own code, but was disappointed to be unable to find a PHP script that did exactly what I needed.  I did manage to find this nifty little function which made things so much easier:

function cidrToRange($cidr) {
    $range = array();
    $cidr = explode('/', $cidr);
    $range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
    $range[1] = long2ip((ip2long($cidr[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
    return $range;
}

This function, those short in stature, is actually all you need to extract data from a provided CIDR range. Firstly, let’s set the value of $cidr to something simple, and get the value of the returned $range array:

$cidr="192.168.1.0/24";
$range=cidrToRange($cidr);
print_r($range);

Result:

Array
(
    [0] => 192.168.1.0
    [1] => 192.168.1.255
)

If you notice, the array contains the values of the first and last IPs in the CIDR range.  Therefore, you can grab these values out rather easily:

$first_ip = $range[0];
$last_ip = $range[1];

Getting the gateway, broadcast, and subnet mask

To get the gateway and broadcast IPs, well, you’ve already done it. This is the first (usually) and last IP in the block.  All you need now is the subnet mask, and to pull this out you’ll need to separate the CIDR mask (what follows the “/”) from the value of $cidr:

$cidrmask = explode("/",$cidr);
$cidrmask = $cidrmask[1];

Once we have the bitmask, which in my example is “24”,  you can calculate the subnet mask:

$subnet_mask = long2ip(-1 &lt;&lt; (32 - (int)$cidrmask));

Listing all IPs in the CIDR range

Now for the fun part, which was not so fun when you’re automatically conditioned to think that everything has to be harder than it needs to be.  PHP has an ip2long function that basically converts an IPv4 IP address to a long forgettable number. The simplest way I found to pull out a list of IPs in a range is to convert the IP to its long format, increment it, then convert it back.  Here’s the full script:

function cidrToRange($cidr) {
    $range = array();
    $cidr = explode('/', $cidr);
    $range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
    $range[1] = long2ip((ip2long($cidr[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
    return $range;
}
 
$cidr="192.168.1.0/24";
 
$range = cidrToRange($cidr);
$first_ip = ip2long($range[0]);
$last_ip = ip2long($range[1]);
 
$first_ip++;
while($first_ip < $last_ip){
    $real_ip = long2ip($first_ip);
 
    if(!preg_match('/\.0$/',$real_ip)){ // Don't include IPs that end in .0
        echo $real_ip . "\n";
    }
 
    $first_ip++;
}

This will list out all the usable IPs in the CIDR provided (typically all the IPs that are not the gateway or broadcast addresses, or that end in in a “.0″.  Note the preg_match function within the while loop – this will prevent ips ending in .0 from being included, which is useful when dealing with blocks of /23 or higher.  This is of course just a starting point – you can adjust to meet the needs of how your networks are set up.

Basics of YAML Parsing in PHP

Posted by Nessa | Posted in uncategorized | Posted on 22-06-2012

0

I know I’ve been on hiatus for a while and have neglected to update my website in over a year. That’s what happens when you get married and pop out a kid three months early. But now that I have more time on my hands I promise to devote a little more time to the website, starting with some of the basic PHP tutorials that my readers have grown to know and love.

First off, YAML is not a markup language, nor does it claim to be (YAML Aint a Markup Language), but it does represent a decent way to statically store arrays of data that are easily parsed by almost any programming language.  I recently had to write a script to parse out domain data from a cPanel “userdata” file, which is a YAML-formatted template that cPanel uses to build an httpd.conf file:

---
addon_domains: {}

cp_php_magic_include_path.conf: 0
main_domain: v-nessa.net
parked_domains:
  - v-nessa.com
sub_domains:
  - mail.v-nessa.net
  - cpanel.v-nessa.net
  - anothersubdomain.v-nessa.net

Essentially with a YAML file you’ll treat the data like a multi-dimensional array – key/value pairs are separated by a colon, and sequences are prefaced by dashes.  PHP makes it easy to parse these files with the YAML PECL module.

First things first, does your PHP installation have YAML support?

root@server [~]# php -m |grep yaml

To install YAML for PHP:

Install libYAML

Then:


wget http://pecl.php.net/get/yaml-1.1.0.tgz
tar -xvzf yaml-1.1.0.tgz
cd yaml-1.1.0
phpize
./configure && make && make install

Then make sure that yaml.so is located in your php.ini

extension=yaml.so

In its simplest form, our YAML file can be parsed as so:

$file="/var/cpanel/userdata/myuser/main";
$parsed = yaml_parse_file($file);
 
print_r($parsed);

This will return the YAML file as a multi-dimensional array:

Array
(
    [addon_domains] => Array
        (
        )

    [cp_php_magic_include_path.conf] => 0
    [main_domain] => v-nessa.net
    [parked_domains] => Array
        (
            [0] => v-nessa.com
        )

    [sub_domains] => Array
        (
            [0] => mail.v-nessa.net
            [1] => cpanel.v-nessa.net
            [2] => anothersubdomain.v-nessa.net
        )

)

Then from here, we can simply parse the array as usual.  For example, this will print out all of the items under the parked_domains key:

foreach($parsed['parked_domains'] as $key=&gt;$value){
    echo "$value\n";
}

phpacademy.org Offers FREE PHP Tutorials

Posted by Nessa | Posted in uncategorized | Posted on 31-03-2011

4

There’s a new site out there that’s offering high quality PHP video tutorials – for FREE!  All you PHP gurus, check out phpacademy.org.  They currently have over 200 PHP and MySQL tutorials for both beginner and intermediate users.  Unlike other PHP tutorial sites, phpacademy is unique because the tutorials are all on video, so there’s less boring reading.  Did I also mention that it’s free?

Head on over and check them out, feel free to post comments and reviews here!

Error Installing APC via PECL

Posted by Nessa | Posted in uncategorized | Posted on 27-02-2011

1

I got the following error when compiling APC on one of the servers:

/root/APC-3.1.6/apc.c:464: error: ‘apc_regex’ has no member named ‘nreg’

To fix this, make sure that PHP is installed with PCRE support (type php -m for a list of modules), then install the pcre-devel package:

yum install pcre-devel

Using POST Variables Outside the Loop

Posted by Nessa | Posted in uncategorized | Posted on 24-02-2011

4

I was working on this mini contact form the other day where a user basically enters in a bunch of info, the form submits to itself via PHP_SELF, and sends out an email. Oddly enough, once I transferred the form to its permanent home and spent a decent chunk of time trying to figure out why it wasn’t working anymore, I discovered that my developement box still had register_globals enabled from a previous project I was working on.  Doh!

This is part of my loop, which basically just grabs all the $_POST variables and their values to compare them with a previous array ($required) that specifies what values should be present in order for the mailer to determine how much of the form was completed:

// Values passed: domain,email
foreach($_POST as $name => $value) {
    if(in_array($name,$required)){
        if(empty($value) && value != 0 ){
            $complete=0;
        }else{
            $complete=1;
        }
    }
}

The problem is, when I went to use the names of the variables or their values later in the script, I got nothing. This of course would work with register_globals enabled, but since I prefer to work in a more secure environment, a small adjustment was needed in to make those variables available outside the loop:

// Values passed: domain,email
 
foreach($_POST as $name => $value) {
    $$name = trim($value);
    if(in_array($name,$required)){
        if(empty($value) && value != 0 ){
            $complete=0;
        }else{
           $complete=1;
        }
    }
}

Pretty simple stuff, eh? All I did here was tell the loop to actually create a variable and value for each $_POST value that was part of the foreach loop. From there, all I had to do was use these values are normal ($domain,$email,etc)

Catching SoapClient Errors

Posted by Nessa | Posted in uncategorized | Posted on 01-02-2011

0

I recently wrote a PHP script that uses SoapClient to connect to a SOAP server to verify username/password credentials for a monitoring system.  The script basically returns the result as a yes or no – however, as we all know with SOAP, when you hit an error you get a mess of crap that comes back like this:

Uncaught SoapFault exception: [ERROR_BAD_LOGIN] Bad login or password. in /path/to/stuff.php:20

Stack trace:
#0 [internal function]: SoapClient-&gt;__call('GetAccountByNam...', Array)
#1 /path/to/stuff(20): Utils_SoapClient-&gt;GetAccountByName(Object(SoapVar))
#2 {main}

All I need is an error code, and when the SOAP function fails (which is expected if the login credentials are invalid), the script halts and I don’t get anything.  Luckily, PHP has pretty good exception handling that can tell SOAP to treat output like a test.   For example, the following line of code in the above example passes a username string ($struct) to a SOAP object, and is the line of code that is failing:

$result=$accountService->GetAccountByName(new SoapVar($struct, SOAP_ENC_OBJECT));
$result_username = $result->UserAccount->AccountId;

In my case, if the login credentials being tested are incorrect, the value of the $result variable should be empty.  To suppress the ugly SOAP error and only return what I want, I use exceptions:

>try {
$result=$accountService->GetAccountByName(new SoapVar($struct, SOAP_ENC_OBJECT));
}
catch(SoapFault $result){
}
catch(Exception $result){
}

Therefore, I can now use the value of $result as intended:

if(!empty($result_username)){
 
echo "Login OK";
exit(0);
}else{
</code>
<code>echo "Login failed";
exit(1);
}

Simple Way to Parse an x509 Certificate with PHP

Posted by Nessa | Posted in uncategorized | Posted on 03-11-2010

5

PHP has a nifty little function for parsing an x.509 SSL certificate into an array to easily pull out the elements: openssl_x509_parse .Essentially, all you need to do is load up the contents of the certificate, either through a file or POST value, and enclose it in the array. Here’s a simple script:

<?php
$sslcert = file_get_contents("/etc/ssl/certs/secure.v-nessa.net");
$sslcert = array(openssl_x509_parse($sslcert,TRUE));
//print_r($sslcert);
foreach ($sslcert as $name => $arrays){
foreach ($arrays as $title => $value){
if(is_array($value)){
echo $value . "\n";
foreach($value as $subtitle => $subvalue){
echo $subtitle . " : " . $subvalue . "\n";
}
}else{
echo $title .  "\n";
}
}
}
?>

The results are several multidimensional arrays, so depending on the data you need, you may need to keep adding foreach loops to get that data.

Using PHP to Extract Image Exif Data

Posted by Nessa | Posted in uncategorized | Posted on 02-08-2010

27

Those of us fluent in digital photography have come across the term “Exif data” numerous times when it comes to software we use to digitally manipulate photographs. Exif (Exchangeable image file format) data is generally used to identify the properties of the camera that snapped a picture, and usually the software that altered it afterwards. It can tell you when a picture was taken, what kind of camera took it, as well as the camera’s model, shutter speed, focal length, and even provide a thumbnail of the image on the camera’s LCD screen.

Why would you need to extract this information?  If you’re ever uploaded images to stock photography sites and wonder how they know so much about your pictures, it’s because they extract the Exif data from your pictures to provide more information on how they were taken. This quick tutorial will demonstrate how to extract Exif data from an image using PHP.

Enabling the Exif Extension

The Exif functions for PHP may not be native to your installation, so you can check by viewing your phpinfo file or running “php -m” via command line to see a list of modules compiled in. If you don’t see Exif listed, there are three ways you can enable it depending on how you installed PHP:

  • If you compiled PHP manually, you can re-compile while adding –enable-exif to the configure line
  • If PHP is installed via package (rpm/deb), it should already have Exif enabled. If not, you can install an RPM for the extension manually
  • If you use cPanel, run EasyApache and select the Exif extension from the PHP module list, and recompile

Determining the Image Type

The exif_imagetype function identifies the format of an image, but returns the result as a code.  The PHP function reference provides a full list of these return codes, but 1-8 are the most common out of the 16:

1: GIF
2: JPEG
3: PNG
4: SWF
5: PSD
6: BMP
7/8: TIFF

Here’s a code example that lists all the desired valid image types in an array and detects the type of image from the specified file, returning the result in “human readable” format:

<?php
$image = "/path/to/myimage";

$types = array(
1 => "GIF",
2 => "JPEG",
3 => "PNG",
4 => "SWF",
5 => "PSD",
6 => "BMP",
7 => "TIFF",
8 => "TIFF"
);

$imagetype = exif_imagetype($image);

if (array_key_exists($imagetype, $types)) {
echo "Image type is: " . $types[$imagetype];
} else {
echo "Not a valid image type";
}
?>

Reading Exif Header Data

The exif_read_data function can be used to extract header data from JPEG and TIFF files:

<?php
$image = "/path/to/myimage";
$exif = exif_read_data($image, 0, true);
foreach ($exif as $key => $section) {
foreach ($section as $name => $val) {
echo "$key.$name: $val\n";
}
}
?>

This will return the elements of the array from exif_read_data, which can be very long depending on what information is available for the image. There are seven sections (arrays) of data types:

  • FILE:  Contains the file’s name, size, timestamp, and what other sections were found (as listed below)
  • COMPUTED: Contains the actual attributes of the image
  • ANY_TAG:  Any information that is tagged
  • IFD0:  Mostly contains information about the camera itself, the software used to edit the image, when it was last modified, etc
  • THUMBNAIL: Information about the embedded thumbnail for the image
  • COMMENT: Comment headers for JPEG images
  • EXIF: Contains more information supplementary to what is in IFD0, mostly related to the camera (includes focal length, zoom ratio, etc)

Depending on the information available for the image, you’ll actually see a lot of data in the output. Say, for instance, you want to only output the IFD0 data to see the information of the camera that took the image:

<?php
$image = "image.jpg";
$exif = exif_read_data($image, 0, true);

foreach ($exif as $key => $section) {
foreach ($section as $name => $val) {
if($key == "IFD0"){
echo "$key.$name: $val\n";
}
}
}
?>

This will output:

IFD0.ImageWidth: 2592
IFD0.ImageLength: 3872
IFD0.BitsPerSample: Array
IFD0.Compression: 1
IFD0.PhotometricInterpretation: 2
IFD0.Make: NIKON CORPORATION
IFD0.Model: NIKON D80
IFD0.Orientation: 1
IFD0.UndefinedTag:0x0000:
IFD0.XResolution: 72/10000
IFD0.YResolution: 72/1
IFD0.PlanarConfiguration: 1
IFD0.ResolutionUnit: 2
IFD0.Software: Adobe Photoshop CS4 Windows
IFD0.DateTime: 2010:02:06 22:24:09
IFD0.Exif_IFD_Pointer: 304

Or, you can further narrow down the output by specifying specific values in the $exif multi-dimensional array:

<?php
$image = "image.jpg";
$exif = exif_read_data($image, 0, true);
echo "Software: " . $exif['IFD0']['Software'] . "\n";
?>

This will return:

Software: Adobe Photoshop CS4 Windows

Using Exif to generate a thumbnail

As touched on previously, many cameras and image manipulation software will include an embedded thumbnail for an image. You can extract this thumbnail using the exif_thumbnail function:

<?php
$image = "image.jpg";
$thumbnail = exif_thumbnail($image, $width, $height, $type);
echo "<img  width='$width' height='$height' src='data:image;base64,".base64_encode($thumbnail)."'>";
?>

Keep in mind that the thumbnail generated here is from the Exif data – there are other ways to create a thumbnails using many of the PHP image functions.

Simple PHP Script for RBL Checking

Posted by Nessa | Posted in uncategorized | Posted on 16-07-2010

16

It’s useful for ISP’s and email service providers to run occasional RBL checks against their IPs to know when they are being blacklisted by populate CBL services. I’ve written a simple script that utilizes the DNSBL pear library to check against common blacklists, when given a list of IPs in a file.

First, you need to download or install the NET_DNSBL pear module. (Command: pear install NET_DNSBL)

<?php
require_once('Net/DNSBL.php');

$iplist = file("/path/to/iplist");

foreach ($iplist as $ip){

$dnsbl = new Net_DNSBL();

$dnsbl->setBlacklists(array(
'sbl-xbl.spamhaus.org',
'dnsbl.sorbs.net',
'bl.spamcop.net',
'dnsbl-1.uceprotect.net',
'dnsbl-2.uceprotect.net',
'dnsbl-3.uceprotect.net',
'isps.spamblocked.com',
'zen.spamhaus.org'
));

if ($dnsbl->isListed($ip)) {

echo "IP $ip is blacklisted!\n";

}

else {
echo "IP $ip not listed\n";
}
}

?>

Of course, this script can be very easily modified to pull IPs from a database, or assign the $ip variable from a GET or POST request (like from a form or API).  Here’s an exclusive list of some RBL’s you can check against using this script, by adding them to the array shown:

asiaspam.spamblocked.com
bl.deadbeef.com
bl.emailbasura.org
bl.spamcop.net
blackholes.five-ten-sg.com
blacklist.woody.ch
bogons.cymru.com
cbl.abuseat.org    cdl.anti-spam.org.cn
combined.abuse.ch
combined.rbl.msrbl.net
db.wpbl.info
dnsbl-1.uceprotect.net
dnsbl-2.uceprotect.net
dnsbl-3.uceprotect.net
dnsbl.abuse.ch
dnsbl.ahbl.org
dnsbl.cyberlogic.net
dnsbl.inps.de
dnsbl.njabl.org
dnsbl.sorbs.net
drone.abuse.ch
duinv.aupads.org
dul.dnsbl.sorbs.net
dul.ru
dyna.spamrats.com
dynip.rothen.com
eurospam.spamblocked.com
fl.chickenboner.biz
http.dnsbl.sorbs.net
images.rbl.msrbl.net
ips.backscatterer.org
isps.spamblocked.com
ix.dnsbl.manitu.net
korea.services.net
lacnic.spamblocked.com
misc.dnsbl.sorbs.net
noptr.spamrats.com
ohps.dnsbl.net.au
omrs.dnsbl.net.au
orvedb.aupads.org
osps.dnsbl.net.au
osrs.dnsbl.net.au
owfs.dnsbl.net.au
owps.dnsbl.net.au
pbl.spamhaus.org
phishing.rbl.msrbl.net
probes.dnsbl.net.au
proxy.bl.gweep.ca
proxy.block.transip.nl
psbl.surriel.com
rbl.interserver.net
rdts.dnsbl.net.au
relays.bl.gweep.ca
relays.bl.kundenserver.de
relays.nether.net
residential.block.transip.nl
ricn.dnsbl.net.au
rmst.dnsbl.net.au
sbl.spamhaus.org
short.rbl.jp
smtp.dnsbl.sorbs.net
socks.dnsbl.sorbs.net
spam.dnsbl.sorbs.net
spam.rbl.msrbl.net
spam.spamrats.com
spamlist.or.kr
spamrbl.imp.ch
t3direct.dnsbl.net.au
tor.ahbl.org
tor.dnsbl.sectoor.de
torserver.tor.dnsbl.sectoor.de
ubl.lashback.com
ubl.unsubscore.com
virbl.bit.nl
virus.rbl.jp
virus.rbl.msrbl.net
web.dnsbl.sorbs.net
wormrbl.imp.ch
xbl.spamhaus.org
zen.spamhaus.org

Using PHP to Perform DNS Lookups

Posted by Nessa | Posted in uncategorized | Posted on 30-06-2010

16

PHP has a couple DNS functions you can use to perform record lookups.

Most of us are familiar with the two basic ones – gethostbyname() and gethostbyaddr(), both of which perform a single function – returning a hostname or IP address. Here’s an example of both:

<?php

$ip = gethostbyname("v-nessa.net");
$host = gethostbyaddr("69.174.114.71");

echo "v-nessa.net has the IP $ip, which reverses to $host";
?>

The above will return:

v-nessa.net has the IP 69.174.114.71, which has a PTR of server.v-nessa.net

Similarly to gethostbyname, there’s gethostbynamel which is useful for hostnames with multiple A records:

$ips = gethostbynamel("test.v-nessa.net");
foreach ($ips as $ip => $value){
echo $value . "\n";
}

Will return:

69.174.114.71
69.174.115.243

A more advanced function is dns_get_record, which can pull any valid record for a hostname or IP.  Think about the dig command you use in Unix to find DNS records:

nessa@nessa-desktop:~$ dig +short v-nessa.net A
69.174.114.71

The dns_get_record function works in a similar way, and can obtain the following DNS record types:

DNS_A, DNS_CNAME, DNS_HINFO, DNS_MX, DNS_NS, DNS_PTR, DNS_SOA, DNS_TXT, DNS_AAAA, DNS_SRV, DNS_NAPTR, DNS_A6, DNS_ALL or DNS_ANY.

The following will give you a similar result:

$recs = dns_get_record("v-nessa.net", DNS_A);
print_r($recs);

Will return:

Array
(
[0] => Array
(
[host] => v-nessa.net
[type] => A
[ip] => 69.174.114.71
[class] => IN
[ttl] => 13728
)
)

If you want to obtain multiple DNS types, you can do so by separating the record types with a plus sign:

$recs = dns_get_record("v-nessa.net", DNS_A + DNS_MX);

Will return:

Array
(
[0] => Array
(
[host] => v-nessa.net
[type] => A
[ip] => 69.174.114.71
[class] => IN
[ttl] => 13736
)

[1] => Array
(
[host] => v-nessa.net
[type] => MX
[pri] => 0
[target] => v-nessa.net
[class] => IN
[ttl] => 14145
)

)

You’ll notice that the output is a double array, so to call individual values you can do either of the following:

// will return the IP for array 0 ( A record)

echo $recs[0]['ip'];

// will return results for common records

foreach ($recs as $type => $value){
echo $value[ip] . "\n";
}

Similar to the example above, you can use the DNS_ALL type to show any records available for the hostname, and use a minus sign to exclude certain record types. For example, the below code will return all DNS results for v-nessa.net, but exclude NS records:

$recs = dns_get_record("v-nessa.net", DNS_ALL - DNS_NS );

foreach ($recs as $type => $value){
echo $value[type] . "\n";
}

The following records were returned:

A
SOA
MX
TXT

Here’s a more practical example – a simple PHP DNS lookup script. Say you have a form on your site that allows a user to type in a hostname and select a type of record: A, MX, NS, and TXT. The passed form values are $host and $type, and the below script will accept the variables and output the results according to the record type:

HTML Form:

<form action="dns.php" method="POST">
Hostname: <input type="text" name="host" />
Type: <select name="type">
<option value="a">A</option>
<option value="mx">MX</option>
<option value="ns">NS</option>
<option value="txt">TXT</option> </select>
</form>

PHP Script (dns.php):

<?php

if(!empty($_POST['host']) && !empty($_POST['type'])){

    // Grab variable from form and define valid types

    $host = $_POST['host'];
    $type = strtoupper($_POST['type');
    $validtypes=array("A","MX","NS","TXT");

    // Check that dns type is defined or allowed

    if(!defined("DNS_" . $type) or !in_array($type,$validtypes)){
       echo "Invalid DNS Type!";
    }else{

       $type = constant("DNS_" . $type);
       $rec = dns_get_record($host, $type);

       // Set result types - can be modified by using available elements from $rec array

       switch($type){
             case DNS_A:
                    $recvals=array("Hostname" => "host","Type" => "type", "IP" => "ip");
                    break;
             case DNS_MX:
                    $recvals=array("Hostname" => "host","Type" => "type", "Target" => "target", "Priority" => "pri");
                    break;
             case DNS_NS:
                    $recvals=array("Hostname" => "host","Type" => "type", "Target" => "target");
                    break;
             case DNS_TXT:
                    $recvals=array("Hostname" => "host","Type" => "type", "Record" => "txt");
                    break;
        }

      // Output results

      foreach ($rec as $arr => $num){
             foreach ($recvals as $title => $value){
                    echo $title . " : " . $num[$value] . "\n";
             }
      }

    }
} else {

     echo "Either hostname or record type is missing";
}

It’s of course easy to modify the above code accordingly, and I’m sure there may be better ways to do this. Feel free to post your own code snippets or comments.