|
@@ -10,6 +10,7 @@ import (
|
|
|
"net/http"
|
|
|
"path"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
)
|
|
|
|
|
|
type ScanRequest struct {
|
|
@@ -27,7 +28,7 @@ Run a new webserver
|
|
|
|
|
|
:param port: port number to run the webserver on
|
|
|
*/
|
|
|
-func RunHttpServer(port int, dbhook TopologyDatabaseIO, portmap []int) {
|
|
|
+func RunHttpServer(port int, dbhook TopologyDatabaseIO, portmap []int, logStream io.Writer) {
|
|
|
assets := &AssetHandler{Root: content, RelPath: "static", EmbedRoot: "html"}
|
|
|
tmpl, err := template.ParseFS(content, "html/templates/*.html")
|
|
|
if err != nil {
|
|
@@ -38,7 +39,7 @@ func RunHttpServer(port int, dbhook TopologyDatabaseIO, portmap []int) {
|
|
|
log.Fatal(err)
|
|
|
}
|
|
|
htmlHndl := &HtmlHandler{Home: tmpl, TableEntry: iptable, DbHook: dbhook}
|
|
|
- execHndl := &ExecutionHandler{DbHook: dbhook, PortMap: portmap, TableEntry: iptable}
|
|
|
+ execHndl := &ExecutionHandler{DbHook: dbhook, PortMap: portmap, TableEntry: iptable, stream: logStream}
|
|
|
http.Handle("/static/", assets)
|
|
|
http.Handle("/home", htmlHndl)
|
|
|
http.Handle("/subnets", htmlHndl)
|
|
@@ -53,57 +54,114 @@ type ExecutionHandler struct {
|
|
|
DbHook TopologyDatabaseIO
|
|
|
TableEntry *template.Template
|
|
|
PortMap []int
|
|
|
+ stream io.Writer
|
|
|
}
|
|
|
|
|
|
+func (e *ExecutionHandler) Log(vals ...string) {
|
|
|
+ e.stream.Write([]byte("KYOKETSU-WEB LOG ||| " + strings.Join(vals, " ||| ")))
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+Top level function to be routed to, this will spawn a suite of goroutines that will perform a concurrent scan on hosts and write back HTML data
|
|
|
+
|
|
|
+ :param w: an http.ResponseWriter that we will write data back to
|
|
|
+ :param r: a pointer to the request coming in from the client
|
|
|
+*/
|
|
|
func (e *ExecutionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
- var input ScanRequest
|
|
|
- b, err := io.ReadAll(r.Body)
|
|
|
- defer r.Body.Close()
|
|
|
+ input, err := e.parseRequest(r)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error processing your request: %s", err)
|
|
|
+ http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
return
|
|
|
}
|
|
|
- err = json.Unmarshal(b, &input)
|
|
|
+
|
|
|
+ subnetMap, err := GetNetworkAddresses(input.IpAddress)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error processing your request: %s", err)
|
|
|
+ http.Error(w, "Failed to get network addresses", http.StatusInternalServerError)
|
|
|
return
|
|
|
+ }
|
|
|
+
|
|
|
+ scanned := make(chan Host)
|
|
|
+ var wg sync.WaitGroup
|
|
|
+ var mu sync.Mutex
|
|
|
+ var errorRaised bool
|
|
|
|
|
|
+ wg.Add(1)
|
|
|
+ go e.processScannedData(w, e.TableEntry, scanned, &wg, &mu, &errorRaised)
|
|
|
+
|
|
|
+ NetSweep(subnetMap.Ipv4s, subnetMap.Mask, RetrieveScanDirectives(), scanned)
|
|
|
+ close(scanned)
|
|
|
+ wg.Wait()
|
|
|
+
|
|
|
+ if errorRaised {
|
|
|
+ http.Error(w, "Error during scan processing. Check logs for details.", http.StatusInternalServerError)
|
|
|
}
|
|
|
- subnetMap, err := GetNetworkAddresses(input.IpAddress)
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+Parse the request sent in from the client
|
|
|
+
|
|
|
+ :param r: pointer to the http.Request coming in from the client
|
|
|
+*/
|
|
|
+func (e *ExecutionHandler) parseRequest(r *http.Request) (ScanRequest, error) {
|
|
|
+ var input ScanRequest
|
|
|
+ b, err := io.ReadAll(r.Body)
|
|
|
+ defer r.Body.Close()
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error processing your request: %s", err)
|
|
|
+ return input, fmt.Errorf("error reading request body: %w", err)
|
|
|
}
|
|
|
- scanned := make(chan Host)
|
|
|
- go func(wr http.ResponseWriter, templ *template.Template) {
|
|
|
- for x := range scanned {
|
|
|
- if len(x.ListeningPorts) > 0 {
|
|
|
+ err = json.Unmarshal(b, &input)
|
|
|
+ if err != nil {
|
|
|
+ return input, fmt.Errorf("error unmarshalling request body: %w", err)
|
|
|
+ }
|
|
|
+ return input, nil
|
|
|
+}
|
|
|
|
|
|
- err := templ.Execute(wr, x)
|
|
|
- if err != nil {
|
|
|
- fmt.Println(err, x)
|
|
|
+/*
|
|
|
+Process the data that is created from the kyoketsu Web Scanner, and parse the data into an HTML template
|
|
|
|
|
|
- }
|
|
|
+ :param w: an http.ResponseWriter to write the template back into
|
|
|
+ :param templ: a pointer to the html template that will house the data
|
|
|
+ :param scanned: a channel with 'Host' structs coming through
|
|
|
+ :param wg: a pointer to a waitgroup that will get decremented when the function exits
|
|
|
+ :param mu: a pointer to a mutex that will control when the errorRaised singleton is modified
|
|
|
+ :param errorRaised: a pointer to a boolean that will signify if an error raised whilst processing data
|
|
|
+*/
|
|
|
+func (e *ExecutionHandler) processScannedData(w http.ResponseWriter, templ *template.Template, scanned chan Host, wg *sync.WaitGroup, mu *sync.Mutex, errorRaised *bool) {
|
|
|
+ defer wg.Done()
|
|
|
+ for x := range scanned {
|
|
|
+ if len(x.ListeningPorts) > 0 {
|
|
|
+ if err := templ.Execute(w, x); err != nil {
|
|
|
+ mu.Lock()
|
|
|
+ *errorRaised = true
|
|
|
+ mu.Unlock()
|
|
|
+ e.Log(err.Error())
|
|
|
+ }
|
|
|
|
|
|
- host, err := e.DbHook.GetByIP(x.IpAddress)
|
|
|
- if err != nil {
|
|
|
- if err != ErrNotExists {
|
|
|
- log.Fatal(err, " Couldnt access the database. Fatal error.\n")
|
|
|
- }
|
|
|
- _, err = e.DbHook.Create(x)
|
|
|
- if err != nil {
|
|
|
- log.Fatal(err, " Fatal error trying to read the database.\n")
|
|
|
- }
|
|
|
- continue
|
|
|
+ host, err := e.DbHook.GetByIP(x.IpAddress)
|
|
|
+ if err != nil {
|
|
|
+ if err != ErrNotExists {
|
|
|
+ mu.Lock()
|
|
|
+ *errorRaised = true
|
|
|
+ mu.Unlock()
|
|
|
+ e.Log(err.Error())
|
|
|
}
|
|
|
- _, err = e.DbHook.Update(host.Id, x)
|
|
|
- if err != nil {
|
|
|
- log.Fatal(err, " fatal error when updating a record.\n")
|
|
|
+ if _, err := e.DbHook.Create(x); err != nil {
|
|
|
+ mu.Lock()
|
|
|
+ *errorRaised = true
|
|
|
+ mu.Unlock()
|
|
|
+ e.Log(err.Error())
|
|
|
}
|
|
|
-
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if _, err := e.DbHook.Update(host.Id, x); err != nil {
|
|
|
+ mu.Lock()
|
|
|
+ *errorRaised = true
|
|
|
+ mu.Unlock()
|
|
|
+ e.Log(err.Error())
|
|
|
}
|
|
|
}
|
|
|
- }(w, e.TableEntry)
|
|
|
- NetSweep(subnetMap.Ipv4s, subnetMap.Mask, RetrieveScanDirectives(), scanned)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// handlers //
|
|
@@ -114,22 +172,34 @@ type HtmlHandler struct {
|
|
|
DbHook TopologyDatabaseIO
|
|
|
}
|
|
|
|
|
|
+func (h *HtmlHandler) handleHome(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if r.RequestURI == "/home" {
|
|
|
+ data, err := h.DbHook.All()
|
|
|
+ if err != nil {
|
|
|
+ http.Error(w, "There was an error reading from the database: "+err.Error(), http.StatusInternalServerError)
|
|
|
+ }
|
|
|
+ h.Home.Execute(w, data)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
func (h *HtmlHandler) subnetQueryHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
var req ScanRequest
|
|
|
b, err := io.ReadAll(r.Body)
|
|
|
defer r.Body.Close()
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error reading the input: %s", err)
|
|
|
+ http.Error(w, "There was an error reading the request: "+err.Error(), http.StatusBadRequest)
|
|
|
return
|
|
|
}
|
|
|
err = json.Unmarshal(b, &req)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error reading the input: %s", err)
|
|
|
+ http.Error(w, "There was an error reading the request: "+err.Error(), http.StatusBadRequest)
|
|
|
return
|
|
|
}
|
|
|
data, err := h.DbHook.GetByNetwork(req.IpAddress)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error reading from the database: %s", err)
|
|
|
+ http.Error(w, "There was an error reading the request: "+err.Error(), http.StatusBadRequest)
|
|
|
return
|
|
|
}
|
|
|
for _, host := range data {
|
|
@@ -143,18 +213,18 @@ func (h *HtmlHandler) fqdnQueryHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
b, err := io.ReadAll(r.Body)
|
|
|
defer r.Body.Close()
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error reading the input: %s", err)
|
|
|
+ http.Error(w, "There was an error reading the request: "+err.Error(), http.StatusBadRequest)
|
|
|
return
|
|
|
}
|
|
|
err = json.Unmarshal(b, &req)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error reading the input: %s", err)
|
|
|
+ http.Error(w, "There was an error reading the request: "+err.Error(), http.StatusBadRequest)
|
|
|
return
|
|
|
}
|
|
|
dnsList := strings.Split(req.FqdnPattern, ",")
|
|
|
data, err := h.DbHook.FilterDnsPattern(dnsList)
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "There was an error reading from the database: %s", err)
|
|
|
+ http.Error(w, "There was an error reading the request: "+err.Error(), http.StatusBadRequest)
|
|
|
return
|
|
|
}
|
|
|
for _, host := range data {
|
|
@@ -170,20 +240,9 @@ Handler function for HTML serving
|
|
|
:param r: pointer to the http.Request coming in
|
|
|
*/
|
|
|
func (h *HtmlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
- if r.RequestURI == "/home" {
|
|
|
- data, err := h.DbHook.All()
|
|
|
- if err != nil {
|
|
|
- fmt.Fprintf(w, "You have made it to the kyoketsu web server!\nThere was an error getting the db table, though.\n%s", err)
|
|
|
- }
|
|
|
- h.Home.Execute(w, data)
|
|
|
- return
|
|
|
- }
|
|
|
- splitpath := strings.Split(r.RequestURI, "/")
|
|
|
- if len(splitpath) <= 1 {
|
|
|
- w.Header().Add("Location", "/home")
|
|
|
- return
|
|
|
- }
|
|
|
switch r.RequestURI {
|
|
|
+ case "/home":
|
|
|
+ h.handleHome(w, r)
|
|
|
case "/subnets":
|
|
|
h.subnetQueryHandler(w, r)
|
|
|
case "/excludefqdn":
|
|
@@ -222,7 +281,7 @@ func (a *AssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
}
|
|
|
b, err = a.Root.ReadFile(path.Join(assetPath...))
|
|
|
if err != nil {
|
|
|
- fmt.Fprintf(w, "Error occured: %s. path split: '%s'\nAsset Path: %s", err, pathSp, assetPath)
|
|
|
+ http.Error(w, "Error occured when getting Asset: "+err.Error(), http.StatusBadRequest)
|
|
|
}
|
|
|
switch {
|
|
|
case strings.Contains(fname, "css"):
|