zgrab2/processing.go

220 lines
6.0 KiB
Go

package zgrab2
import (
"encoding/json"
"fmt"
"net"
"sync"
log "github.com/sirupsen/logrus"
"github.com/zmap/zgrab2/lib/output"
)
// Grab contains all scan responses for a single host
type Grab struct {
IP string `json:"ip,omitempty"`
Domain string `json:"domain,omitempty"`
Data map[string]ScanResponse `json:"data,omitempty"`
}
// ScanTarget is the host that will be scanned
type ScanTarget struct {
IP net.IP
Domain string
Tag string
Port *uint
}
func (target ScanTarget) String() string {
if target.IP == nil && target.Domain == "" {
return "<empty target>"
}
res := ""
if target.IP != nil && target.Domain != "" {
res = target.Domain + "(" + target.IP.String() + ")"
} else if target.IP != nil {
res = target.IP.String()
} else {
res = target.Domain
}
if target.Tag != "" {
res += " tag:" + target.Tag
}
return res
}
// Host gets the host identifier as a string: the IP address if it is available,
// or the domain if not.
func (target *ScanTarget) Host() string {
if target.IP != nil {
return target.IP.String()
} else if target.Domain != "" {
return target.Domain
}
log.Fatalf("Bad target %s: no IP/Domain", target.String())
panic("unreachable")
}
// Open connects to the ScanTarget using the configured flags, and returns a net.Conn that uses the configured timeouts for Read/Write operations.
func (target *ScanTarget) Open(flags *BaseFlags) (net.Conn, error) {
var port uint
// If the port is supplied in ScanTarget, let that override the cmdline option
if target.Port != nil {
port = *target.Port
} else {
port = flags.Port
}
address := net.JoinHostPort(target.Host(), fmt.Sprintf("%d", port))
return DialTimeoutConnection("tcp", address, flags.Timeout, flags.BytesReadLimit)
}
// OpenTLS connects to the ScanTarget using the configured flags, then performs
// the TLS handshake. On success error is nil, but the connection can be non-nil
// even if there is an error (this allows fetching the handshake log).
func (target *ScanTarget) OpenTLS(baseFlags *BaseFlags, tlsFlags *TLSFlags) (*TLSConnection, error) {
conn, err := tlsFlags.Connect(target, baseFlags)
if err != nil {
return conn, err
}
err = conn.Handshake()
return conn, err
}
// OpenUDP connects to the ScanTarget using the configured flags, and returns a net.Conn that uses the configured timeouts for Read/Write operations.
// Note that the UDP "connection" does not have an associated timeout.
func (target *ScanTarget) OpenUDP(flags *BaseFlags, udp *UDPFlags) (net.Conn, error) {
var port uint
// If the port is supplied in ScanTarget, let that override the cmdline option
if target.Port != nil {
port = *target.Port
} else {
port = flags.Port
}
address := net.JoinHostPort(target.Host(), fmt.Sprintf("%d", port))
var local *net.UDPAddr
if udp != nil && (udp.LocalAddress != "" || udp.LocalPort != 0) {
local = &net.UDPAddr{}
if udp.LocalAddress != "" && udp.LocalAddress != "*" {
local.IP = net.ParseIP(udp.LocalAddress)
}
if udp.LocalPort != 0 {
local.Port = int(udp.LocalPort)
}
}
remote, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", local, remote)
if err != nil {
return nil, err
}
return NewTimeoutConnection(nil, conn, flags.Timeout, 0, 0, flags.BytesReadLimit), nil
}
// grabTarget calls handler for each action
func grabTarget(input ScanTarget, m *Monitor) []byte {
moduleResult := make(map[string]ScanResponse)
for _, scannerName := range orderedScanners {
scanner := scanners[scannerName]
trigger := (*scanner).GetTrigger()
if input.Tag != trigger {
continue
}
defer func(name string) {
if e := recover(); e != nil {
log.Errorf("Panic on scanner %s when scanning target %s: %#v", scannerName, input.String(), e)
// Bubble out original error (with original stack) in lieu of explicitly logging the stack / error
panic(e)
}
}(scannerName)
name, res := RunScanner(*scanner, m, input)
moduleResult[name] = res
if res.Error != nil && !config.Multiple.ContinueOnError {
break
}
if res.Status == SCAN_SUCCESS && config.Multiple.BreakOnSuccess {
break
}
}
var ipstr string
if input.IP == nil {
ipstr = ""
} else {
s := input.IP.String()
ipstr = s
}
raw := Grab{IP: ipstr, Domain: input.Domain, Data: moduleResult}
var outputData interface{} = raw
if !includeDebugOutput() {
// If the caller doesn't explicitly request debug data, strip it out.
// Take advantage of the fact that we can skip the (expensive) call to
// process if debug output is included (TODO: until Process does anything else)
processor := output.Processor{Verbose: false}
stripped, err := processor.Process(raw)
if err != nil {
log.Debugf("Error processing results: %v", err)
stripped = raw
}
outputData = stripped
}
result, err := json.Marshal(outputData)
if err != nil {
log.Fatalf("unable to marshal data: %s", err)
}
return result
}
// Process sets up an output encoder, input reader, and starts grab workers.
func Process(mon *Monitor) {
workers := config.Senders
processQueue := make(chan ScanTarget, workers*4)
outputQueue := make(chan []byte, workers*4)
//Create wait groups
var workerDone sync.WaitGroup
var outputDone sync.WaitGroup
workerDone.Add(int(workers))
outputDone.Add(1)
// Start the output encoder
go func() {
defer outputDone.Done()
if err := config.outputResults(outputQueue); err != nil {
log.Fatal(err)
}
}()
//Start all the workers
for i := 0; i < workers; i++ {
go func(i int) {
for _, scannerName := range orderedScanners {
scanner := *scanners[scannerName]
scanner.InitPerSender(i)
}
for obj := range processQueue {
for run := uint(0); run < uint(config.ConnectionsPerHost); run++ {
result := grabTarget(obj, mon)
outputQueue <- result
}
}
workerDone.Done()
}(i)
}
if err := config.inputTargets(processQueue); err != nil {
log.Fatal(err)
}
close(processQueue)
workerDone.Wait()
close(outputQueue)
outputDone.Wait()
}