/*
Copyright (c) 2017, AverageSecurityGuy
# All rights reserved.

Created: 2018-11-08
Modified: 2018-11-08
Author: Stephen Haywood
Sources: http://blog.davidsingleton.org/parsing-huge-xml-files-with-go/
*/

package main

import (
	"encoding/xml"
	"fmt"
	"os"
	"strings"
)

type Tag struct {
	Name string `xml:"name,attr"`
	Value string `xml:",chardata"`
}

type Host struct {
	ip string
	fqdn string
	os string
	Properties []Tag `xml:"tag"`
}

func (h *Host) Parse() {
	for _, hp := range h.Properties {
		switch hp.Name {
		case "host-ip":
			h.ip = hp.Value
		case "operating-system":
			h.os = hp.Value
		case "os":
			if h.os == "" {
				h.os = hp.Value
			}
		case "host-fqdn":
			h.fqdn = hp.Value
		default:
			continue
		}
	}
}

type HostItem struct {
	host Host
	Port string `xml:"port,attr"`
	Protocol string `xml:"protocol,attr"`
	PluginId string `xml:"pluginID,attr"`
	Metasploit bool `xml:"exploit_framework_metasploit"`
	Module string `xml:"metasploit_name"`
}

func (h *HostItem) String() string {
	return fmt.Sprintf("%s\t%s\t%s\t%s", h.host.ip, h.Port, h.host.fqdn, h.host.os)
}

func check(e error) {
	if e != nil {
		fmt.Printf("Error: %s\n", e.Error())
		os.Exit(0)
	}
}

func parseFile(fn string, hic chan<- *HostItem) {
	reader, err := os.Open(fn)
	check(err)

	decoder := xml.NewDecoder(reader)

	var host Host

	for {
	    tkn, err := decoder.Token()
		if tkn == nil {
	        break
	    }

		if err != nil {
			fmt.Printf("Parse Error: %s", err)
			continue
		}

	    switch ele := tkn.(type) {
	    case xml.StartElement:
			switch ele.Name.Local {
			case "HostProperties":
	            decoder.DecodeElement(&host, &ele)
				host.Parse()
	        case "ReportItem":
				var item HostItem

				item.host = host
				decoder.DecodeElement(&item, &ele)
				hic <- &item
			default:
				continue
			}
		default:
			continue
		}
	}

	close(hic)
}

func processExploitable(hi *HostItem, exp map[string][]string) {
	if hi.Metasploit {
		_, ok := exp[hi.Module]
		if !ok {
			exp[hi.Module] = []string{hi.String()}
		} else {
			exp[hi.Module] = append(exp[hi.Module], hi.String())
		}
	}
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("Usage: go run exploitable.go nessus_file")
		os.Exit(1)
	}

	exploitable := make(map[string] []string)
	hostItemChan := make(chan *HostItem, 100)

	go parseFile(os.Args[1], hostItemChan)

	for hi := range hostItemChan {
		processExploitable(hi, exploitable)
	}

	for e := range exploitable {
		fmt.Println(e)
		fmt.Println(strings.Join(exploitable[e], "\n"))
		fmt.Println("")
	}
}
