package cmrdr import ( "bufio" "encoding/xml" "fmt" "io/ioutil" "os/exec" "strings" "github.com/pkg/errors" v "gopkg.in/go-playground/validator.v9" ) // These constants detail the different level of nmap speed presets // that determine the timeout values and wether or not nmap makes use of parallelism const ( // PARANOIAC NO PARALLELISM | 5min timeout | 100ms to 10s round-trip time timeout | 5mn scan delay PARANOIAC = 0 // SNEAKY NO PARALLELISM | 15sec timeout | 100ms to 10s round-trip time timeout | 15s scan delay SNEAKY = 1 // POLITE NO PARALLELISM | 1sec timeout | 100ms to 10s round-trip time timeout | 400ms scan delay POLITE = 2 // NORMAL PARALLELISM | 1sec timeout | 100ms to 10s round-trip time timeout | 0s scan delay NORMAL = 3 // AGGRESSIVE PARALLELISM | 500ms timeout | 100ms to 1250ms round-trip time timeout | 0s scan delay AGGRESSIVE = 4 // INSANE PARALLELISM | 250ms timeout | 50ms to 300ms round-trip time timeout | 0s scan delay INSANE = 5 ) // Allows unit tests to override the exec function to avoid launching a real command // during the tests. The NmapRun method will soon be refactored with an adaptor in order // to make it possible to mock all external calls. var execCommand = exec.Command // NmapRun runs nmap on the specified targets's specified ports, using the given nmap speed. func NmapRun(targets, ports, resultFilePath string, nmapSpeed int, enableLogs bool) error { if nmapSpeed < PARANOIAC || nmapSpeed > INSANE { return fmt.Errorf("invalid nmap speed value '%d'. Should be between '%d' and '%d'", nmapSpeed, PARANOIAC, INSANE) } cmdArgs := fmt.Sprintf( "-T%d -A -p %s -oX %s %s", nmapSpeed, ports, resultFilePath, targets) if enableLogs { fmt.Printf("command: nmap %s\n", cmdArgs) } args := strings.Split(cmdArgs, " ") // Prepare nmap command cmd := execCommand("nmap", args...) // Pipe stdout to be able to write the logs in realtime stdout, err := cmd.StdoutPipe() if err != nil { return errors.Wrap(err, "couldn't get stdout pipe") } // Execute the nmap command if err := cmd.Start(); err != nil { return errors.Wrap(err, "coudln't run nmap command") } // Scan the pipe until an end of file or an error occurs in := bufio.NewScanner(stdout) for in.Scan() { if enableLogs { fmt.Println(in.Text()) } } if err := in.Err(); err != nil { if enableLogs { fmt.Printf("error: %s\n", err) } } return nil } // NmapParseResults returns a slice of streams from an NMap XML result file. // To generate one yourself, use the -X option when running NMap. func NmapParseResults(nmapResultFilePath string) ([]Stream, error) { var streams []Stream // Open & Read XML file content, err := ioutil.ReadFile(nmapResultFilePath) if err != nil { return streams, errors.Wrap(err, "could not read nmap result file at "+nmapResultFilePath+":") } // Unmarshal content of XML file into data structure result := &nmapResult{} err = xml.Unmarshal(content, &result) if err != nil { return streams, err } // Iterate on hosts to try to find hosts with ports that // - serve RTSP // - are open validate := v.New() for _, host := range result.Hosts { if host.Ports.Ports == nil { continue } for _, port := range host.Ports.Ports { err = validate.Struct(port) if err != nil { continue } streams = append(streams, Stream{ Device: port.Service.Product, Address: host.Address.Addr, Port: port.PortID, }) } } return streams, nil } // Discover scans the target networks and tries to find RTSP streams within them. // // targets can be: // // - a subnet (e.g.: 172.16.100.0/24) // - an IP (e.g.: 172.16.100.10) // - a hostname (e.g.: localhost) // - a range of IPs (e.g.: 172.16.100.10-20) // // ports can be: // // - one or multiple ports and port ranges separated by commas (e.g.: 554,8554-8560,18554-28554) func Discover(targets, ports, nmapResultPath string, speed int, log bool) ([]Stream, error) { var streams []Stream // Run nmap command to discover open ports on the specified targets & ports err := NmapRun(targets, ports, nmapResultPath, speed, log) if err != nil { return streams, err } // Get found streams from nmap results streams, err = NmapParseResults(nmapResultPath) if err != nil { return streams, err } return streams, nil }