Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

144 lines
3.6 KiB

package core
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
)
func sha256Base64(in string) string {
h := sha256.New()
h.Write([]byte(in))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func checkCredential(right string, guess string) bool {
if strings.HasPrefix(right, "sha256:") {
return right[len("sha256:"):] == sha256Base64(guess)
}
return right == guess
}
func doExternalAuthentication(
ur string,
accessRequest defs.PathAccessRequest,
) error {
enc, _ := json.Marshal(struct {
IP string `json:"ip"`
User string `json:"user"`
Password string `json:"password"`
Path string `json:"path"`
Protocol string `json:"protocol"`
ID *uuid.UUID `json:"id"`
Action string `json:"action"`
Query string `json:"query"`
}{
IP: accessRequest.IP.String(),
User: accessRequest.User,
Password: accessRequest.Pass,
Path: accessRequest.Name,
Protocol: string(accessRequest.Proto),
ID: accessRequest.ID,
Action: func() string {
if accessRequest.Publish {
return "publish"
}
return "read"
}(),
Query: accessRequest.Query,
})
res, err := http.Post(ur, "application/json", bytes.NewReader(enc))
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode > 299 {
if resBody, err := io.ReadAll(res.Body); err == nil && len(resBody) != 0 {
return fmt.Errorf("server replied with code %d: %s", res.StatusCode, string(resBody))
}
return fmt.Errorf("server replied with code %d", res.StatusCode)
}
return nil
}
func doAuthentication(
externalAuthenticationURL string,
rtspAuthMethods conf.AuthMethods,
pathConf *conf.Path,
accessRequest defs.PathAccessRequest,
) error {
var rtspAuth headers.Authorization
if accessRequest.RTSPRequest != nil {
err := rtspAuth.Unmarshal(accessRequest.RTSPRequest.Header["Authorization"])
if err == nil && rtspAuth.Method == headers.AuthBasic {
accessRequest.User = rtspAuth.BasicUser
accessRequest.Pass = rtspAuth.BasicPass
}
}
if externalAuthenticationURL != "" {
err := doExternalAuthentication(
externalAuthenticationURL,
accessRequest,
)
if err != nil {
return defs.AuthenticationError{Message: fmt.Sprintf("external authentication failed: %s", err)}
}
}
var pathIPs conf.IPsOrCIDRs
var pathUser string
var pathPass string
if accessRequest.Publish {
pathIPs = pathConf.PublishIPs
pathUser = string(pathConf.PublishUser)
pathPass = string(pathConf.PublishPass)
} else {
pathIPs = pathConf.ReadIPs
pathUser = string(pathConf.ReadUser)
pathPass = string(pathConf.ReadPass)
}
if pathIPs != nil {
if !ipEqualOrInRange(accessRequest.IP, pathIPs) {
return defs.AuthenticationError{Message: fmt.Sprintf("IP %s not allowed", accessRequest.IP)}
}
}
if pathUser != "" {
if accessRequest.RTSPRequest != nil && rtspAuth.Method == headers.AuthDigest {
err := auth.Validate(
accessRequest.RTSPRequest,
pathUser,
pathPass,
accessRequest.RTSPBaseURL,
rtspAuthMethods,
"IPCAM",
accessRequest.RTSPNonce)
if err != nil {
return defs.AuthenticationError{Message: err.Error()}
}
} else if !checkCredential(pathUser, accessRequest.User) ||
!checkCredential(pathPass, accessRequest.Pass) {
return defs.AuthenticationError{Message: "invalid credentials"}
}
}
return nil
}