diff --git a/Makefile b/Makefile
index cf124bbf0cb..1a7d878b771 100644
--- a/Makefile
+++ b/Makefile
@@ -8,12 +8,12 @@ help:
@echo ""
@echo "available actions:"
@echo ""
- @echo " mod-tidy run go mod tidy"
- @echo " format format source files"
- @echo " test run available tests"
- @echo " run run app"
- @echo " release build release assets"
- @echo " travis-setup setup travis CI"
+ @echo " mod-tidy run go mod tidy"
+ @echo " format format source files"
+ @echo " test run available tests"
+ @echo " run ARGS=args run app"
+ @echo " release build release assets"
+ @echo " travis-setup setup travis CI"
@echo ""
mod-tidy:
@@ -64,7 +64,7 @@ run:
--network=host \
--name temp \
temp \
- /out
+ /out $(ARGS)
define DOCKERFILE_RELEASE
FROM $(BASE_IMAGE)
diff --git a/README.md b/README.md
index b07d2ad21c7..9ba1d13f0c7 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,7 @@ Features:
* Publish multiple streams at once, each in a separate path, that can be read by multiple users
* Each stream can have multiple video and audio tracks
* Supports the RTP/RTCP streaming protocol
+* Optional authentication schema for publishers
* Compatible with Linux and Windows, does not require any dependency or interpreter, it's a single executable
@@ -27,6 +28,8 @@ Precompiled binaries are available in the [release](https://github.com/aler9/rts
## Usage
+#### Basic usage
+
1. Start the server:
```
./rtsp-simple-server
@@ -47,14 +50,24 @@ Precompiled binaries are available in the [release](https://github.com/aler9/rts
gst-launch-1.0 -v rtspsrc location=rtsp://localhost:8554/mystream ! rtph264depay ! decodebin ! autovideosink
```
-
+#### Publisher authentication
-## Full command-line usage
+1. Start the server and set a publish key:
+ ```
+ ./rtsp-simple-server --publish-key=IU23yyfaw6324
+ ```
+
+ 2. Only publishers which have the key will be able to publish:
+ ```
+ ffmpeg -re -stream_loop -1 -i file.ts -c copy -f rtsp rtsp://localhost:8554/mystream?key=IU23yyfaw6324
+ ```
+
+#### Full command-line usage
```
usage: rtsp-simple-server []
-rtsp-simple-server
+rtsp-simple-server v0.0.0
RTSP server.
@@ -64,8 +77,10 @@ Flags:
--rtsp-port=8554 port of the RTSP TCP listener
--rtp-port=8000 port of the RTP UDP listener
--rtcp-port=8001 port of the RTCP UDP listener
+ --publish-key="" optional authentication key required to publish
```
+
## Links
diff --git a/client.go b/client.go
index 0223472f1d1..98688214215 100644
--- a/client.go
+++ b/client.go
@@ -19,6 +19,7 @@ var (
errTeardown = errors.New("teardown")
errPlay = errors.New("play")
errRecord = errors.New("record")
+ errWrongKey = errors.New("wrong key")
)
func interleavedChannelToTrack(channel int) (int, trackFlow) {
@@ -170,7 +171,7 @@ func (c *client) run() {
return
}
- // TEARDOWN, close connection silently
+ // TEARDOWN: close connection silently
case errTeardown:
return
@@ -258,7 +259,20 @@ func (c *client) run() {
}
}
- // error: write and exit
+ // wrong key: reply with 401 and exit
+ case errWrongKey:
+ c.log("ERR: %s", err)
+
+ c.rconn.WriteResponse(&rtsp.Response{
+ StatusCode: 401,
+ Status: "Unauthorized",
+ Headers: map[string]string{
+ "CSeq": req.Headers["CSeq"],
+ },
+ })
+ return
+
+ // generic error: reply with code 400 and exit
default:
c.log("ERR: %s", err)
@@ -287,30 +301,27 @@ func (c *client) handleRequest(req *rtsp.Request) (*rtsp.Response, error) {
return nil, fmt.Errorf("cseq missing")
}
- path, err := func() (string, error) {
- ur, err := url.Parse(req.Url)
- if err != nil {
- return "", fmt.Errorf("unable to parse path '%s'", req.Url)
- }
- path := ur.Path
+ ur, err := url.Parse(req.Url)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse path '%s'", req.Url)
+ }
+
+ path := func() string {
+ ret := ur.Path
// remove leading slash
- if len(path) > 1 {
- path = path[1:]
+ if len(ret) > 1 {
+ ret = ret[1:]
}
// strip any subpath
- if n := strings.Index(path, "/"); n >= 0 {
- path = path[:n]
+ if n := strings.Index(ret, "/"); n >= 0 {
+ ret = ret[:n]
}
- return path, nil
+ return ret
}()
- c.p.mutex.Lock()
- c.path = path
- c.p.mutex.Unlock()
-
switch req.Method {
case "OPTIONS":
// do not check state, since OPTIONS can be requested
@@ -397,6 +408,22 @@ func (c *client) handleRequest(req *rtsp.Request) (*rtsp.Response, error) {
return nil, fmt.Errorf("invalid SDP: %s", err)
}
+ if c.p.publishKey != "" {
+ q, err := url.ParseQuery(ur.RawQuery)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse query")
+ }
+
+ key, ok := q["key"]
+ if !ok || len(key) == 0 {
+ return nil, fmt.Errorf("key missing")
+ }
+
+ if key[0] != c.p.publishKey {
+ return nil, errWrongKey
+ }
+ }
+
err = func() error {
c.p.mutex.Lock()
defer c.p.mutex.Unlock()
diff --git a/main.go b/main.go
index e4c1ee7d9b3..e2d6ea40c4f 100644
--- a/main.go
+++ b/main.go
@@ -5,6 +5,7 @@ import (
"log"
"net"
"os"
+ "regexp"
"sync"
"gopkg.in/alecthomas/kingpin.v2"
@@ -42,6 +43,7 @@ type program struct {
rtspPort int
rtpPort int
rtcpPort int
+ publishKey string
mutex sync.RWMutex
rtspl *rtspListener
rtpl *udpListener
@@ -50,11 +52,20 @@ type program struct {
publishers map[string]*client
}
-func newProgram(rtspPort int, rtpPort int, rtcpPort int) (*program, error) {
+func newProgram(rtspPort int, rtpPort int, rtcpPort int, publishKey string) (*program, error) {
+ if publishKey != "" {
+ if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(publishKey) {
+ return nil, fmt.Errorf("publish key must be alphanumeric")
+ }
+ }
+
+ log.Printf("rtsp-simple-server %s", Version)
+
p := &program{
rtspPort: rtspPort,
rtpPort: rtpPort,
rtcpPort: rtcpPort,
+ publishKey: publishKey,
clients: make(map[*client]struct{}),
publishers: make(map[string]*client),
}
@@ -120,6 +131,7 @@ func main() {
rtspPort := kingpin.Flag("rtsp-port", "port of the RTSP TCP listener").Default("8554").Int()
rtpPort := kingpin.Flag("rtp-port", "port of the RTP UDP listener").Default("8000").Int()
rtcpPort := kingpin.Flag("rtcp-port", "port of the RTCP UDP listener").Default("8001").Int()
+ publishKey := kingpin.Flag("publish-key", "optional authentication key required to publish").Default("").String()
kingpin.Parse()
@@ -128,7 +140,7 @@ func main() {
os.Exit(0)
}
- p, err := newProgram(*rtspPort, *rtpPort, *rtcpPort)
+ p, err := newProgram(*rtspPort, *rtpPort, *rtcpPort, *publishKey)
if err != nil {
log.Fatal("ERR: ", err)
}