2021-07-24 13:55:42 +00:00
|
|
|
package core
|
2019-12-31 12:48:17 +00:00
|
|
|
|
|
|
|
import (
|
2020-05-10 20:56:46 +00:00
|
|
|
"errors"
|
2021-12-22 18:13:56 +00:00
|
|
|
"fmt"
|
2019-12-31 12:48:17 +00:00
|
|
|
"net"
|
2020-06-21 09:30:30 +00:00
|
|
|
"time"
|
2019-12-31 12:48:17 +00:00
|
|
|
|
2020-01-20 09:21:05 +00:00
|
|
|
"github.com/aler9/gortsplib"
|
2020-11-15 16:56:54 +00:00
|
|
|
"github.com/aler9/gortsplib/pkg/auth"
|
|
|
|
"github.com/aler9/gortsplib/pkg/base"
|
|
|
|
"github.com/aler9/gortsplib/pkg/headers"
|
2020-10-13 22:02:55 +00:00
|
|
|
|
2021-09-26 21:06:40 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/conf"
|
2020-11-01 21:56:56 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
2020-12-08 11:21:06 +00:00
|
|
|
"github.com/aler9/rtsp-simple-server/internal/logger"
|
2019-12-31 12:48:17 +00:00
|
|
|
)
|
|
|
|
|
2020-06-21 09:30:30 +00:00
|
|
|
const (
|
2021-07-24 13:55:42 +00:00
|
|
|
rtspConnPauseAfterAuthError = 2 * time.Second
|
2020-06-21 09:30:30 +00:00
|
|
|
)
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
type rtspConnParent interface {
|
2021-10-27 19:01:00 +00:00
|
|
|
log(logger.Level, string, ...interface{})
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
type rtspConn struct {
|
2022-11-09 17:31:31 +00:00
|
|
|
id string
|
2021-12-22 18:13:56 +00:00
|
|
|
externalAuthenticationURL string
|
|
|
|
rtspAddress string
|
|
|
|
authMethods []headers.AuthMethod
|
|
|
|
readTimeout conf.StringDuration
|
|
|
|
runOnConnect string
|
|
|
|
runOnConnectRestart bool
|
|
|
|
externalCmdPool *externalcmd.Pool
|
|
|
|
pathManager *pathManager
|
|
|
|
conn *gortsplib.ServerConn
|
|
|
|
parent rtspConnParent
|
2020-10-19 20:17:48 +00:00
|
|
|
|
2022-11-09 17:31:31 +00:00
|
|
|
created time.Time
|
2021-05-07 21:07:31 +00:00
|
|
|
onConnectCmd *externalcmd.Cmd
|
2021-03-22 20:40:07 +00:00
|
|
|
authUser string
|
|
|
|
authPass string
|
|
|
|
authValidator *auth.Validator
|
|
|
|
authFailures int
|
2019-12-31 12:48:17 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
func newRTSPConn(
|
2022-11-09 17:31:31 +00:00
|
|
|
id string,
|
2021-12-22 18:13:56 +00:00
|
|
|
externalAuthenticationURL string,
|
2021-04-24 16:25:19 +00:00
|
|
|
rtspAddress string,
|
2021-08-01 14:56:53 +00:00
|
|
|
authMethods []headers.AuthMethod,
|
2021-09-26 21:06:40 +00:00
|
|
|
readTimeout conf.StringDuration,
|
2020-10-19 20:17:48 +00:00
|
|
|
runOnConnect string,
|
2020-10-31 15:36:09 +00:00
|
|
|
runOnConnectRestart bool,
|
2021-12-22 18:13:56 +00:00
|
|
|
externalCmdPool *externalcmd.Pool,
|
2021-07-30 18:13:17 +00:00
|
|
|
pathManager *pathManager,
|
2020-12-06 17:01:10 +00:00
|
|
|
conn *gortsplib.ServerConn,
|
2022-04-07 10:50:35 +00:00
|
|
|
parent rtspConnParent,
|
|
|
|
) *rtspConn {
|
2021-07-24 13:55:42 +00:00
|
|
|
c := &rtspConn{
|
2022-11-09 17:31:31 +00:00
|
|
|
id: id,
|
2021-12-22 18:13:56 +00:00
|
|
|
externalAuthenticationURL: externalAuthenticationURL,
|
|
|
|
rtspAddress: rtspAddress,
|
|
|
|
authMethods: authMethods,
|
|
|
|
readTimeout: readTimeout,
|
|
|
|
runOnConnect: runOnConnect,
|
|
|
|
runOnConnectRestart: runOnConnectRestart,
|
|
|
|
externalCmdPool: externalCmdPool,
|
|
|
|
pathManager: pathManager,
|
|
|
|
conn: conn,
|
|
|
|
parent: parent,
|
2022-11-09 17:31:31 +00:00
|
|
|
created: time.Now(),
|
2019-12-31 12:48:17 +00:00
|
|
|
}
|
|
|
|
|
2021-05-09 12:41:18 +00:00
|
|
|
c.log(logger.Info, "opened")
|
2020-10-14 17:35:21 +00:00
|
|
|
|
2021-05-07 21:07:31 +00:00
|
|
|
if c.runOnConnect != "" {
|
2021-10-03 13:46:06 +00:00
|
|
|
c.log(logger.Info, "runOnConnect command started")
|
2021-05-07 21:07:31 +00:00
|
|
|
_, port, _ := net.SplitHostPort(c.rtspAddress)
|
2021-12-21 11:39:32 +00:00
|
|
|
c.onConnectCmd = externalcmd.NewCmd(
|
|
|
|
c.externalCmdPool,
|
2021-12-08 19:50:09 +00:00
|
|
|
c.runOnConnect,
|
|
|
|
c.runOnConnectRestart,
|
|
|
|
externalcmd.Environment{
|
|
|
|
"RTSP_PATH": "",
|
|
|
|
"RTSP_PORT": port,
|
2021-12-08 20:23:45 +00:00
|
|
|
},
|
|
|
|
func(co int) {
|
|
|
|
c.log(logger.Info, "runOnInit command exited with code %d", co)
|
2021-12-08 19:50:09 +00:00
|
|
|
})
|
2021-05-07 21:07:31 +00:00
|
|
|
}
|
2021-01-31 20:42:27 +00:00
|
|
|
|
2020-10-19 20:17:48 +00:00
|
|
|
return c
|
2019-12-31 12:48:17 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
func (c *rtspConn) log(level logger.Level, format string, args ...interface{}) {
|
2021-10-27 19:01:00 +00:00
|
|
|
c.parent.log(level, "[conn %v] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr()}, args...)...)
|
2020-10-19 20:17:48 +00:00
|
|
|
}
|
2020-07-29 21:30:42 +00:00
|
|
|
|
2021-05-07 21:07:31 +00:00
|
|
|
// Conn returns the RTSP connection.
|
2021-07-24 13:55:42 +00:00
|
|
|
func (c *rtspConn) Conn() *gortsplib.ServerConn {
|
2021-05-07 21:07:31 +00:00
|
|
|
return c.conn
|
|
|
|
}
|
|
|
|
|
2022-11-09 17:31:31 +00:00
|
|
|
func (c *rtspConn) remoteAddr() net.Addr {
|
|
|
|
return c.conn.NetConn().RemoteAddr()
|
|
|
|
}
|
|
|
|
|
2021-07-24 13:55:42 +00:00
|
|
|
func (c *rtspConn) ip() net.IP {
|
2020-05-03 21:26:41 +00:00
|
|
|
return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).IP
|
|
|
|
}
|
|
|
|
|
2021-12-22 18:13:56 +00:00
|
|
|
func (c *rtspConn) authenticate(
|
|
|
|
pathName string,
|
2022-06-21 11:41:15 +00:00
|
|
|
pathIPs []fmt.Stringer,
|
2021-09-27 13:45:51 +00:00
|
|
|
pathUser conf.Credential,
|
|
|
|
pathPass conf.Credential,
|
2022-08-22 09:24:21 +00:00
|
|
|
isPublishing bool,
|
2021-05-07 21:07:31 +00:00
|
|
|
req *base.Request,
|
2022-01-11 03:21:18 +00:00
|
|
|
query string,
|
2021-05-07 21:07:31 +00:00
|
|
|
) error {
|
2021-12-22 18:13:56 +00:00
|
|
|
if c.externalAuthenticationURL != "" {
|
|
|
|
username := ""
|
|
|
|
password := ""
|
|
|
|
|
|
|
|
var auth headers.Authorization
|
2022-06-27 15:47:07 +00:00
|
|
|
err := auth.Unmarshal(req.Header["Authorization"])
|
2021-12-22 18:13:56 +00:00
|
|
|
if err == nil && auth.Method == headers.AuthBasic {
|
|
|
|
username = auth.BasicUser
|
|
|
|
password = auth.BasicPass
|
|
|
|
}
|
|
|
|
|
|
|
|
err = externalAuth(
|
|
|
|
c.externalAuthenticationURL,
|
|
|
|
c.ip().String(),
|
|
|
|
username,
|
|
|
|
password,
|
|
|
|
pathName,
|
2022-08-22 09:24:21 +00:00
|
|
|
isPublishing,
|
2022-01-11 03:21:18 +00:00
|
|
|
query)
|
2021-12-22 18:13:56 +00:00
|
|
|
if err != nil {
|
|
|
|
c.authFailures++
|
|
|
|
|
|
|
|
// VLC with login prompt sends 4 requests:
|
|
|
|
// 1) without credentials
|
|
|
|
// 2) with password but without username
|
|
|
|
// 3) without credentials
|
|
|
|
// 4) with password and username
|
|
|
|
// therefore we must allow up to 3 failures
|
|
|
|
if c.authFailures > 3 {
|
|
|
|
return pathErrAuthCritical{
|
2022-01-14 22:42:41 +00:00
|
|
|
message: "unauthorized: " + err.Error(),
|
|
|
|
response: &base.Response{
|
2021-12-22 18:13:56 +00:00
|
|
|
StatusCode: base.StatusUnauthorized,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
v := "IPCAM"
|
|
|
|
return pathErrAuthNotCritical{
|
2022-01-14 22:42:41 +00:00
|
|
|
message: "unauthorized: " + err.Error(),
|
|
|
|
response: &base.Response{
|
2021-12-22 18:13:56 +00:00
|
|
|
StatusCode: base.StatusUnauthorized,
|
|
|
|
Header: base.Header{
|
|
|
|
"WWW-Authenticate": headers.Authenticate{
|
|
|
|
Method: headers.AuthBasic,
|
|
|
|
Realm: &v,
|
2022-06-27 15:47:07 +00:00
|
|
|
}.Marshal(),
|
2021-12-22 18:13:56 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2021-05-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 18:13:56 +00:00
|
|
|
if pathIPs != nil {
|
|
|
|
ip := c.ip()
|
|
|
|
if !ipEqualOrInRange(ip, pathIPs) {
|
2021-07-31 18:46:06 +00:00
|
|
|
return pathErrAuthCritical{
|
2022-01-14 22:42:41 +00:00
|
|
|
message: fmt.Sprintf("IP '%s' not allowed", ip),
|
|
|
|
response: &base.Response{
|
2020-12-13 12:58:56 +00:00
|
|
|
StatusCode: base.StatusUnauthorized,
|
2021-05-07 21:07:31 +00:00
|
|
|
},
|
2020-12-13 12:58:56 +00:00
|
|
|
}
|
2021-05-07 21:07:31 +00:00
|
|
|
}
|
2021-12-22 18:13:56 +00:00
|
|
|
}
|
2020-12-13 12:58:56 +00:00
|
|
|
|
2021-12-22 18:13:56 +00:00
|
|
|
if pathUser != "" {
|
|
|
|
// reset authValidator every time the credentials change
|
|
|
|
if c.authValidator == nil || c.authUser != string(pathUser) || c.authPass != string(pathPass) {
|
|
|
|
c.authUser = string(pathUser)
|
|
|
|
c.authPass = string(pathPass)
|
|
|
|
c.authValidator = auth.NewValidator(string(pathUser), string(pathPass), c.authMethods)
|
2020-12-13 12:58:56 +00:00
|
|
|
}
|
2021-05-07 21:07:31 +00:00
|
|
|
|
2021-12-22 18:13:56 +00:00
|
|
|
err := c.authValidator.ValidateRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
c.authFailures++
|
|
|
|
|
|
|
|
// VLC with login prompt sends 4 requests:
|
|
|
|
// 1) without credentials
|
|
|
|
// 2) with password but without username
|
|
|
|
// 3) without credentials
|
|
|
|
// 4) with password and username
|
|
|
|
// therefore we must allow up to 3 failures
|
|
|
|
if c.authFailures > 3 {
|
|
|
|
return pathErrAuthCritical{
|
2022-01-14 22:42:41 +00:00
|
|
|
message: "unauthorized: " + err.Error(),
|
|
|
|
response: &base.Response{
|
2021-12-22 18:13:56 +00:00
|
|
|
StatusCode: base.StatusUnauthorized,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pathErrAuthNotCritical{
|
2022-01-14 22:42:41 +00:00
|
|
|
response: &base.Response{
|
2021-12-22 18:13:56 +00:00
|
|
|
StatusCode: base.StatusUnauthorized,
|
|
|
|
Header: base.Header{
|
|
|
|
"WWW-Authenticate": c.authValidator.Header(),
|
|
|
|
},
|
2021-05-16 14:25:22 +00:00
|
|
|
},
|
2021-12-22 18:13:56 +00:00
|
|
|
}
|
2021-05-16 14:25:22 +00:00
|
|
|
}
|
2020-12-13 12:58:56 +00:00
|
|
|
|
2021-12-22 18:13:56 +00:00
|
|
|
// login successful, reset authFailures
|
|
|
|
c.authFailures = 0
|
|
|
|
}
|
2020-12-13 12:58:56 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2021-07-30 18:13:17 +00:00
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
// onClose is called by rtspServer.
|
|
|
|
func (c *rtspConn) onClose(err error) {
|
2021-10-27 17:49:43 +00:00
|
|
|
c.log(logger.Info, "closed (%v)", err)
|
2021-08-12 08:50:29 +00:00
|
|
|
|
|
|
|
if c.onConnectCmd != nil {
|
|
|
|
c.onConnectCmd.Close()
|
2021-10-03 13:46:06 +00:00
|
|
|
c.log(logger.Info, "runOnConnect command stopped")
|
2021-08-12 08:50:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
// onRequest is called by rtspServer.
|
|
|
|
func (c *rtspConn) onRequest(req *base.Request) {
|
2021-07-30 18:13:17 +00:00
|
|
|
c.log(logger.Debug, "[c->s] %v", req)
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnResponse is called by rtspServer.
|
|
|
|
func (c *rtspConn) OnResponse(res *base.Response) {
|
|
|
|
c.log(logger.Debug, "[s->c] %v", res)
|
|
|
|
}
|
|
|
|
|
2021-10-27 19:01:00 +00:00
|
|
|
// onDescribe is called by rtspServer.
|
|
|
|
func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
2021-09-09 21:05:54 +00:00
|
|
|
) (*base.Response, *gortsplib.ServerStream, error) {
|
2022-08-04 19:07:17 +00:00
|
|
|
res := c.pathManager.describe(pathDescribeReq{
|
2022-01-14 22:42:41 +00:00
|
|
|
pathName: ctx.Path,
|
2022-02-19 22:06:24 +00:00
|
|
|
url: ctx.Request.URL,
|
2022-01-14 22:42:41 +00:00
|
|
|
authenticate: func(
|
2022-06-21 11:41:15 +00:00
|
|
|
pathIPs []fmt.Stringer,
|
2021-12-22 18:13:56 +00:00
|
|
|
pathUser conf.Credential,
|
2022-04-07 10:50:35 +00:00
|
|
|
pathPass conf.Credential,
|
|
|
|
) error {
|
2022-08-22 09:24:21 +00:00
|
|
|
return c.authenticate(ctx.Path, pathIPs, pathUser, pathPass, false, ctx.Request, ctx.Query)
|
2021-07-30 18:13:17 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
if res.err != nil {
|
|
|
|
switch terr := res.err.(type) {
|
2021-07-31 18:46:06 +00:00
|
|
|
case pathErrAuthNotCritical:
|
2022-01-14 22:42:41 +00:00
|
|
|
c.log(logger.Debug, "non-critical authentication error: %s", terr.message)
|
|
|
|
return terr.response, nil, nil
|
2021-07-30 18:13:17 +00:00
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
case pathErrAuthCritical:
|
2021-07-30 18:13:17 +00:00
|
|
|
// wait some seconds to stop brute force attacks
|
|
|
|
<-time.After(rtspConnPauseAfterAuthError)
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
return terr.response, nil, errors.New(terr.message)
|
2021-07-30 18:13:17 +00:00
|
|
|
|
2021-07-31 18:46:06 +00:00
|
|
|
case pathErrNoOnePublishing:
|
2021-07-30 18:13:17 +00:00
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusNotFound,
|
2022-01-14 22:42:41 +00:00
|
|
|
}, nil, res.err
|
2021-07-30 18:13:17 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusBadRequest,
|
2022-01-14 22:42:41 +00:00
|
|
|
}, nil, res.err
|
2021-07-30 18:13:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-14 22:42:41 +00:00
|
|
|
if res.redirect != "" {
|
2021-07-30 18:13:17 +00:00
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusMovedPermanently,
|
|
|
|
Header: base.Header{
|
2022-01-14 22:42:41 +00:00
|
|
|
"Location": base.HeaderValue{res.redirect},
|
2021-07-30 18:13:17 +00:00
|
|
|
},
|
|
|
|
}, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &base.Response{
|
|
|
|
StatusCode: base.StatusOK,
|
2022-01-14 22:42:41 +00:00
|
|
|
}, res.stream.rtspStream, nil
|
2021-07-30 18:13:17 +00:00
|
|
|
}
|