nmaparse.py is a Python script that parses Nmap’s “grepable” output (.gnmap files) and then inserts them into a MySQL database. If a host has multiple ports open, it expands the results into multiple lines — one port per line into the following fields:

  • Host
  • Port
  • OS
  • Seq Index
  • IPID Seq
  • Scan date
  • Scan flags

The script then splits up the port information into the following subfields:

  • Port
  • Protocol
  • State
  • Service
  • Version

Together, the aforementioned fields make up each result. This allows you to load the results up into a database and perform all sorts of analysis, network device monitoring, etc. The script requires the Python MySQLdb module, and has been tested using Python 2.5.2. It uses this database schema (nmapdb.sql), though nothing stops you from using your own.

To use the script, it’s as simple as:

$ ./nmaparse.py results.gnmap
Number of rows inserted: 35 results
$ ./nmaparse.py *.gnmap
Number of rows inserted: 1520 results

You can also download nmaparse.py directly.

#!/usr/bin/env python
from __future__ import with_statement
from time import strftime, strptime
import sys


#   nmaparse.py
#   Copyright (C) 2008  Marcin Wielgoszewski (tssci-security.com)
#   
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

__author__  = 'Marcin Wielgoszewski'
__version__ = '0.6'

DB_HOST   = 'hostname'
DB_UNAME  = 'username'
DB_PASSWD = 'password'
DB_NAME   = 'database'


def delete_labels(line):
    line[0] = line[0].replace('Host: ', '')
    line[1] = line[1].replace('Ports: ', '')
    line[2] = line[2].replace('OS: ', '')
    line[3] = line[3].replace('Seq Index: ', '')
    line[4] = line[4].replace('IPID Seq: ', '')
    line[4] = line[4].replace('\n', '')
    return line


def d1_replace(d):
    d[1] = d[1].replace('///', '/')
    d[1] = d[1].replace('//', '/')
    d[1] = d[1].replace('/,', ',')
    d[1] = d[1].rstrip('/')
    return d[1].split(', ')


def expand_ports(d):
    ports = d1_replace(d)
    ret = []
    for i in range(len(ports)):
        ret.append(list(d[:1]))         # append IP/hostname
    j = 0
    for k in ports:
        k = k.split('/', 4)
        if len(k) == 4:
            k.append('')                # append a missing version field
        ret[j].extend(k + list(d[2:]))  # append ports and rest of results
        j += 1
    return ret


def parse_gnmap(infile):
    infile = infile.readlines()
    date = strftime("%Y-%m-%d %H:%M:%S", strptime(infile[0].strip()[27:51]))
    flags = infile[0].strip()[56:]
    scan = [line.split('\t') for line in infile[1:-1]]
    scan_results = []
    for line in scan:
        if len(line) == 4:
            line.insert(2, '')          # insert field for missing OS
        line = delete_labels(line)
        line.append(date)
        line.append(flags)
        scan_results.extend(expand_ports(line))
    return scan_results


def insert_gnmap(results):
    """For corresponding database schema, see:

    http://www.tssci-security.com/upload/nmaparse_py/nmapdb.sql
    """
    import MySQLdb

    try:
        db = MySQLdb.connect(DB_HOST, DB_UNAME, DB_PASSWD, DB_NAME)
    except MySQLdb.Error, e:
        print """Error %d: %s""" % (e.args[0], e.args[1])
        sys.exit (1)

    c = db.cursor()
    results_rows = 0

    while results:
        small_results, results = results[:100], results[100:]   
        c.executemany("""INSERT INTO results 
                      (host, port, protocol, state, service, version, 
                      os, seqIndex, ipidSeq, scandate, scanflags) 
                      VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", 
                      (small_results))
        results_rows += c.rowcount

    print """Number of rows inserted: %d results""" % results_rows


def main():
    results = []
    for args in sys.argv[1:]:
        with open(args, 'rU') as infile:
            results += parse_gnmap(infile)
    insert_gnmap(results)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print """This program parses *.gnmap files."""
        print """Usage: nmap.py results1.gnmap results2.gnmap"""
        sys.exit(1)
    else:
        main()