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

DEBUG = False
full_output = False
hosts = []

# Describe the port fields
class Port:

    def __init__(self):
        self.port_number = 0
        self.state = ""
        self.protocol = ""
        self.owner = ""
        self.service = ""
        self.sunRPCinfo = ""
        self.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,
                "state":self.state,
                }


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

    def merge_ports(self, host):

        local_ports = [p.port_number for p in self.ports]
        for p in host.ports:
            if p.port_number not in local_ports:
                self.ports.append(p)



    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

    # Create new Instance
    host = Host()

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

    # parse the different fields
    parse_fields(host, fields)

    # merge known hosts
    merge_hosts(host)
    # delete instance
    del host


def parse_host_field(host, field):
    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}")

def parse_ports_field(host, field):
    portlist = [s.strip() for s in 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)
        del port

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


# Jumptable for the different fields
field_parser = {
        "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:
        # hosts already present in list
        if ih.ip == host.ip:
            ih.merge_ports(host)
            break
    else:
        hosts.append(host)

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

    # create a Host instance
    # loop each field that is present
    for field in fields:
        field_parts = field.split(": ")
        field_type = field_parts[0]
        field_value = "".join(field_parts[1:])

        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":
            parse_host_field(host, field_value)
        else:
            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)))