PIX Access List Configuration Parser

2015 Update: Are you still running pix? This is from 2005 so probably not applicable. Process – sure. Script? Well let me know if you come up with a place for it in the world of ASA.

A large part of my work has been in making new systems interoperate cleanly and providing migration services when systems become outdated. I wrote this script to take the configuration from a PIX firewall (using access lists) and turn it into a comma-separated file. My methodology is often to turn the configuration into a standard format, make any necessary modifications and then automatically built the new configuration from that abstract of sorts.

This script is also useful for reviewing excessive configurations. When it breaks out the access list into a CSV, it also includes a field marking the full text of the original access list. This can be easily pasted into the system with a ‘no’ to negate the access-list in question.

This script will read text files and gzip compressed files. Just paste the configuration into a file, and execute the script with the filename as the first argument. Note that your installation of PHP should be compiled with zlib (for gz support) and mime-magic for file identification.

As always, I give no warranties. You break it, you fix it.

#!/usr/local/bin/php
<?

// Copyright (C) 2005  James Bly
//
// 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 2
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


// This script will turn a PIX access-list configuration into a comma-separated
// file. The filename to convert should be specified on the command line and the
// result will go to stdout.
//
// Version 1.0
// sir @
// mangeek.com

include 'is_key.inc';

$data = '';
$content = '';
$file = '';
// The ignored array is a list of PIX statements that should be ignored by the parsing engine.
// Any of them could of course be parsed but I've only concerned myself with access-list, access-group
// and object-group commands. Note that you do NOT want to put "description" in this list for reasons
// I document below.
$ignored = array(":", "PIX", "interface", "nameif", "enable", "passwd", "hostname", "domain-name", "fixup",
                 "name", "names", "no", "logging", "mtu", "ip", "failover", "pdm", "arp", "global", "nat", "static",
         "route", "timeout", "aaa-server", "http", "snmp-server", "floodguard", "telnet", "ssh", 
         "console", "terminal", "aaa");
$objg = array();
$cobjg = ''; $cobjgt = ''; $cobjgs = '';

// bitmask() will of course return a bitmask for any specified netmask.
// If you set this function to simply "return $mask" the script will
// document all of the ACLs with an ipv4 style netmask. Alternatively
// this could be modified to convert a netmask to a hostmask (say if this
// config was going to go into a Cisco router...I shudder to think)
function bitmask($mask)
{
    $masks = array(
        "0.0.0.0" => 0,
        "128.0.0.0" => 1,
        "192.0.0.0" => 2,
        "224.0.0.0" => 3,
        "240.0.0.0" => 4,
        "248.0.0.0" => 5,
        "252.0.0.0" => 6,
        "254.0.0.0" => 7,
        "255.0.0.0" => 8,
        "255.128.0.0" => 9,
        "255.192.0.0" => 10,
        "255.224.0.0" => 11,
        "255.240.0.0" => 12,
        "255.248.0.0" => 13,
        "255.252.0.0" => 14,
        "255.254.0.0" => 15,
        "255.255.0.0" => 16,
        "255.255.128.0" => 17,
        "255.255.192.0" => 18,
        "255.255.224.0" => 19,
        "255.255.240.0" => 20,
        "255.255.248.0" => 21,
        "255.255.252.0" => 22,
        "255.255.254.0" => 23,
        "255.255.255.0" => 24,
        "255.255.255.128" => 25,
        "255.255.255.192" => 26,
        "255.255.255.224" => 27,
        "255.255.255.240" => 28,
        "255.255.255.248" => 29,
        "255.255.255.252" => 30,
        "255.255.255.254" => 31,
        "255.255.255.255" => 32
        );
    if(!preg_match("/^\d+\.\d+\.\d+\.\d+$/", $mask))
        return -1;
    return $masks[$mask];
}


// Here we check our command line and massage our file into the
// $data variable.
if(count($argv) > 1)
{
    $file = $argv[1];
    if(!file_exists($file))
    {
        print "Error! File \"${file}\" not found!\n";
        exit;
    }

    $content = mime_content_type($file);
    switch($content)
    {
        case 'application/x-gzip':
            $fp = gzopen($file, "r");
            while(!gzeof($fp)) $data .= gzgets($fp);
            gzclose($fp);
            break;
        case 'text/plain':
            $data = file_get_contents($file);
            break;
        default:
            print "Error! Unknown file type \"${content}\"\n";
            exit;
            break;
    }
    
} else {
    print "Error! No file specified.\n";
    exit;
}

