Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af41fc6cb8 |
+38
-65
@@ -7,12 +7,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Ullaakut/cameradar/v6"
|
||||
"github.com/bluenviron/gortsplib/v5"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/liberrors"
|
||||
)
|
||||
|
||||
// Route that should never be a constructor default.
|
||||
const dummyRoute = "/0x8b6c42"
|
||||
const dummyRoute = "0x8b6c42"
|
||||
|
||||
// Dictionary provides dictionaries for routes, usernames and passwords.
|
||||
type Dictionary interface {
|
||||
@@ -187,6 +189,7 @@ func (a Attacker) reattackRoutes(ctx context.Context, streams []cameradar.Stream
|
||||
func needsReattack(streams []cameradar.Stream) bool {
|
||||
for _, stream := range streams {
|
||||
if stream.RouteFound && stream.CredentialsFound && stream.Available {
|
||||
// This stream is fully discovered, no need to re-attack.
|
||||
continue
|
||||
}
|
||||
return true
|
||||
@@ -257,7 +260,7 @@ func (a Attacker) attackRoutesForStream(ctx context.Context, target cameradar.St
|
||||
}
|
||||
if ok {
|
||||
target.RouteFound = true
|
||||
target.Routes = append(target.Routes, "/")
|
||||
target.Routes = append(target.Routes, "") // Add empty route for default.
|
||||
a.reporter.Progress(cameradar.StepAttackRoutes, fmt.Sprintf("Default route accepted for %s:%d", target.Address.String(), target.Port))
|
||||
return target, nil
|
||||
}
|
||||
@@ -287,67 +290,6 @@ func (a Attacker) attackRoutesForStream(ctx context.Context, target cameradar.St
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (a Attacker) detectAuthMethods(ctx context.Context, targets []cameradar.Stream) ([]cameradar.Stream, error) {
|
||||
streams, err := runParallel(ctx, targets, a.detectAuthMethod)
|
||||
if err != nil {
|
||||
return streams, err
|
||||
}
|
||||
|
||||
for i := range streams {
|
||||
a.reporter.Progress(cameradar.StepDetectAuth, cameradar.ProgressTickMessage())
|
||||
|
||||
var authMethod string
|
||||
switch streams[i].AuthenticationType {
|
||||
case cameradar.AuthNone:
|
||||
authMethod = "no"
|
||||
case cameradar.AuthBasic:
|
||||
authMethod = "basic"
|
||||
case cameradar.AuthDigest:
|
||||
authMethod = "digest"
|
||||
default:
|
||||
return streams, fmt.Errorf("unknown authentication method %d for %s:%d", streams[i].AuthenticationType, streams[i].Address.String(), streams[i].Port)
|
||||
}
|
||||
|
||||
a.reporter.Progress(cameradar.StepDetectAuth, fmt.Sprintf("Detected %s authentication for %s:%d", authMethod, streams[i].Address.String(), streams[i].Port))
|
||||
}
|
||||
|
||||
return streams, nil
|
||||
}
|
||||
|
||||
func (a Attacker) detectAuthMethod(ctx context.Context, stream cameradar.Stream) (cameradar.Stream, error) {
|
||||
if ctx.Err() != nil {
|
||||
return stream, ctx.Err()
|
||||
}
|
||||
u, urlStr, err := buildRTSPURL(stream, stream.Route(), "", "")
|
||||
if err != nil {
|
||||
return stream, fmt.Errorf("building rtsp url: %w", err)
|
||||
}
|
||||
|
||||
client, err := a.newRTSPClient(u)
|
||||
if err != nil {
|
||||
return stream, fmt.Errorf("starting rtsp client: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
_, res, err := client.Describe(u)
|
||||
if err != nil {
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) && res != nil && badStatus.Code == base.StatusUnauthorized {
|
||||
stream.AuthenticationType = authTypeFromHeaders(res.Header["WWW-Authenticate"])
|
||||
a.reporter.Debug(cameradar.StepDetectAuth, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, badStatus.Code))
|
||||
return stream, nil
|
||||
}
|
||||
return stream, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
if res != nil {
|
||||
a.reporter.Debug(cameradar.StepDetectAuth, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, res.StatusCode))
|
||||
}
|
||||
|
||||
stream.AuthenticationType = cameradar.AuthNone
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (a Attacker) routeAttack(stream cameradar.Stream, route string) (bool, error) {
|
||||
u, urlStr, err := buildRTSPURL(stream, route, stream.Username, stream.Password)
|
||||
if err != nil {
|
||||
@@ -399,7 +341,7 @@ func (a Attacker) validateStream(ctx context.Context, stream cameradar.Stream, e
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
desc, res, err := client.Describe(u)
|
||||
desc, res, err := a.describeWithRetry(ctx, client, u, urlStr)
|
||||
if err != nil {
|
||||
return a.handleDescribeError(stream, urlStr, err)
|
||||
}
|
||||
@@ -413,7 +355,6 @@ func (a Attacker) validateStream(ctx context.Context, stream cameradar.Stream, e
|
||||
if err != nil {
|
||||
return a.handleSetupError(stream, urlStr, err)
|
||||
}
|
||||
|
||||
a.logSetupResponse(urlStr, res)
|
||||
|
||||
stream.Available = res != nil && res.StatusCode == base.StatusOK
|
||||
@@ -424,9 +365,39 @@ func (a Attacker) validateStream(ctx context.Context, stream cameradar.Stream, e
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (a Attacker) describeWithRetry(ctx context.Context, client *gortsplib.Client, u *base.URL, urlStr string) (*description.Session, *base.Response, error) {
|
||||
var (
|
||||
desc *description.Session
|
||||
res *base.Response
|
||||
err error
|
||||
)
|
||||
for range 5 {
|
||||
desc, res, err = client.Describe(u)
|
||||
if err == nil {
|
||||
return desc, res, nil
|
||||
}
|
||||
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) && badStatus.Code == base.StatusServiceUnavailable {
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d (retrying)", urlStr, badStatus.Code))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil, ctx.Err()
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("describe retries exhausted for %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
func (a Attacker) handleDescribeError(stream cameradar.Stream, urlStr string, err error) (cameradar.Stream, error) {
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) && badStatus.Code == base.StatusServiceUnavailable {
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, badStatus.Code))
|
||||
a.reporter.Progress(cameradar.StepValidateStreams, fmt.Sprintf("Stream unavailable for %s:%d (RTSP %d)",
|
||||
stream.Address.String(),
|
||||
stream.Port,
|
||||
@@ -436,6 +407,8 @@ func (a Attacker) handleDescribeError(stream cameradar.Stream, urlStr string, er
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
a.reporter.Debug(cameradar.StepValidateStreams, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > error: %v", urlStr, err))
|
||||
|
||||
return stream, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ func TestAttacker_Attack_ReturnsErrorWhenRouteMissing(t *testing.T) {
|
||||
|
||||
got, err := attacker.Attack(t.Context(), streams)
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, "detecting authentication methods")
|
||||
assert.ErrorContains(t, err, "validating streams")
|
||||
require.Len(t, got, 1)
|
||||
assert.False(t, got[0].RouteFound)
|
||||
}
|
||||
@@ -304,7 +304,7 @@ func TestAttacker_Attack_AllowsDummyRoute(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
assert.True(t, got[0].RouteFound)
|
||||
assert.Equal(t, []string{"/"}, got[0].Routes)
|
||||
assert.Equal(t, []string{""}, got[0].Routes)
|
||||
assert.True(t, got[0].Available)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package attack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Ullaakut/cameradar/v6"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||
)
|
||||
|
||||
func (a Attacker) detectAuthMethods(ctx context.Context, targets []cameradar.Stream) ([]cameradar.Stream, error) {
|
||||
streams, err := runParallel(ctx, targets, a.detectAuthMethod)
|
||||
if err != nil {
|
||||
return streams, err
|
||||
}
|
||||
|
||||
for i := range streams {
|
||||
a.reporter.Progress(cameradar.StepDetectAuth, cameradar.ProgressTickMessage())
|
||||
|
||||
var authMethod string
|
||||
switch streams[i].AuthenticationType {
|
||||
case cameradar.AuthNone:
|
||||
authMethod = "no"
|
||||
case cameradar.AuthBasic:
|
||||
authMethod = "basic"
|
||||
case cameradar.AuthDigest:
|
||||
authMethod = "digest"
|
||||
case cameradar.AuthUnknown:
|
||||
authMethod = "unknown"
|
||||
default:
|
||||
authMethod = fmt.Sprintf("unknown (%d)", streams[i].AuthenticationType)
|
||||
}
|
||||
|
||||
a.reporter.Progress(cameradar.StepDetectAuth, fmt.Sprintf("Detected %s authentication for %s:%d", authMethod, streams[i].Address.String(), streams[i].Port))
|
||||
}
|
||||
|
||||
return streams, nil
|
||||
}
|
||||
|
||||
func (a Attacker) detectAuthMethod(ctx context.Context, stream cameradar.Stream) (cameradar.Stream, error) {
|
||||
if ctx.Err() != nil {
|
||||
return stream, ctx.Err()
|
||||
}
|
||||
u, urlStr, err := buildRTSPURL(stream, stream.Route(), "", "")
|
||||
if err != nil {
|
||||
return stream, fmt.Errorf("building rtsp url: %w", err)
|
||||
}
|
||||
|
||||
statusCode, headers, err := a.probeDescribeHeaders(ctx, u, urlStr)
|
||||
if err != nil {
|
||||
a.reporter.Debug(cameradar.StepDetectAuth, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > error: %v", urlStr, err))
|
||||
stream.AuthenticationType = cameradar.AuthUnknown
|
||||
return stream, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
a.reporter.Debug(cameradar.StepDetectAuth, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, statusCode))
|
||||
values := headerValues(headers, "WWW-Authenticate")
|
||||
switch statusCode {
|
||||
case base.StatusOK:
|
||||
stream.AuthenticationType = cameradar.AuthNone
|
||||
case base.StatusUnauthorized:
|
||||
stream.AuthenticationType = authTypeFromHeaders(values)
|
||||
default:
|
||||
stream.AuthenticationType = cameradar.AuthUnknown
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package attack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Ullaakut/cameradar/v6"
|
||||
"github.com/Ullaakut/cameradar/v6/internal/ui"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/base"
|
||||
"github.com/bluenviron/gortsplib/v5/pkg/headers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testDictionary struct {
|
||||
routes []string
|
||||
usernames []string
|
||||
passwords []string
|
||||
}
|
||||
|
||||
func (d testDictionary) Routes() []string {
|
||||
return d.routes
|
||||
}
|
||||
|
||||
func (d testDictionary) Usernames() []string {
|
||||
return d.usernames
|
||||
}
|
||||
|
||||
func (d testDictionary) Passwords() []string {
|
||||
return d.passwords
|
||||
}
|
||||
|
||||
func TestAuthTypeFromHeaders(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
values base.HeaderValue
|
||||
want cameradar.AuthType
|
||||
}{
|
||||
{
|
||||
name: "digest wins over basic",
|
||||
values: base.HeaderValue{
|
||||
headers.Authenticate{Method: headers.AuthMethodBasic, Realm: "cam"}.Marshal()[0],
|
||||
headers.Authenticate{Method: headers.AuthMethodDigest, Realm: "cam", Nonce: "nonce"}.Marshal()[0],
|
||||
},
|
||||
want: cameradar.AuthDigest,
|
||||
},
|
||||
{
|
||||
name: "basic auth",
|
||||
values: headers.Authenticate{Method: headers.AuthMethodBasic, Realm: "cam"}.Marshal(),
|
||||
want: cameradar.AuthBasic,
|
||||
},
|
||||
{
|
||||
name: "digest auth",
|
||||
values: headers.Authenticate{Method: headers.AuthMethodDigest, Realm: "cam", Nonce: "nonce"}.Marshal(),
|
||||
want: cameradar.AuthDigest,
|
||||
},
|
||||
{
|
||||
name: "unknown with empty values",
|
||||
values: nil,
|
||||
want: cameradar.AuthUnknown,
|
||||
},
|
||||
{
|
||||
name: "unknown with unsupported header",
|
||||
values: base.HeaderValue{"Bearer abc"},
|
||||
want: cameradar.AuthUnknown,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.want, authTypeFromHeaders(test.values))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectAuthMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
statusCode base.StatusCode
|
||||
headers base.Header
|
||||
want cameradar.AuthType
|
||||
}{
|
||||
{
|
||||
name: "no auth when status ok",
|
||||
statusCode: base.StatusOK,
|
||||
headers: base.Header{
|
||||
"WWW-Authenticate": headers.Authenticate{Method: headers.AuthMethodBasic, Realm: "cam"}.Marshal(),
|
||||
},
|
||||
want: cameradar.AuthNone,
|
||||
},
|
||||
{
|
||||
name: "basic auth on unauthorized",
|
||||
statusCode: base.StatusUnauthorized,
|
||||
headers: base.Header{
|
||||
"WWW-Authenticate": headers.Authenticate{Method: headers.AuthMethodBasic, Realm: "cam"}.Marshal(),
|
||||
},
|
||||
want: cameradar.AuthBasic,
|
||||
},
|
||||
{
|
||||
name: "digest auth on unauthorized",
|
||||
statusCode: base.StatusUnauthorized,
|
||||
headers: base.Header{
|
||||
"WWW-Authenticate": headers.Authenticate{Method: headers.AuthMethodDigest, Realm: "cam", Nonce: "nonce"}.Marshal(),
|
||||
},
|
||||
want: cameradar.AuthDigest,
|
||||
},
|
||||
{
|
||||
name: "unknown auth on unauthorized without www-authenticate",
|
||||
statusCode: base.StatusUnauthorized,
|
||||
headers: nil,
|
||||
want: cameradar.AuthUnknown,
|
||||
},
|
||||
{
|
||||
name: "unknown auth on other status",
|
||||
statusCode: base.StatusNotFound,
|
||||
headers: nil,
|
||||
want: cameradar.AuthUnknown,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
addr, port := startRTSPProbeServer(t, test.statusCode, test.headers)
|
||||
|
||||
attacker, err := New(testDictionary{}, 0, time.Second, ui.NopReporter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
stream := cameradar.Stream{
|
||||
Address: addr,
|
||||
Port: port,
|
||||
}
|
||||
|
||||
got, err := attacker.detectAuthMethod(t.Context(), stream)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.want, got.AuthenticationType)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func startRTSPProbeServer(t *testing.T, statusCode base.StatusCode, headers base.Header) (netip.Addr, uint16) {
|
||||
t.Helper()
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_ = conn.SetDeadline(time.Now().Add(time.Second))
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(line) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
statusText := statusTextFromCode(statusCode)
|
||||
|
||||
var builder strings.Builder
|
||||
_, _ = fmt.Fprintf(&builder, "RTSP/1.0 %d %s\r\n", statusCode, statusText)
|
||||
builder.WriteString("CSeq: 1\r\n")
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
_, _ = fmt.Fprintf(&builder, "%s: %s\r\n", key, value)
|
||||
}
|
||||
}
|
||||
builder.WriteString("Content-Length: 0\r\n\r\n")
|
||||
|
||||
_, _ = conn.Write([]byte(builder.String()))
|
||||
}()
|
||||
|
||||
tcpAddr, ok := listener.Addr().(*net.TCPAddr)
|
||||
require.True(t, ok)
|
||||
|
||||
return netip.MustParseAddr("127.0.0.1"), uint16(tcpAddr.Port)
|
||||
}
|
||||
|
||||
func statusTextFromCode(code base.StatusCode) string {
|
||||
switch code {
|
||||
case base.StatusOK:
|
||||
return "OK"
|
||||
case base.StatusUnauthorized:
|
||||
return "Unauthorized"
|
||||
case base.StatusNotFound:
|
||||
return "Not Found"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
+87
-6
@@ -1,10 +1,16 @@
|
||||
package attack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Ullaakut/cameradar/v6"
|
||||
"github.com/bluenviron/gortsplib/v5"
|
||||
@@ -39,7 +45,7 @@ func (a Attacker) describeStatus(u *base.URL) (base.StatusCode, error) {
|
||||
_, res, err := client.Describe(u)
|
||||
if err != nil {
|
||||
var badStatus liberrors.ErrClientBadStatusCode
|
||||
if errors.As(err, &badStatus) && res != nil {
|
||||
if errors.As(err, &badStatus) {
|
||||
return badStatus.Code, nil
|
||||
}
|
||||
return 0, err
|
||||
@@ -51,9 +57,69 @@ func (a Attacker) describeStatus(u *base.URL) (base.StatusCode, error) {
|
||||
return res.StatusCode, nil
|
||||
}
|
||||
|
||||
// probeDescribeHeaders performs a manual DESCRIBE request and returns the status code and headers.
|
||||
//
|
||||
// NOTE: We do not use gortsplib here because it does not expose response headers when the status code is 401 Unauthorized,
|
||||
// which is exactly what we need in order to detect authentication methods.
|
||||
func (a Attacker) probeDescribeHeaders(ctx context.Context, u *base.URL, urlStr string) (base.StatusCode, base.Header, error) {
|
||||
dialer := &net.Dialer{Timeout: a.timeout}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", u.Host)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
deadline = time.Now().Add(a.timeout)
|
||||
}
|
||||
|
||||
err = conn.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
request := fmt.Sprintf(
|
||||
"DESCRIBE %s RTSP/1.0\r\nCSeq: 1\r\nUser-Agent: cameradar\r\nAccept: application/sdp\r\nHost: %s\r\n\r\n",
|
||||
urlStr,
|
||||
u.Host,
|
||||
)
|
||||
_, err = conn.Write([]byte(request))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
reader := textproto.NewReader(bufio.NewReader(conn))
|
||||
statusLine, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
fields := strings.Fields(statusLine)
|
||||
if len(fields) < 2 {
|
||||
return 0, nil, fmt.Errorf("invalid RTSP status line: %q", statusLine)
|
||||
}
|
||||
|
||||
code, err := strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("parsing RTSP status code %q: %w", fields[1], err)
|
||||
}
|
||||
|
||||
mimeHeader, err := reader.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
headers := make(base.Header)
|
||||
for key, values := range mimeHeader {
|
||||
headers[key] = append(base.HeaderValue(nil), values...)
|
||||
}
|
||||
|
||||
return base.StatusCode(code), headers, nil
|
||||
}
|
||||
|
||||
func authTypeFromHeaders(values base.HeaderValue) cameradar.AuthType {
|
||||
if len(values) == 0 {
|
||||
return cameradar.AuthNone
|
||||
return cameradar.AuthUnknown
|
||||
}
|
||||
|
||||
var hasBasic bool
|
||||
@@ -63,6 +129,9 @@ func authTypeFromHeaders(values base.HeaderValue) cameradar.AuthType {
|
||||
var authHeader headers.Authenticate
|
||||
err := authHeader.Unmarshal(base.HeaderValue{value})
|
||||
if err != nil {
|
||||
lower := strings.ToLower(value)
|
||||
hasDigest = hasDigest || strings.Contains(lower, "digest")
|
||||
hasBasic = hasBasic || strings.Contains(lower, "basic")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -80,14 +149,26 @@ func authTypeFromHeaders(values base.HeaderValue) cameradar.AuthType {
|
||||
if hasBasic {
|
||||
return cameradar.AuthBasic
|
||||
}
|
||||
return cameradar.AuthType(-1)
|
||||
return cameradar.AuthUnknown
|
||||
}
|
||||
|
||||
func headerValues(header base.Header, name string) base.HeaderValue {
|
||||
if header == nil {
|
||||
return nil
|
||||
}
|
||||
for key, values := range header {
|
||||
if strings.EqualFold(key, name) {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildRTSPURL(stream cameradar.Stream, route, username, password string) (*base.URL, string, error) {
|
||||
host := net.JoinHostPort(stream.Address.String(), strconv.Itoa(int(stream.Port)))
|
||||
path := "/" + route
|
||||
if route == "" {
|
||||
path = "/"
|
||||
path := strings.TrimSpace(route)
|
||||
if path != "" && !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
u := &url.URL{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/live/ch01_0
|
||||
|
||||
live/ch01_0
|
||||
0/1:1/main
|
||||
0/usrnm:pwd/main
|
||||
0/video1
|
||||
|
||||
Reference in New Issue
Block a user