mirror of
https://github.com/bluenviron/mediamtx.git
synced 2025-12-25 04:22:00 -08:00
fix authenticating with vlc and login prompt
This commit is contained in:
parent
0c8745009e
commit
4c6f929c02
5 changed files with 130 additions and 108 deletions
2
go.mod
2
go.mod
|
|
@ -5,7 +5,7 @@ go 1.13
|
|||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
|
||||
github.com/aler9/gortsplib v0.0.0-20200709205811-04b0a6eabe37
|
||||
github.com/aler9/gortsplib v0.0.0-20200710091324-fb7d7b008e68
|
||||
github.com/stretchr/testify v1.4.0
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -2,8 +2,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
|
|||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/aler9/gortsplib v0.0.0-20200709205811-04b0a6eabe37 h1:O92YFUvPXejUg08pDIQTI8MydvHZBnCnSsOMxP2Q828=
|
||||
github.com/aler9/gortsplib v0.0.0-20200709205811-04b0a6eabe37/go.mod h1:sL64nUkmrTVhlT/GCaxRXyI2Xk7m8XSdw5Uv8xKGPdc=
|
||||
github.com/aler9/gortsplib v0.0.0-20200710091324-fb7d7b008e68 h1:apyYugiG/luHl0Xyc2xJtGkL2HbF1umxjCNP2sX9iyw=
|
||||
github.com/aler9/gortsplib v0.0.0-20200710091324-fb7d7b008e68/go.mod h1:sL64nUkmrTVhlT/GCaxRXyI2Xk7m8XSdw5Uv8xKGPdc=
|
||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
|
|||
26
main.go
26
main.go
|
|
@ -192,16 +192,16 @@ type programEventTerminate struct{}
|
|||
func (programEventTerminate) isProgramEvent() {}
|
||||
|
||||
type ConfPath struct {
|
||||
Source string `yaml:"source"`
|
||||
SourceProtocol string `yaml:"sourceProtocol"`
|
||||
PublishUser string `yaml:"publishUser"`
|
||||
PublishPass string `yaml:"publishPass"`
|
||||
PublishIps []string `yaml:"publishIps"`
|
||||
publishIps []interface{}
|
||||
ReadUser string `yaml:"readUser"`
|
||||
ReadPass string `yaml:"readPass"`
|
||||
ReadIps []string `yaml:"readIps"`
|
||||
readIps []interface{}
|
||||
Source string `yaml:"source"`
|
||||
SourceProtocol string `yaml:"sourceProtocol"`
|
||||
PublishUser string `yaml:"publishUser"`
|
||||
PublishPass string `yaml:"publishPass"`
|
||||
PublishIps []string `yaml:"publishIps"`
|
||||
publishIpsParsed []interface{}
|
||||
ReadUser string `yaml:"readUser"`
|
||||
ReadPass string `yaml:"readPass"`
|
||||
ReadIps []string `yaml:"readIps"`
|
||||
readIpsParsed []interface{}
|
||||
}
|
||||
|
||||
type conf struct {
|
||||
|
|
@ -366,7 +366,7 @@ func newProgram(sargs []string, stdin io.Reader) (*program, error) {
|
|||
return nil, fmt.Errorf("publish password must be alphanumeric")
|
||||
}
|
||||
}
|
||||
pconf.publishIps, err = parseIpCidrList(pconf.PublishIps)
|
||||
pconf.publishIpsParsed, err = parseIpCidrList(pconf.PublishIps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -387,7 +387,7 @@ func newProgram(sargs []string, stdin io.Reader) (*program, error) {
|
|||
if pconf.ReadUser != "" && pconf.ReadPass == "" || pconf.ReadUser == "" && pconf.ReadPass != "" {
|
||||
return nil, fmt.Errorf("read username and password must be both filled")
|
||||
}
|
||||
pconf.readIps, err = parseIpCidrList(pconf.ReadIps)
|
||||
pconf.readIpsParsed, err = parseIpCidrList(pconf.ReadIps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -482,7 +482,7 @@ outer:
|
|||
// close all other clients that share the same path
|
||||
if pub.publisherIsReady() {
|
||||
for oc := range p.clients {
|
||||
if oc.path == evt.client.path {
|
||||
if oc != evt.client && oc.path == evt.client.path {
|
||||
go oc.close()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
194
server-client.go
194
server-client.go
|
|
@ -71,8 +71,10 @@ type serverClient struct {
|
|||
conn *gortsplib.ConnServer
|
||||
state clientState
|
||||
path string
|
||||
publishAuth *gortsplib.AuthServer
|
||||
readAuth *gortsplib.AuthServer
|
||||
authUser string
|
||||
authPass string
|
||||
authHelper *gortsplib.AuthServer
|
||||
authFailures int
|
||||
streamSdpText []byte // filled only if publisher
|
||||
streamSdpParsed *sdp.Message // filled only if publisher
|
||||
streamProtocol streamProtocol
|
||||
|
|
@ -86,8 +88,8 @@ type serverClient struct {
|
|||
writeBuf2 []byte
|
||||
writeCurBuf bool
|
||||
|
||||
writec chan *gortsplib.InterleavedFrame
|
||||
done chan struct{}
|
||||
writeChan chan *gortsplib.InterleavedFrame
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newServerClient(p *program, nconn net.Conn) *serverClient {
|
||||
|
|
@ -103,7 +105,7 @@ func newServerClient(p *program, nconn net.Conn) *serverClient {
|
|||
readBuf2: make([]byte, 0, 512*1024),
|
||||
writeBuf1: make([]byte, 2048),
|
||||
writeBuf2: make([]byte, 2048),
|
||||
writec: make(chan *gortsplib.InterleavedFrame),
|
||||
writeChan: make(chan *gortsplib.InterleavedFrame),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +166,7 @@ func (c *serverClient) run() {
|
|||
}
|
||||
|
||||
go func() {
|
||||
for range c.writec {
|
||||
for range c.writeChan {
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -182,9 +184,10 @@ func (c *serverClient) run() {
|
|||
c.p.events <- programEventClientClose{done, c}
|
||||
<-done
|
||||
|
||||
close(c.writec)
|
||||
close(c.writeChan)
|
||||
c.conn.NetConn().Close() // close socket in case it has not been closed yet
|
||||
|
||||
close(c.done)
|
||||
close(c.done) // close() never blocks
|
||||
}
|
||||
|
||||
func (c *serverClient) close() {
|
||||
|
|
@ -204,7 +207,7 @@ func (c *serverClient) writeFrame(channel uint8, inbuf []byte) {
|
|||
copy(buf, inbuf)
|
||||
c.writeCurBuf = !c.writeCurBuf
|
||||
|
||||
c.writec <- &gortsplib.InterleavedFrame{
|
||||
c.writeChan <- &gortsplib.InterleavedFrame{
|
||||
Channel: channel,
|
||||
Content: buf,
|
||||
}
|
||||
|
|
@ -224,79 +227,6 @@ func (c *serverClient) writeResError(req *gortsplib.Request, code gortsplib.Stat
|
|||
})
|
||||
}
|
||||
|
||||
var errAuthCritical = errors.New("auth critical")
|
||||
var errAuthNotCritical = errors.New("auth not critical")
|
||||
|
||||
func (c *serverClient) validateAuth(req *gortsplib.Request, user string, pass string, auth **gortsplib.AuthServer, ips []interface{}) error {
|
||||
err := func() error {
|
||||
if ips == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
connIp := c.conn.NetConn().LocalAddr().(*net.TCPAddr).IP
|
||||
|
||||
for _, item := range ips {
|
||||
switch titem := item.(type) {
|
||||
case net.IP:
|
||||
if titem.Equal(connIp) {
|
||||
return nil
|
||||
}
|
||||
|
||||
case *net.IPNet:
|
||||
if titem.Contains(connIp) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.log("ERR: ip '%s' not allowed", connIp)
|
||||
return errAuthCritical
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
if user == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
initialRequest := false
|
||||
if *auth == nil {
|
||||
initialRequest = true
|
||||
*auth = gortsplib.NewAuthServer(user, pass, nil)
|
||||
}
|
||||
|
||||
err := (*auth).ValidateHeader(req.Header["Authorization"], req.Method, req.Url)
|
||||
if err != nil {
|
||||
if !initialRequest {
|
||||
c.log("ERR: unauthorized: %s", err)
|
||||
}
|
||||
|
||||
c.conn.WriteResponse(&gortsplib.Response{
|
||||
StatusCode: gortsplib.StatusUnauthorized,
|
||||
Header: gortsplib.Header{
|
||||
"CSeq": req.Header["CSeq"],
|
||||
"WWW-Authenticate": (*auth).GenerateHeader(),
|
||||
},
|
||||
})
|
||||
|
||||
if !initialRequest {
|
||||
return errAuthCritical
|
||||
}
|
||||
|
||||
return errAuthNotCritical
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *serverClient) findConfForPath(path string) *ConfPath {
|
||||
if pconf, ok := c.p.conf.Paths[path]; ok {
|
||||
return pconf
|
||||
|
|
@ -309,6 +239,98 @@ func (c *serverClient) findConfForPath(path string) *ConfPath {
|
|||
return nil
|
||||
}
|
||||
|
||||
var errAuthCritical = errors.New("auth critical")
|
||||
var errAuthNotCritical = errors.New("auth not critical")
|
||||
|
||||
func (c *serverClient) authenticate(ips []interface{}, user string, pass string, req *gortsplib.Request) error {
|
||||
// validate ip
|
||||
err := func() error {
|
||||
if ips == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ip := c.ip()
|
||||
|
||||
for _, item := range ips {
|
||||
switch titem := item.(type) {
|
||||
case net.IP:
|
||||
if titem.Equal(ip) {
|
||||
return nil
|
||||
}
|
||||
|
||||
case *net.IPNet:
|
||||
if titem.Contains(ip) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.log("ERR: ip '%s' not allowed", ip)
|
||||
return errAuthCritical
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate credentials
|
||||
err = func() error {
|
||||
if user == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset authHelper every time the credentials change
|
||||
if c.authHelper == nil || c.authUser != user || c.authPass != pass {
|
||||
c.authUser = user
|
||||
c.authPass = pass
|
||||
c.authHelper = gortsplib.NewAuthServer(user, pass, nil)
|
||||
}
|
||||
|
||||
err := c.authHelper.ValidateHeader(req.Header["Authorization"], req.Method, req.Url)
|
||||
if err != nil {
|
||||
c.authFailures += 1
|
||||
|
||||
// vlc with login prompt sends 4 requests:
|
||||
// 1) without credentials
|
||||
// 2) with password but without the username
|
||||
// 3) without credentials
|
||||
// 4) with password and username
|
||||
// hence we must allow up to 3 failures
|
||||
var retErr error
|
||||
if c.authFailures > 3 {
|
||||
c.log("ERR: unauthorized: %s", err)
|
||||
retErr = errAuthCritical
|
||||
|
||||
} else if c.authFailures > 1 {
|
||||
c.log("WARN: unauthorized: %s", err)
|
||||
retErr = errAuthNotCritical
|
||||
|
||||
} else {
|
||||
retErr = errAuthNotCritical
|
||||
}
|
||||
|
||||
c.conn.WriteResponse(&gortsplib.Response{
|
||||
StatusCode: gortsplib.StatusUnauthorized,
|
||||
Header: gortsplib.Header{
|
||||
"CSeq": req.Header["CSeq"],
|
||||
"WWW-Authenticate": c.authHelper.GenerateHeader(),
|
||||
},
|
||||
})
|
||||
|
||||
return retErr
|
||||
}
|
||||
|
||||
// reset authFailures after a successful login
|
||||
c.authFailures = 0
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
||||
c.log(string(req.Method))
|
||||
|
||||
|
|
@ -370,7 +392,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
err := c.validateAuth(req, pconf.ReadUser, pconf.ReadPass, &c.readAuth, pconf.readIps)
|
||||
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
|
||||
if err != nil {
|
||||
if err == errAuthCritical {
|
||||
return false
|
||||
|
|
@ -411,7 +433,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
err := c.validateAuth(req, pconf.PublishUser, pconf.PublishPass, &c.publishAuth, pconf.publishIps)
|
||||
err := c.authenticate(pconf.publishIpsParsed, pconf.PublishUser, pconf.PublishPass, req)
|
||||
if err != nil {
|
||||
if err == errAuthCritical {
|
||||
return false
|
||||
|
|
@ -484,7 +506,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
err := c.validateAuth(req, pconf.ReadUser, pconf.ReadPass, &c.readAuth, pconf.readIps)
|
||||
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
|
||||
if err != nil {
|
||||
if err == errAuthCritical {
|
||||
return false
|
||||
|
|
@ -772,7 +794,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
|
||||
// write RTP frames sequentially
|
||||
go func() {
|
||||
for frame := range c.writec {
|
||||
for frame := range c.writeChan {
|
||||
c.conn.WriteInterleavedFrame(frame)
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ type serverUdpListener struct {
|
|||
writeBuf2 []byte
|
||||
writeCurBuf bool
|
||||
|
||||
writec chan *udpWrite
|
||||
done chan struct{}
|
||||
writeChan chan *udpWrite
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newServerUdpListener(p *program, port int, trackFlowType trackFlowType) (*serverUdpListener, error) {
|
||||
|
|
@ -41,7 +41,7 @@ func newServerUdpListener(p *program, port int, trackFlowType trackFlowType) (*s
|
|||
readBuf2: make([]byte, 2048),
|
||||
writeBuf1: make([]byte, 2048),
|
||||
writeBuf2: make([]byte, 2048),
|
||||
writec: make(chan *udpWrite),
|
||||
writeChan: make(chan *udpWrite),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ func (l *serverUdpListener) log(format string, args ...interface{}) {
|
|||
|
||||
func (l *serverUdpListener) run() {
|
||||
go func() {
|
||||
for w := range l.writec {
|
||||
for w := range l.writeChan {
|
||||
l.nconn.SetWriteDeadline(time.Now().Add(l.p.conf.WriteTimeout))
|
||||
l.nconn.WriteTo(w.buf, w.addr)
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ func (l *serverUdpListener) run() {
|
|||
}
|
||||
}
|
||||
|
||||
close(l.writec)
|
||||
close(l.writeChan)
|
||||
|
||||
close(l.done)
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ func (l *serverUdpListener) write(addr *net.UDPAddr, inbuf []byte) {
|
|||
copy(buf, inbuf)
|
||||
l.writeCurBuf = !l.writeCurBuf
|
||||
|
||||
l.writec <- &udpWrite{
|
||||
l.writeChan <- &udpWrite{
|
||||
addr: addr,
|
||||
buf: buf,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue