Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix run with progress and add new Run function #69

Merged
merged 8 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions examples/basic_scan_streamer_interface/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"fmt"
"io/ioutil"
"log"
"strings"

"github.com/Ullaakut/nmap/v2"
)

// CustomType is your custom type in code.
// You just have to make it a Streamer.
type CustomType struct {
nmap.Streamer
File string
}

// Write is a function that handles the normal nmap stdout
elivlo marked this conversation as resolved.
Show resolved Hide resolved
func (c *CustomType) Write(d []byte) (int, error) {
var err error
elivlo marked this conversation as resolved.
Show resolved Hide resolved
lines := string(d)

if strings.Contains(lines, "Stats: ") {
fmt.Print(lines)
}
return len(d), err
}

// Bytes returns scan result bytes
elivlo marked this conversation as resolved.
Show resolved Hide resolved
func (c *CustomType) Bytes() []byte {
data, err := ioutil.ReadFile(c.File)
if err != nil {
data = append(data, "\ncould not read File"...)
}
return data
}

func main() {
cType := &CustomType{
File: "/tmp/output.xml",
}
scanner, err := nmap.NewScanner(
nmap.WithTargets("localhost"),
nmap.WithPorts("1-4000"),
nmap.WithServiceInfo(),
nmap.WithVerbosity(3),
)
if err != nil {
log.Fatalf("unable to create nmap scanner: %v", err)
}

warnings, err := scanner.RunWithStreamer(cType, cType.File)
if err != nil {
log.Fatalf("unable to run nmap scan: %v", err)
}

fmt.Printf("Nmap warnings: %v\n", warnings)

result, err := nmap.Parse(cType.Bytes())
if err != nil {
log.Fatalf("unable to parse nmap output: %v", err)
}

fmt.Printf("Nmap done: %d hosts up scanned in %.2f seconds\n", len(result.Hosts), result.Stats.Finished.Elapsed)
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ module github.com/Ullaakut/nmap/v2

go 1.15

require github.com/stretchr/testify v1.6.1
require (
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
Expand Down
78 changes: 77 additions & 1 deletion nmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"context"
"encoding/xml"
"fmt"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"io"
"os/exec"
"strings"
"time"
elivlo marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -17,6 +20,12 @@ type ScanRunner interface {
Run() (result *Run, warnings []string, err error)
}

// Streamer will be used to constantly stream the stdout
elivlo marked this conversation as resolved.
Show resolved Hide resolved
type Streamer interface {
Write(d []byte) (int, error)
Bytes() []byte
}

// Scanner represents an Nmap scanner.
type Scanner struct {
cmd *exec.Cmd
Expand Down Expand Up @@ -210,14 +219,15 @@ func (s *Scanner) RunWithProgress(liveProgress chan<- float32) (result *Run, war
// Wait for nmap process or timeout
select {
case <-s.ctx.Done():
// Trigger progress function exit
elivlo marked this conversation as resolved.
Show resolved Hide resolved
close(doneProgress)

// Context was done before the scan was finished.
// The process is killed and a timeout error is returned.
_ = cmd.Process.Kill()

return nil, warnings, ErrScanTimeout
case <-done:

// Trigger progress function exit
close(doneProgress)

Expand Down Expand Up @@ -269,6 +279,72 @@ func (s *Scanner) RunWithProgress(liveProgress chan<- float32) (result *Run, war
}
}

// RunWithStreamer runs nmap synchronously. The xml output will be written directly to a file.
elivlo marked this conversation as resolved.
Show resolved Hide resolved
// It uses a streamer interface to constantly stream the stdout.
func (s *Scanner) RunWithStreamer(stream Streamer, file string) (warnings []string, err error) {
Ullaakut marked this conversation as resolved.
Show resolved Hide resolved
// Enable XML output
s.args = append(s.args, "-oX")

// Get XML output in stdout instead of writing it in a file
s.args = append(s.args, file)

// Enable progress output every second
s.args = append(s.args, "--stats-every", "5s")

// Prepare nmap process
cmd := exec.CommandContext(s.ctx, s.binaryPath, s.args...)

// Write stderr to buffer
stderrBuf := bytes.Buffer{}
cmd.Stderr = &stderrBuf

// Connect to the StdoutPipe
stdoutIn, err := cmd.StdoutPipe()
if err != nil {
return warnings, errors.WithMessage(err, "connect to StdoutPipe failed")
}
stdout := stream

// Run nmap process
if err := cmd.Start(); err != nil {
return warnings, errors.WithMessage(err, "start command failed")
}

// Copy stdout to pipe
g, _ := errgroup.WithContext(s.ctx)
g.Go(func() error {
_, err = io.Copy(stdout, stdoutIn)
return err
})

if err := cmd.Wait(); err != nil {
warnings = append(warnings, errors.WithMessage(err, "nmap command failed").Error())
}
if err := g.Wait(); err != nil {
warnings = append(warnings, errors.WithMessage(err, "read from stdout failed").Error())
}
// Process nmap stderr output containing none-critical errors and warnings
// Everyone needs to check whether one or some of these warnings is a hard issue in their use case
if stderrBuf.Len() > 0 {
for _, v := range strings.Split(strings.Trim(stderrBuf.String(), "\n"), "\n") {
warnings = append(warnings, v)
}
}

// Check for warnings that will inevitable lead to parsing errors, hence, have priority
for _, warning := range warnings {
switch {
case strings.Contains(warning, "Malloc Failed!"):
return warnings, ErrMallocFailed
// TODO: Add cases for other known errors we might want to guard.
default:
}
}

// Return result, optional warnings but no error
return warnings, nil
}

// RunAsync runs nmap asynchronously and returns error.
// TODO: RunAsync should return warnings as well.
func (s *Scanner) RunAsync() error {
Expand Down
6 changes: 3 additions & 3 deletions nmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,10 @@ func TestRunWithProgress(t *testing.T) {
WithCustomArguments("tests/xml/scan_base.xml"),
},

elivlo marked this conversation as resolved.
Show resolved Hide resolved
compareWholeRun: true,
expectedResult: r,
compareWholeRun: true,
expectedResult: r,
expectedProgress: []float32{56.66, 81.95, 87.84, 94.43, 97.76, 97.76},
expectedErr: nil,
expectedErr: nil,
},
}

Expand Down