Compare commits

...

12 Commits

Author SHA1 Message Date
Brendan Le Glaunec 80e75061da Create separate go module for xplatform magefile (#284) 2021-04-05 15:58:07 +02:00
Brendan Le Glaunec 5b48737cc7 Add crossplatform docker builds (#283) 2021-04-05 15:38:28 +02:00
Brendan Le Glaunec da2dac70ac Update curl-dev dependency version (#282) 2021-04-05 12:48:48 +02:00
Sliicy 8c8ea1209b Update routes with support for Lorex LHB927 (#270)
The Lorex LHB927 supports RTSP streams which have routes:
ch1/0 for camera 1
ch2/0 for camera 2
...
They start counting from 1, not 0.
Alternatively, they can also be reached at:
ch1_0
ch2_0
...
Adding more routes (for cameras #3 & 4) increases the odds of finding the DVR, if for example, camera 1 is offline.
2020-08-13 09:48:43 +02:00
Brendan Le Glaunec df3718a06c Remove /v5 in module path in documentation 2020-08-12 09:10:29 +02:00
Ullaakut 6486d04e61 Test debug mode in attack & remove unnecessary aliases & newlines 2020-05-04 11:10:48 +02:00
Ullaakut 96928ac43c Set module version to v5
* Update README to reflect changes
* Various README improvements
2020-05-04 11:10:48 +02:00
Ullaakut 8e7de3f59e Support multi-route detection
* Change stream model to support multiple routes
* Simplify attack algorithm
* Use dummy route to detect cameras which accept all routes
2020-05-04 11:10:48 +02:00
Ullaakut fbc0b7a66d Move issue template to .github and remove old changelog file 2020-05-04 11:10:48 +02:00
Ullaakut 78eda6672e Fix stream discovery on non-standard ports by using service info mode in nmap scan 2020-05-04 11:10:48 +02:00
Ullaakut 9f05634531 Reword error message when no stream is found 2020-05-04 11:10:48 +02:00
Ullaakut defc308a9d Remove unnecessary import alias 2020-05-04 11:10:48 +02:00
20 changed files with 349 additions and 80 deletions
View File
+1 -1
View File
@@ -26,7 +26,7 @@ RUN echo 'http://dl-cdn.alpinelinux.org/alpine/v3.9/main' >> /etc/apk/repositori
RUN apk --update add --no-cache nmap \ RUN apk --update add --no-cache nmap \
nmap-nselibs \ nmap-nselibs \
nmap-scripts \ nmap-scripts \
curl-dev==7.64.0-r3 curl-dev==7.64.0-r5
WORKDIR /app/cameradar WORKDIR /app/cameradar
COPY --from=build-env /go/src/github.com/Ullaakut/cameradar/dictionaries/ /app/dictionaries/ COPY --from=build-env /go/src/github.com/Ullaakut/cameradar/dictionaries/ /app/dictionaries/
+18 -24
View File
@@ -76,6 +76,8 @@ e.g.: `docker run -t ullaakut/cameradar -t 192.168.100.0/24` will scan the ports
Only use this solution if for some reason using docker is not an option for you or if you want to locally build Cameradar on your machine. Only use this solution if for some reason using docker is not an option for you or if you want to locally build Cameradar on your machine.
**WARNING**: Manually building the binary will **NOT WORK** for any camera that uses **DIGEST AUTHENTICATION** [if your version of `curl` is over `7.64.0`](https://github.com/Ullaakut/cameradar/pull/252), which is most likely the case. For more information, see [this response on the subject from the author of curl](https://stackoverflow.com/a/59778142/4145098).
### Dependencies ### Dependencies
* `go` (> `1.10`) * `go` (> `1.10`)
@@ -84,13 +86,10 @@ Only use this solution if for some reason using docker is not an option for you
### Steps to install ### Steps to install
Make sure you installed the [dependencies](#dependencies), **and that you have Go modules enabled (`GO111MODULE=on`)**. 1. `go get github.com/Ullaakut/cameradar`
2. `cd $GOPATH/src/github.com/Ullaakut/cameradar`
1. `export GO111MODULE=on` (unless it's already on) 3. `cd cmd/cameradar`
2. `go get github.com/Ullaakut/cameradar` 4. `go install`
3. `cd $GOPATH/src/github.com/Ullaakut/cameradar`
4. `cd cmd/cameradar`
5. `go install`
The `cameradar` binary is now in your `$GOPATH/bin` ready to be used. See command line options [here](#command-line-options). The `cameradar` binary is now in your `$GOPATH/bin` ready to be used. See command line options [here](#command-line-options).
@@ -134,7 +133,7 @@ If you have [VLC Media Player](http://www.videolan.org/vlc/), you should be able
The file can contain IPs, hostnames, IP ranges and subnetwork, separated by newlines. Example: The file can contain IPs, hostnames, IP ranges and subnetwork, separated by newlines. Example:
```go ```text
0.0.0.0 0.0.0.0
localhost localhost
192.17.0.0/16 192.17.0.0/16
@@ -186,7 +185,7 @@ Default value: `4`
### `CAMERADAR_ATTACK_INTERVAL` ### `CAMERADAR_ATTACK_INTERVAL`
This optional variable allows you to set custom interval to wait between each attack in order to stay stealthy. It's recommended to increase it when attempting to scan a network that might be protected against bruteforce attacks. By default, there is no interval, in order to make attacks as fast as possible This optional variable allows you to set `custom interval` to wait between each attack in order to stay stealthy. It's recommended to increase it when attempting to scan a network that might be protected against bruteforce attacks. By default, there is no interval, in order to make attacks as fast as possible
Default value: `0ms` Default value: `0ms`
@@ -216,18 +215,13 @@ Your image will be called `cameradar` and NOT `ullaakut/cameradar`.
#### Go build #### Go build
Make sure you installed the [dependencies](#dependencies), **and that you have Go modules enabled (`GO111MODULE=on`)**. 1. `go get github.com/Ullaakut/cameradar`
2. `cd $GOPATH/src/github.com/Ullaakut/cameradar`
1. `export GO111MODULE=on` (unless it's already on) 3. `cd cmd/cameradar`
2. `go get github.com/Ullaakut/cameradar` 4. `go install`
3. `cd $GOPATH/src/github.com/Ullaakut/cameradar`
4. `cd cmd/cameradar`
5. `go install`
The cameradar binary is now in `$GOPATH/bin/cameradar`. The cameradar binary is now in `$GOPATH/bin/cameradar`.
See [the contribution document](/CONTRIBUTING.md) to get started.
## Frequently Asked Questions ## Frequently Asked Questions
> Cameradar does not detect any camera! > Cameradar does not detect any camera!
@@ -236,27 +230,27 @@ That means that either your cameras are not streaming in RTSP or that they are n
> Cameradar detects my cameras, but does not manage to access them at all! > Cameradar detects my cameras, but does not manage to access them at all!
Maybe your cameras have been configured and the credentials / URL have been changed. Cameradar only guesses using default constructor values if a custom dictionary is not provided. You can use your own dictionaries in which you just have to add your credentials and RTSP routes. To do that, see how the [configuration](#configuration) works. Also, maybe your camera's credentials are not yet known, in which case if you find them it would be very nice to add them to the Cameradar dictionaries to help other people in the future. Maybe your cameras have been configured, and the credentials / URL have been changed. Cameradar only guesses using default constructor values if a custom dictionary is not provided. You can use your own dictionaries in which you just have to add your credentials and RTSP routes. To do that, see how the [configuration](#configuration) works. Also, maybe your camera's credentials are not yet known, in which case if you find them it would be very nice to add them to the Cameradar dictionaries to help other people in the future.
> What happened to the C++ version? > What happened to the C++ version?
You can still find it under the 1.1.4 tag on this repo, however it was slower and less stable than the current version written in Golang. It is not recommended to use it. You can still find it under the 1.1.4 tag on this repo, however it was slower and less stable than the current version written in Golang. It is not recommended using it.
> How to use the Cameradar library for my own project? > How to use the Cameradar library for my own project?
See the example in `/cmd/cameradar`. You just need to run `go get github.com/Ullaakut/cameradar` and to use the `cameradar` package in your code. You can find the documentation on [godoc](https://godoc.org/github.com/ullaakut/cameradar). See the example in `/cmd/cameradar`. You just need to run `go get github.com/Ullaakut/cameradar` and to use the `cameradar` package in your code. You can find the documentation on [godoc](https://godoc.org/github.com/ullaakut/cameradar).
> I want to scan my own localhost for some reason and it does not work! What's going on? > I want to scan my own localhost for some reason, and it does not work! What's going on?
Use the `--net=host` flag when launching the cameradar image, or use the binary by running `go run cameradar/cameradar.go` or [installing it](#installing-the-binary). Use the `--net=host` flag when launching the cameradar image, or use the binary by running `go run cameradar/cameradar.go` or [installing it](#go-build).
> I don't see a colored output:( > I don't see a colored output:(
You forgot the `-t` flag before `ullaakut/cameradar` in your command-line. This tells docker to allocate a pseudo-tty for cameradar, which makes it able to use colors. You forgot the `-t` flag before `ullaakut/cameradar` in your command-line. This tells docker to allocate a pseudo-tty for cameradar, which makes it able to use colors.
> I don't have a camera but I'd like to try Cameradar! > I don't have a camera, but I'd like to try Cameradar!
Simply run `docker run -p 8554:8554 -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -e RTSP_PORT=8554 ullaakut/rtspatt` and then run cameradar and it should guess that the username is admin and the password is 12345. You can try this with any default constructor credentials (they can be found [here](dictionaries/credentials.json)). Simply run `docker run -p 8554:8554 -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -e RTSP_PORT=8554 ullaakut/rtspatt` and then run cameradar, and it should guess that the username is admin and that the password is 12345. You can try this with any default constructor credentials (they can be found [here](dictionaries/credentials.json)).
> What authentication types does Cameradar support? > What authentication types does Cameradar support?
+49 -34
View File
@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
curl "github.com/Ullaakut/go-curl" "github.com/Ullaakut/go-curl"
) )
// HTTP responses. // HTTP responses.
@@ -21,10 +21,20 @@ const (
rtspSetup = 4 rtspSetup = 4
) )
// Authentication types.
const (
authNone = 0
authBasic = 1
authDigest = 2
)
// Route that should never be a constructor default.
const dummyRoute = "/0x8b6c42"
// Attack attacks the given targets and returns the accessed streams. // Attack attacks the given targets and returns the accessed streams.
func (s *Scanner) Attack(targets []Stream) ([]Stream, error) { func (s *Scanner) Attack(targets []Stream) ([]Stream, error) {
if len(targets) == 0 { if len(targets) == 0 {
return nil, fmt.Errorf("unable to attack empty list of targets") return nil, fmt.Errorf("no stream found")
} }
// Most cameras will be accessed successfully with these two attacks. // Most cameras will be accessed successfully with these two attacks.
@@ -76,20 +86,13 @@ func (s *Scanner) AttackCredentials(targets []Stream) []Stream {
defer close(resChan) defer close(resChan)
for i := range targets { for i := range targets {
// TODO: Perf Improvement: Skip cameras with no auth type detected, and set their
// CredentialsFound value to true.
go s.attackCameraCredentials(targets[i], resChan) go s.attackCameraCredentials(targets[i], resChan)
} }
attackResults := []Stream{}
// TODO: Change this into a for+select and make a successful result close the chan.
for range targets { for range targets {
attackResults = append(attackResults, <-resChan) attackResult := <-resChan
} if attackResult.CredentialsFound {
targets = replace(targets, attackResult)
for i := range attackResults {
if attackResults[i].CredentialsFound {
targets = replace(targets, attackResults[i])
} }
} }
@@ -106,15 +109,10 @@ func (s *Scanner) AttackRoute(targets []Stream) []Stream {
go s.attackCameraRoute(targets[i], resChan) go s.attackCameraRoute(targets[i], resChan)
} }
attackResults := []Stream{}
// TODO: Change this into a for+select and make a successful result close the chan.
for range targets { for range targets {
attackResults = append(attackResults, <-resChan) attackResult := <-resChan
} if attackResult.RouteFound {
targets = replace(targets, attackResult)
for i := range attackResults {
if attackResults[i].RouteFound {
targets = replace(targets, attackResults[i])
} }
} }
@@ -130,11 +128,11 @@ func (s *Scanner) DetectAuthMethods(targets []Stream) []Stream {
var authMethod string var authMethod string
switch targets[i].AuthenticationType { switch targets[i].AuthenticationType {
case 0: case authNone:
authMethod = "no" authMethod = "no"
case 1: case authBasic:
authMethod = "basic" authMethod = "basic"
case 2: case authDigest:
authMethod = "digest" authMethod = "digest"
} }
@@ -164,18 +162,27 @@ func (s *Scanner) attackCameraCredentials(target Stream, resChan chan<- Stream)
} }
func (s *Scanner) attackCameraRoute(target Stream, resChan chan<- Stream) { func (s *Scanner) attackCameraRoute(target Stream, resChan chan<- Stream) {
// If the stream responds positively to the dummy route, it means
// it doesn't require (or respect the RFC) a route and the attack
// can be skipped.
ok := s.routeAttack(target, dummyRoute)
if ok {
target.RouteFound = true
target.Routes = append(target.Routes, "/")
resChan <- target
return
}
// Otherwise, bruteforce the routes.
for _, route := range s.routes { for _, route := range s.routes {
ok := s.routeAttack(target, route) ok := s.routeAttack(target, route)
if ok { if ok {
target.RouteFound = true target.RouteFound = true
target.Route = route target.Routes = append(target.Routes, route)
resChan <- target
return
} }
time.Sleep(s.attackInterval) time.Sleep(s.attackInterval)
} }
target.RouteFound = false
resChan <- target resChan <- target
} }
@@ -186,7 +193,7 @@ func (s *Scanner) detectAuthMethod(stream Stream) int {
"rtsp://%s:%d/%s", "rtsp://%s:%d/%s",
stream.Address, stream.Address,
stream.Port, stream.Port,
stream.Route, stream.Route(),
) )
s.setCurlOptions(c) s.setCurlOptions(c)
@@ -210,7 +217,7 @@ func (s *Scanner) detectAuthMethod(stream Stream) int {
return -1 return -1
} }
if s.verbose { if s.debug {
s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", authType) s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", authType)
} }
@@ -255,7 +262,7 @@ func (s *Scanner) routeAttack(stream Stream, route string) bool {
return false return false
} }
if s.verbose { if s.debug {
s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", rc) s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", rc)
} }
// If it's a 401 or 403, it means that the credentials are wrong but the route might be okay. // If it's a 401 or 403, it means that the credentials are wrong but the route might be okay.
@@ -275,7 +282,7 @@ func (s *Scanner) credAttack(stream Stream, username string, password string) bo
password, password,
stream.Address, stream.Address,
stream.Port, stream.Port,
stream.Route, stream.Route(),
) )
s.setCurlOptions(c) s.setCurlOptions(c)
@@ -304,7 +311,7 @@ func (s *Scanner) credAttack(stream Stream, username string, password string) bo
return false return false
} }
if s.verbose { if s.debug {
s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", rc) s.term.Debugln("DESCRIBE", attackURL, "RTSP/1.0 >", rc)
} }
@@ -325,7 +332,7 @@ func (s *Scanner) validateStream(stream Stream) bool {
stream.Password, stream.Password,
stream.Address, stream.Address,
stream.Port, stream.Port,
stream.Route, stream.Route(),
) )
s.setCurlOptions(c) s.setCurlOptions(c)
@@ -356,9 +363,10 @@ func (s *Scanner) validateStream(stream Stream) bool {
return false return false
} }
if s.verbose { if s.debug {
s.term.Debugln("SETUP", attackURL, "RTSP/1.0 >", rc) s.term.Debugln("SETUP", attackURL, "RTSP/1.0 >", rc)
} }
// If it's a 200, the stream is accessed successfully. // If it's a 200, the stream is accessed successfully.
if rc == httpOK { if rc == httpOK {
return true return true
@@ -375,6 +383,13 @@ func (s *Scanner) setCurlOptions(c Curler) {
_ = c.Setopt(curl.OPT_NOBODY, 1) _ = c.Setopt(curl.OPT_NOBODY, 1)
// Set custom timeout. // Set custom timeout.
_ = c.Setopt(curl.OPT_TIMEOUT_MS, int(s.timeout/time.Millisecond)) _ = c.Setopt(curl.OPT_TIMEOUT_MS, int(s.timeout/time.Millisecond))
// Enable verbose logs if verbose mode is on.
if s.verbose {
_ = c.Setopt(curl.OPT_VERBOSE, 1)
} else {
_ = c.Setopt(curl.OPT_VERBOSE, 0)
}
} }
// HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing // HACK: See https://stackoverflow.com/questions/3572397/lib-curl-in-c-disable-printing
+102 -3
View File
@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/Ullaakut/disgo" "github.com/Ullaakut/disgo"
curl "github.com/Ullaakut/go-curl" "github.com/Ullaakut/go-curl"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -89,7 +89,7 @@ func TestAttack(t *testing.T) {
targets: nil, targets: nil,
expectedStreams: nil, expectedStreams: nil,
expectedErr: errors.New("unable to attack empty list of targets"), expectedErr: errors.New("no stream found"),
}, },
} }
@@ -109,7 +109,8 @@ func TestAttack(t *testing.T) {
term: disgo.NewTerminal(disgo.WithDefaultOutput(ioutil.Discard)), term: disgo.NewTerminal(disgo.WithDefaultOutput(ioutil.Discard)),
curl: curlerMock, curl: curlerMock,
timeout: time.Millisecond, timeout: time.Millisecond,
verbose: false, verbose: true,
debug: true,
credentials: fakeCredentials, credentials: fakeCredentials,
routes: fakeRoutes, routes: fakeRoutes,
} }
@@ -251,6 +252,7 @@ func TestAttackCredentials(t *testing.T) {
curl: curlerMock, curl: curlerMock,
timeout: test.timeout, timeout: test.timeout,
verbose: test.verbose, verbose: test.verbose,
debug: test.verbose,
credentials: test.credentials, credentials: test.credentials,
} }
@@ -389,6 +391,102 @@ func TestAttackRoute(t *testing.T) {
} }
} }
scanner := &Scanner{
term: disgo.NewTerminal(disgo.WithDefaultOutput(ioutil.Discard)),
curl: curlerMock,
timeout: test.timeout,
verbose: test.verbose,
debug: test.verbose,
routes: test.routes,
}
results := scanner.AttackRoute(test.targets)
assert.Len(t, results, len(test.expectedStreams))
curlerMock.AssertExpectations(t)
})
}
}
func TestAttackRoute_NoDummyRoute(t *testing.T) {
var (
stream1 = Stream{
Device: "fakeDevice",
Address: "fakeAddress",
Port: 1337,
Available: true,
}
stream2 = Stream{
Device: "fakeDevice",
Address: "differentFakeAddress",
Port: 1337,
Available: true,
}
fakeTargets = []Stream{stream1, stream2}
fakeRoutes = Routes{"live.sdp", "media.amp"}
)
tests := []struct {
description string
targets []Stream
routes Routes
timeout time.Duration
verbose bool
status int
expectedStreams []Stream
expectedErr error
}{
{
description: "Route found",
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
status: 403,
expectedStreams: fakeTargets,
},
{
description: "Route found",
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
status: 401,
expectedStreams: fakeTargets,
},
{
description: "Camera accessed",
targets: fakeTargets,
routes: fakeRoutes,
timeout: 1 * time.Millisecond,
status: 200,
expectedStreams: fakeTargets,
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
curlerMock := &CurlerMock{}
curlerMock.On("Setopt", mock.Anything, mock.Anything).Return(nil)
curlerMock.On("Perform").Return(nil)
// 404 on first call to the dummy route.
curlerMock.On("Getinfo", mock.Anything).Return(404, nil).Once()
curlerMock.On("Getinfo", mock.Anything).Return(test.status, nil)
scanner := &Scanner{ scanner := &Scanner{
term: disgo.NewTerminal(disgo.WithDefaultOutput(ioutil.Discard)), term: disgo.NewTerminal(disgo.WithDefaultOutput(ioutil.Discard)),
curl: curlerMock, curl: curlerMock,
@@ -534,6 +632,7 @@ func TestValidateStreams(t *testing.T) {
curl: curlerMock, curl: curlerMock,
timeout: test.timeout, timeout: test.timeout,
verbose: test.verbose, verbose: test.verbose,
debug: test.verbose,
} }
results := scanner.ValidateStreams(test.targets) results := scanner.ValidateStreams(test.targets)
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/Ullaakut/cameradar" "github.com/Ullaakut/cameradar/v5"
"github.com/Ullaakut/disgo" "github.com/Ullaakut/disgo"
"github.com/Ullaakut/disgo/style" "github.com/Ullaakut/disgo/style"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -25,7 +25,7 @@ func parseArguments() error {
pflag.IntP("scan-speed", "s", 4, "The nmap speed preset to use for scanning (lower is stealthier)") pflag.IntP("scan-speed", "s", 4, "The nmap speed preset to use for scanning (lower is stealthier)")
pflag.DurationP("attack-interval", "I", 0, "The interval between each attack (i.e: 2000ms, higher is stealthier)") pflag.DurationP("attack-interval", "I", 0, "The interval between each attack (i.e: 2000ms, higher is stealthier)")
pflag.DurationP("timeout", "T", 2000*time.Millisecond, "The timeout to use for attack attempts (i.e: 2000ms)") pflag.DurationP("timeout", "T", 2000*time.Millisecond, "The timeout to use for attack attempts (i.e: 2000ms)")
pflag.BoolP("debug", "d", true, "Enable the debug logs") pflag.BoolP("debug", "d", false, "Enable the debug logs")
pflag.BoolP("verbose", "v", false, "Enable the verbose logs") pflag.BoolP("verbose", "v", false, "Enable the verbose logs")
pflag.BoolP("help", "h", false, "displays this help message") pflag.BoolP("help", "h", false, "displays this help message")
+8
View File
@@ -66,6 +66,14 @@ ch001.sdp
ch01.264 ch01.264
ch01.264? ch01.264?
ch01.264?ptype=tcp ch01.264?ptype=tcp
ch1_0
ch2_0
ch3_0
ch4_0
ch1/0
ch2/0
ch3/0
ch4/0
ch0_0.h264 ch0_0.h264
ch0_unicast_firststream ch0_unicast_firststream
ch0_unicast_secondstream ch0_unicast_secondstream
+2 -2
View File
@@ -1,6 +1,6 @@
module github.com/Ullaakut/cameradar module github.com/Ullaakut/cameradar/v5
go 1.12 go 1.14
require ( require (
github.com/PuerkitoBio/goquery v1.5.0 github.com/PuerkitoBio/goquery v1.5.0
-2
View File
@@ -78,7 +78,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -139,7 +138,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+1 -1
View File
@@ -18,7 +18,7 @@ func replace(streams []Stream, new Stream) []Stream {
// GetCameraRTSPURL generates a stream's RTSP URL. // GetCameraRTSPURL generates a stream's RTSP URL.
func GetCameraRTSPURL(stream Stream) string { func GetCameraRTSPURL(stream Stream) string {
return "rtsp://" + stream.Username + ":" + stream.Password + "@" + stream.Address + ":" + fmt.Sprint(stream.Port) + "/" + stream.Route return "rtsp://" + stream.Username + ":" + stream.Password + "@" + stream.Address + ":" + fmt.Sprint(stream.Port) + "/" + stream.Route()
} }
// GetCameraAdminPanelURL returns the URL to the camera's admin panel. // GetCameraAdminPanelURL returns the URL to the camera's admin panel.
+1 -1
View File
@@ -61,7 +61,7 @@ func TestGetCameraRTSPURL(t *testing.T) {
Address: "1.2.3.4", Address: "1.2.3.4",
Username: "ullaakut", Username: "ullaakut",
Password: "ba69897483886f0d2b0afb6345b76c0c", Password: "ba69897483886f0d2b0afb6345b76c0c",
Route: "cameradar.sdp", Routes: []string{"cameradar.sdp"},
Port: 1337, Port: 1337,
} }
+14 -6
View File
@@ -4,12 +4,12 @@ import "time"
// Stream represents a camera's RTSP stream // Stream represents a camera's RTSP stream
type Stream struct { type Stream struct {
Device string `json:"device"` Device string `json:"device"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
Route string `json:"route"` Routes []string `json:"route"`
Address string `json:"address" validate:"required"` Address string `json:"address" validate:"required"`
Port uint16 `json:"port" validate:"required"` Port uint16 `json:"port" validate:"required"`
CredentialsFound bool `json:"credentials_found"` CredentialsFound bool `json:"credentials_found"`
RouteFound bool `json:"route_found"` RouteFound bool `json:"route_found"`
@@ -18,6 +18,14 @@ type Stream struct {
AuthenticationType int `json:"authentication_type"` AuthenticationType int `json:"authentication_type"`
} }
// Route returns this stream's route if there is one.
func (s Stream) Route() string {
if len(s.Routes) > 0 {
return s.Routes[0]
}
return ""
}
// Credentials is a map of credentials // Credentials is a map of credentials
// usernames are keys and passwords are values // usernames are keys and passwords are values
// creds['admin'] -> 'secure_password' // creds['admin'] -> 'secure_password'
+1
View File
@@ -25,6 +25,7 @@ func (s *Scanner) Scan() ([]Stream, error) {
nmapScanner, err := nmap.NewScanner( nmapScanner, err := nmap.NewScanner(
nmap.WithTargets(s.targets...), nmap.WithTargets(s.targets...),
nmap.WithPorts(s.ports...), nmap.WithPorts(s.ports...),
nmap.WithServiceInfo(),
nmap.WithTimingTemplate(nmap.Timing(s.scanSpeed)), nmap.WithTimingTemplate(nmap.Timing(s.scanSpeed)),
) )
if err != nil { if err != nil {
-1
View File
@@ -103,7 +103,6 @@ func TestScan(t *testing.T) {
} }
func TestInternalScan(t *testing.T) { func TestInternalScan(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
+7 -2
View File
@@ -46,11 +46,16 @@ func (s *Scanner) PrintStreams(streams []Stream) {
s.term.Infof("\tPassword:\t\t%s\n", style.Failure("not found")) s.term.Infof("\tPassword:\t\t%s\n", style.Failure("not found"))
} }
s.term.Infoln("\tRTSP routes:")
if stream.RouteFound { if stream.RouteFound {
s.term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Success("/"+stream.Route)) for _, route := range stream.Routes {
s.term.Infoln(style.Success("\t\t\t\t/" + route))
}
} else { } else {
s.term.Infof("\tRTSP route:\t\t%s\n\n\n", style.Failure("not found")) s.term.Infoln(style.Failure("not found"))
} }
s.term.Info("\n\n")
} }
if success > 1 { if success > 1 {
+1 -1
View File
@@ -39,7 +39,7 @@ var (
routeFound = Stream{ routeFound = Stream{
RouteFound: true, RouteFound: true,
Route: "r0ute", Routes: []string{"r0ute"},
} }
) )
+10
View File
@@ -0,0 +1,10 @@
module github.com/Ullaakut/cameradar/magefile
go 1.16
require (
github.com/Ullaakut/disgo v0.3.1
github.com/fatih/color v1.10.0 // indirect
github.com/magefile/mage v1.11.0
github.com/stretchr/testify v1.7.0 // indirect
)
+23
View File
@@ -0,0 +1,23 @@
github.com/Ullaakut/disgo v0.3.1 h1:BGGVHynji41KGuGI02ztTCnILRvyzlvmiCRl5bBpjKk=
github.com/Ullaakut/disgo v0.3.1/go.mod h1:/CSvpnYVSKOeh2dvUvx9cXshzz2t7T1/lRO/MrFj3fI=
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/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+109
View File
@@ -0,0 +1,109 @@
//+build mage
package main
import (
"os"
"github.com/magefile/mage/sh"
"github.com/Ullaakut/disgo"
"github.com/Ullaakut/disgo/style"
)
var supportedPlatforms = map[string]string{
"linux/amd64": "ullaakut/cameradar:amd64",
"linux/386": "ullaakut/cameradar:386",
"linux/arm64": "ullaakut/cameradar:arm64",
"linux/arm/v7": "ullaakut/cameradar:armv7",
//"linux/riscv64": "ullaakut/cameradar:riscv64", // UNSUPPORTED.
//"linux/ppc64le": "ullaakut/cameradar:ppc64le", // UNSUPPORTED.
//"linux/s390x": "ullaakut/cameradar:s390x", // UNSUPPORTED.
//"linux/arm/v6": "ullaakut/cameradar:armv6", // UNSUPPORTED.
}
var Default = Build
// Follows https://www.docker.com/blog/multi-platform-docker-builds/.
func Build() error {
term := disgo.NewTerminal(disgo.WithColors(true))
term.StartStep("Building images for all platforms")
term.Infof("Builds planned for %v\n", supportedPlatforms)
for platform, name := range supportedPlatforms {
term.Infoln("Building image for", platform, "at", name)
// docker buildx build --platform linux/arm/v7 -t ullaakut/cameradar:armv7 .
if err := sh.Run("docker", "buildx", "build", "--platform", platform, "-t", name, "../../"); err != nil {
return term.FailStepf("unable to build image: %v", err)
}
}
term.Infoln(style.Success("Cross-platform docker build successful."))
return nil
}
func Publish() error {
term := disgo.NewTerminal(disgo.WithColors(true))
term.StartStep("Pushing images to DockerHub")
term.Infoln("Pushing ullaakut/cameradar:latest")
if err := sh.Run("docker", "push", "ullaakut/cameradar:latest"); err != nil {
return term.FailStepf("unable to push latest docker images to docker hub: %v", err)
}
if version, exists := os.LookupEnv("CAMERADAR_VERSION"); exists {
term.Infoln("Pushing ullaakut/cameradar:"+version)
if err := sh.Run("docker", "push", "ullaakut/cameradar:"+version); err != nil {
return term.FailStepf("unable to push versionned docker images to docker hub: %v", err)
}
}
term.StartStep("Pushing images to GitHub Packages")
term.Infoln("Pushing docker.pkg.github.com/ullaakut/cameradar/cameradar:latest")
if err := sh.Run("docker", "tag", "ullaakut/cameradar:latest", "docker.pkg.github.com/ullaakut/cameradar/cameradar:latest"); err != nil {
return term.FailStepf("unable to push latest docker images to docker hub: %v", err)
}
if err := sh.Run("docker", "push", "docker.pkg.github.com/ullaakut/cameradar/cameradar:latest"); err != nil {
return term.FailStepf("unable to push latest docker images to docker hub: %v", err)
}
if version, exists := os.LookupEnv("CAMERADAR_VERSION"); exists {
term.Infoln("Pushing docker.pkg.github.com/ullaakut/cameradar/cameradar:"+version)
if err := sh.Run("docker", "tag", "ullaakut/cameradar:"+version, "docker.pkg.github.com/ullaakut/cameradar/cameradar:"+version); err != nil {
return term.FailStepf("unable to push latest docker images to docker hub: %v", err)
}
if err := sh.Run("docker", "push", "ullaakut/cameradar:"+version); err != nil {
return term.FailStepf("unable to push versionned docker images to docker hub: %v", err)
}
}
term.StartStep("Creating manifest(s) for cross platform builds")
var manifestImages []string
for _, image := range supportedPlatforms {
manifestImages = append(manifestImages, image)
}
args := []string{"manifest", "create", "--amend", "ullaakut/cameradar:latest"}
args = append(args, manifestImages...)
// docker manifest create ullaakut/cameradar:latest ullaakut/cameradar:amd64 ullaakut/cameradar:armv7 [...]
if err := sh.Run("docker", args...); err != nil {
return term.FailStepf("unable to create manifest: %v", err)
}
if version, exists := os.LookupEnv("CAMERADAR_VERSION"); exists {
args = []string{"manifest", "create", "--amend", "ullaakut/cameradar:"+version}
args = append(args, manifestImages...)
if err := sh.Run("docker", args...); err != nil {
return term.FailStepf("unable to create manifest: %v", err)
}
}
term.EndStep()
term.Infoln(style.Success("Images published successfully."))
return nil
}