Files
cameradar/discover.go
T
2018-03-12 15:04:06 +01:00

169 lines
4.8 KiB
Go

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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
}