// Now we parse out $data and work over what's provided.
foreach(explode("\n", $data) as $line)
{
    $line = rtrim($line);
    $first = 1;

    // This conditional statement is for error handling and also
    // data sanity.
    if(preg_match("/^\s*([\w-:]+)\s*.*$/", $line, $matches))
    {
        // This handles the ignoring of lines we don't want to bother with
        // along with dropping any checksum statements that don't look like
        // anything else in the log. Note that if an ignored object is reached,
        // it assumes that the current "object-group" has been closed. 
        if(in_array($matches[1], $ignored) || preg_match("/Cryptochecksum/", $line)) 
        {
            $cobjg = ''; $cobjgt = ''; $cobjgs = '';
            continue;
        }

        // object-group statements are normally before access-list statements
        // in the config. If something happens where they aren't, an access-list
        // might not parse out properly. (It won't have the object-group to document
        // yet. Ideally this would grab the object-groups first and then pull in
        // the rest, but this is mostly unnecessary. Even when there be dragons,
        // they don't always bite, and even if they did, anyone doing this should
        // know how not to get bit. Like doing weird things. That'll do it.
        if($matches[1] == 'object-group') {

            preg_match("/object-group (.*)/", $line, $matches);
            $list = explode(" ", $matches[1]);
            $cobjgt = array_shift($list);
            $cobjg = array_shift($list);
            if($cobjgt == 'service')
                $cobjgs = array_shift($list);
            #printf("objects: %s %s %s\n", $cobjg, $cobjgt, $cobjgs);

        // I don't bother handling description lines but I don't want to ignore
        // them. If they were ignored, then the object group would be closed out.
        } elseif($matches[1] == 'description') {

            continue;

        } elseif($matches[1] == 'port-object') {

            preg_match("/port-object (.*)/", $line, $matches);
            if(!is_key($cobjg, $objg)) $objg[$cobjg] = array();
            array_push($objg[$cobjg], $matches[1]);
            
        } elseif($matches[1] == 'network-object') {

            preg_match("/network-object (.*)/", $line, $matches);
            if(!is_key($cobjg, $objg)) $objg[$cobjg] = array();
            array_push($objg[$cobjg], $matches[1]);

        // The big daddy.
        } elseif($matches[1] == 'access-list') {

            $cobjg = ''; $cobjgt = ''; $cobjgs = '';

            if(preg_match("/access-list (.*?) (.*?) (.*?) (.*)/", $line, $matches))
            {
                $src = array();
                $dst = array();
                $svc = array();
                $acl = $matches[1];
                $action = $matches[2];
                $protocol = $matches[3];
                $aclar = explode(" ", $matches[4]);

                $x = array_shift($aclar);
                if(preg_match("/^\d+\.\d+\.\d+\.\d+$/", $x))
                {
                    $mask = bitmask(array_shift($aclar));
                    $src = array("$x/$mask");
                } elseif($x == 'host') {
                    $src = array(sprintf("%s/32", array_shift($aclar)));
                } elseif($x == 'object-group') {
                    $grp = array_shift($aclar);
                    $src = $objg[$grp];
                } elseif($x == 'any') {
                    $src = array("any");
                } else {
                    print "Error! Unhandled ACL item: $x\n";
                    continue;
                }

                $x = array_shift($aclar);
                if(preg_match("/^\d+\.\d+\.\d+\.\d+$/", $x))
                {
                    $mask = bitmask(array_shift($aclar));
                    $dst = array("$x/$mask");
                } elseif($x == 'host') {
                    $dst = array(sprintf("%s/32", array_shift($aclar)));
                } elseif($x == 'object-group') {
                    $grp = array_shift($aclar);
                    $dst = $objg[$grp];
                } elseif($x == 'any') {
                    $dst = array("any");
                } else {
                    print "Error! Unhandled ACL item: $x\n";
                    continue;
                }

                if(count($aclar))
                {
                    $x = array_shift($aclar);
                    if($x == "echo") {
                        $svc = array("ping");
                    } elseif($x == "echo-reply") {
                        $svc = array("ping-reply");
                    } elseif($x == "range") {
                        $s = array_shift($aclar);
                        $e = array_shift($aclar);
                        $svc = array(sprintf("%s-%s", $s, $e));
                    } elseif ($x == "eq") {
                        $svc = array(array_shift($aclar));
                    } elseif($x == 'object-group') {
                        $grp = array_shift($aclar);
                        $svc = $objg[$grp];
                    } else {
                        print "Error! Unhandled ACL item: $x\n";
                        continue;
                    }
                } else {
                    $svc = "any";
                }


                // This is where we actually output the current line's results. The numerous foreach
                // statements are of course because we break out object-groups and print them individually.
                foreach($src as $s)
                    foreach($dst as $d)
                        foreach($svc as $v)
                        {
                            if(preg_match("/^host (\d+\.\d+\.\d+\.\d+)$/", $s, $matches))
                                $s = sprintf("%s/32", $matches[1]);
                            elseif(preg_match("/^(\d+\.\d+\.\d+\.\d+) (\d+\.\d+\.\d+\.\d+)$/", $s, $matches))
                                $s = sprintf("%s/%s", $matches[1], bitmask($matches[2]));

                            if(preg_match("/^host (\d+\.\d+\.\d+\.\d+)$/", $d, $matches))
                                $d = sprintf("%s/32", $matches[1]);
                            elseif(preg_match("/^(\d+\.\d+\.\d+\.\d+) (\d+\.\d+\.\d+\.\d+)$/", $d, $matches))
                                $d = sprintf("%s/%s", $matches[1], bitmask($matches[2]));

                            if(preg_match("/^eq (.+)/", $v, $matches))
                                $v = $matches[1];
                            elseif(preg_match("/^range (.+) (.+)/", $v, $matches))
                                $v = sprintf("%s-%s", $matches[1], $matches[2]);
                                

                            printf("%s,%s,%s,%s,%s/%s,%s\n", $acl, $action, $s, $d, $protocol, $v, ($first)?$line:'Object-Group');
                            $first = 0;
                        }
                print "\n";
            } else {
                print "Error! Unhandled access list: ${line}\n";
                exit;
            }

        } elseif($matches[1] == 'access-group') {

            continue;

        } else {

            print "Error! Unhandled line: $line\n";
        }

    } else {
        if(strlen($line)) print "Error! Uninterpreted line: $line\n";
    }

}

?>

 

Leave a Reply

Your email address will not be published. Required fields are marked *