#!/usr/bin/python3
import json
import sys
from enum import Enum

DEBUG = False
full_output = False
hosts = []

# Describe the port fields
class Port:
    port_number = 0
    state = ""
    protocol = ""
    owner = ""
    service = ""
    sunRPCinfo = ""
    version = ""

    def __str__(self):
        return f"Port {self.port_number} - Version: {self.version}"

    def todict(self, full=False):
        if full:
            return {
                    "port":self.port_number,
                    "state":self.state,
                    "protocol":self.protocol,
                    "owner":self.owner,
                    "service":self.service,
                    "RPCInfo":self.sunRPCinfo,
                    "version":self.version,
                    }
        return {
                "port":self.port_number,
                "protocol":self.protocol,
                "service":self.service,
                "version":self.version,
                }


# Class to represent all the host attributes
class Host:
    ip = ""
    status = ""
    hostname = ""
    ports = [] # Array of Port() items

    def todict(self, full):
        return {
                "ip":self.ip,
                "hostname":self.hostname,
                "ports":[port.todict(full) for port in self.ports],
                "status":self.status
            }


def open_gnmap(filename):
    with open(filename, "r") as gnmap:
        # split into lines and only take the ones that begin with "Host:"
        lines = gnmap.read().strip().split("\n")
        lines = [l for l in lines if not l.startswith("#") and l.startswith("Host:")]

    return lines


# parse every single line
def parse_line(line):
    global hosts

    # split line into different fields
    fields = line.split("\t")

    # parse the different fields
    out_host = parse_fields(fields)
    merge_hosts(out_host)


def parse_host_field(field):

    host = Host()
    subfields = field.split(" (")
    host.ip = subfields[0]
    host.hostname =subfields[1].replace(")", "")
    if DEBUG: print(f"DEBUG: parse_host_field::ip={host.ip};hostname={host.hostname}")
    return host

def parse_ports_field(host, field):
    portlist = field.split("/,")

    # loop through the ports and append them to the host
    for p in portlist:
        port = Port()
        portparts = p.split("/")
        port.port_number = portparts[0]
        port.state = portparts[1]
        port.protocol = portparts[2]
        port.owner = portparts[3]
        port.service = portparts[4]
        port.sunRPCinfo = portparts[5]
        port.version = portparts[6]
        host.ports.append(port)
    return host

def parse_status_field(host, field):
    host.status = field
    return host


# Jumptable for the different fields
field_parser = {
        "Host": parse_host_field,
        "Ports": parse_ports_field,
        "Status": parse_status_field,
}


# if no parser for that type of field is implemented
def not_implemented(host, field_type):
    return host


def merge_hosts(host):
    global hosts

    for ih in hosts:
        if ih.ip == host.ip:
            ih.ports = host.ports
            break
    else:
        hosts.append(host)

# parsed the different fields, each separeted by a space
def parse_fields(fields):

    # create a Host instance
    host = Host()

    # loop each field that is present
    for field in fields:
        field_type, field_value = field.split(": ")

        if DEBUG: print(f"DEBUG: field_type={field_type}")
        if DEBUG: print(f"DEBUG: field_content={field_value}")
        # Host Field is the most important one
        if field_type == "Host":
            host = parse_host_field(field_value)
        else:
            host = field_parser.get(field_type, not_implemented)(host, field_value)

    return host


def hosts_to_json(full_output):
    global hosts
    out = []
    for host in hosts:
        out.append(host.todict(full_output))
    return out


# check usage
if len(sys.argv) == 1:
    print(f"usage: {sys.argv[0]} <scan.gnmap>")
    exit(1)
elif len(sys.argv) == 3 and sys.argv[2] == "--full":
    full_output = True

# Open the gnmap file
lines = open_gnmap(sys.argv[1])

# parse file
for line in lines:
    parse_line(line)

# dump as json
print(json.dumps(hosts_to_json(full_output)))