From fb38fc9728b23483a9e0d82923cfbab238e4c475 Mon Sep 17 00:00:00 2001 From: Terminal <32599364+TheSecEng@users.noreply.github.com> Date: Mon, 26 Aug 2019 15:19:32 -0400 Subject: [PATCH] Nmap error handling (#21) --- internal/slices/unique_string.go | 16 ++++++++++++++++ nmap.go | 10 ++++++++-- nmap_test.go | 19 ++++++++++++++++++- xml.go | 5 +++-- xml_test.go | 11 +++-------- 5 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 internal/slices/unique_string.go diff --git a/internal/slices/unique_string.go b/internal/slices/unique_string.go new file mode 100644 index 0000000..6b8f400 --- /dev/null +++ b/internal/slices/unique_string.go @@ -0,0 +1,16 @@ +package slices + +// Adapted from +// https://siongui.github.io/2018/04/14/go-remove-duplicates-from-slice-or-array/ +// RemoveDuplicatesFromStringSlice +func RemoveDuplicatesFromStringSlice(s []string) []string { + m := make(map[string]struct{}) + var result []string + for _, item := range s { + if _, ok := m[item]; !ok { + m[item] = struct{}{} + result = append(result, item) + } + } + return result +} diff --git a/nmap.go b/nmap.go index 93cdbd7..838d3a9 100644 --- a/nmap.go +++ b/nmap.go @@ -4,11 +4,12 @@ package nmap import ( "bytes" "context" - "errors" "fmt" "os/exec" "strings" "time" + + . "github.com/Ullaakut/nmap/internal/slices" ) // ScanRunner represents something that can run a scan. @@ -83,8 +84,11 @@ func (s *Scanner) Run() (*Run, error) { return nil, ErrScanTimeout case <-done: // Scan finished before timeout. + var nmapErrors []string if stderr.Len() > 0 { - return nil, errors.New(strings.Trim(stderr.String(), ".\n")) + // List all unique errors returned by nmap. + nmapErrors = strings.Split(strings.Trim(stderr.String(), "\n"), "\n") + nmapErrors = RemoveDuplicatesFromStringSlice(nmapErrors) } result, err := Parse(stdout.Bytes()) @@ -92,6 +96,8 @@ func (s *Scanner) Run() (*Run, error) { return nil, fmt.Errorf("unable to parse nmap output: %v", err) } + result.NmapErrors = nmapErrors + // Call filters if they are set. if s.portFilter != nil { result = choosePorts(result, s.portFilter) diff --git a/nmap_test.go b/nmap_test.go index ab01c2c..18f00a5 100644 --- a/nmap_test.go +++ b/nmap_test.go @@ -10,6 +10,9 @@ import ( "strings" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNmapNotInstalled(t *testing.T) { @@ -44,6 +47,7 @@ func TestRun(t *testing.T) { expectedResult *Run expectedErr error + expectedNmapErr string }{ { description: "invalid binary path", @@ -99,7 +103,11 @@ func TestRun(t *testing.T) { WithTimingTemplate(TimingFastest), }, - expectedErr: errors.New("WARNING: No targets were specified, so 0 hosts scanned"), + expectedNmapErr: "WARNING: No targets were specified, so 0 hosts scanned.", + expectedResult: &Run{ + Scanner: "nmap", + Args: "/usr/local/bin/nmap -T5 -oX -", + }, }, { description: "scan localhost with filters", @@ -174,12 +182,21 @@ func TestRun(t *testing.T) { } result, err := s.Run() + if err != test.expectedErr { + require.NotNil(t, err) + if err.Error() != test.expectedErr.Error() { t.Errorf("expected error %q got %q", test.expectedErr, err) } } + if test.expectedNmapErr != "" { + require.NotNil(t, result) + + assert.Contains(t, result.NmapErrors, test.expectedNmapErr) + } + if result == nil && test.expectedResult == nil { return } else if result == nil && test.expectedResult != nil { diff --git a/xml.go b/xml.go index a0c303e..5cd5c2b 100644 --- a/xml.go +++ b/xml.go @@ -32,7 +32,8 @@ type Run struct { TaskProgress []TaskProgress `xml:"taskprogress" json:"task_progress"` TaskEnd []Task `xml:"taskend" json:"task_end"` - rawXML []byte + NmapErrors []string + rawXML []byte } // ToFile writes a Run as XML into the specified file path. @@ -420,7 +421,7 @@ func (t *Timestamp) UnmarshalXMLAttr(attr xml.Attr) (err error) { // Run struct. func Parse(content []byte) (*Run, error) { r := &Run{ - rawXML: content, + rawXML: content, } err := xml.Unmarshal(content, r) diff --git a/xml_test.go b/xml_test.go index 10bd083..2aa3d1d 100644 --- a/xml_test.go +++ b/xml_test.go @@ -1,7 +1,6 @@ package nmap import ( - "bufio" "bytes" "encoding/json" "encoding/xml" @@ -173,12 +172,6 @@ func TestParseTableXML(t *testing.T) { } } -type mockWriter struct { - wr *bufio.Writer - writeErr error - flushErr error -} - func TestFormatTableXML(t *testing.T) { table := Table{ Key: "key123", @@ -1069,7 +1062,9 @@ func TestParseRunXML(t *testing.T) { result, err := Parse(rawXML) // Remove rawXML before comparing - result.rawXML = []byte{} + if result != nil { + result.rawXML = []byte{} + } compareResults(t, test.expectedResult, result)