1
0
mirror of https://gitlab.crans.org/nounous/ghostream.git synced 2025-07-02 03:18:29 +02:00

10 Commits

10 changed files with 58 additions and 27 deletions

View File

@ -11,6 +11,13 @@
This project was developped at [Cr@ns](https://crans.org/) to stream events. This project was developped at [Cr@ns](https://crans.org/) to stream events.
> **Note**
> *This project is no longer maintained!*
>
> As an alternative, you should try [Galène](https://galene.org/) which supports [WebRTC-HTTP ingestion protocol (WHIP)](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/) for low-latency streaming.
> OBS Studio introduced WHIP output in version 30.0.
> Galène supports WHIP since Galène 0.8.
Features: Features:
- WebRTC playback with a lightweight web interface. - WebRTC playback with a lightweight web interface.

View File

@ -20,7 +20,7 @@ type Options struct {
// Backend to log user in // Backend to log user in
type Backend interface { type Backend interface {
Login(string, string) (bool, error) Login(string, string) (bool, string, error)
Close() Close()
} }

View File

@ -23,15 +23,15 @@ type Basic struct {
// Login hashs password and compare // Login hashs password and compare
// Returns (true, nil) if success // Returns (true, nil) if success
func (a Basic) Login(username string, password string) (bool, error) { func (a Basic) Login(username string, password string) (bool, string, error) {
hash, ok := a.Cfg.Credentials[username] hash, ok := a.Cfg.Credentials[username]
if !ok { if !ok {
return false, errors.New("user not found in credentials") return false, "", errors.New("user not found in credentials")
} }
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
// Login succeeded if no error // Login succeeded if no error
return err == nil, err return err == nil, username, err
} }
// Close has no connection to close // Close has no connection to close

View File

@ -10,19 +10,19 @@ func TestBasicLogin(t *testing.T) {
// Test good credentials // Test good credentials
backend, _ := New(&Options{Credentials: basicCredentials}) backend, _ := New(&Options{Credentials: basicCredentials})
ok, err := backend.Login("demo", "demo") ok, _, err := backend.Login("demo", "demo")
if !ok { if !ok {
t.Error("Error while logging with the basic authentication:", err) t.Error("Error while logging with the basic authentication:", err)
} }
// Test bad username // Test bad username
ok, err = backend.Login("baduser", "demo") ok, _, err = backend.Login("baduser", "demo")
if ok { if ok {
t.Error("Authentification failed to fail:", err) t.Error("Authentification failed to fail:", err)
} }
// Test bad password // Test bad password
ok, err = backend.Login("demo", "badpass") ok, _, err = backend.Login("demo", "badpass")
if ok { if ok {
t.Error("Authentification failed to fail:", err) t.Error("Authentification failed to fail:", err)
} }

View File

@ -4,11 +4,12 @@ package ldap
import ( import (
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"log" "log"
"strings"
) )
// Options holds package configuration // Options holds package configuration
type Options struct { type Options struct {
Aliases map[string]string Aliases map[string]map[string]string
URI string URI string
UserDn string UserDn string
} }
@ -21,19 +22,37 @@ type LDAP struct {
// Login tries to bind to LDAP // Login tries to bind to LDAP
// Returns (true, nil) if success // Returns (true, nil) if success
func (a LDAP) Login(username string, password string) (bool, error) { func (a LDAP) Login(username string, password string) (bool, string, error) {
aliasSplit := strings.SplitN(username, "__", 2)
potentialUsernames := []string{username}
if len(aliasSplit) == 2 {
alias := aliasSplit[0]
trueUsername := aliasSplit[1]
// Resolve stream alias if necessary // Resolve stream alias if necessary
for aliasFor, ok := a.Cfg.Aliases[username]; ok; aliasFor, ok = a.Cfg.Aliases[username] { if aliases, ok := a.Cfg.Aliases[alias]; ok {
log.Printf("[LDAP] Use stream alias %s for username %s", username, aliasFor) if _, ok := aliases[trueUsername]; ok {
username = aliasFor log.Printf("[LDAP] Use stream alias %s for username %s", alias, trueUsername)
potentialUsernames = append(potentialUsernames, trueUsername)
}
}
} }
var err error = nil
for _, username := range potentialUsernames {
// Try to bind as user // Try to bind as user
bindDn := "cn=" + username + "," + a.Cfg.UserDn bindDn := "cn=" + username + "," + a.Cfg.UserDn
err := a.Conn.Bind(bindDn, password) log.Printf("[LDAP] Logging to %s...", bindDn)
err = a.Conn.Bind(bindDn, password)
if err == nil {
// Login succeeded if no error // Login succeeded if no error
return err == nil, err return true, aliasSplit[0], nil
}
}
log.Printf("[LDAP] Logging failed: %s", err)
// Unable to log in
return err == nil, "", err
} }
// Close LDAP connection // Close LDAP connection

View File

@ -36,8 +36,10 @@ auth:
# userdn: cn=users,dc=example,dc=com # userdn: cn=users,dc=example,dc=com
# #
# # You can define aliases, to stream on stream.example.com/example with the credentials of the demo account. # # You can define aliases, to stream on stream.example.com/example with the credentials of the demo account.
# # You will have to use the streamid example__demo:password
# aliases: # aliases:
# example: demo # example:
# demo: ignored
# #
## Stream forwarding ## ## Stream forwarding ##

View File

@ -42,7 +42,7 @@ func New() *Config {
Credentials: make(map[string]string), Credentials: make(map[string]string),
}, },
LDAP: ldap.Options{ LDAP: ldap.Options{
Aliases: make(map[string]string), Aliases: make(map[string]map[string]string),
URI: "ldap://127.0.0.1:389", URI: "ldap://127.0.0.1:389",
UserDn: "cn=users,dc=example,dc=com", UserDn: "cn=users,dc=example,dc=com",
}, },

View File

@ -79,20 +79,22 @@ func Serve(streams *messaging.Streams, authBackend auth.Backend, cfg *Options) {
if len(split) > 1 { if len(split) > 1 {
// password was provided so it is a streamer // password was provided so it is a streamer
name, password := split[0], split[1] name, password := strings.ToLower(split[0]), split[1]
if authBackend != nil { if authBackend != nil {
// check password // check password
if ok, err := authBackend.Login(name, password); !ok || err != nil { ok, username, err := authBackend.Login(name, password)
if !ok || err != nil {
log.Printf("Failed to authenticate for stream %s", name) log.Printf("Failed to authenticate for stream %s", name)
s.Close() s.Close()
continue continue
} }
name = username
} }
go handleStreamer(s, streams, name) go handleStreamer(s, streams, name)
} else { } else {
// password was not provided so it is a viewer // password was not provided so it is a viewer
name := split[0] name := strings.ToLower(split[0])
// Send stream // Send stream
go handleViewer(s, streams, name) go handleViewer(s, streams, name)

View File

@ -21,7 +21,7 @@ import (
var ( var (
// Precompile regex // Precompile regex
validPath = regexp.MustCompile("^/[a-z0-9@_-]*$") validPath = regexp.MustCompile("^/[a-zA-Z0-9@_-]*$")
counterMutex = new(sync.Mutex) counterMutex = new(sync.Mutex)
connectedClients = make(map[string]map[string]int64) connectedClients = make(map[string]map[string]int64)
@ -42,7 +42,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
} }
// Get stream ID from URL, or from domain name // Get stream ID from URL, or from domain name
path := r.URL.Path[1:] path := strings.ToLower(r.URL.Path[1:])
host := r.Host host := r.Host
if strings.Contains(host, ":") { if strings.Contains(host, ":") {
realHost, _, err := net.SplitHostPort(r.Host) realHost, _, err := net.SplitHostPort(r.Host)
@ -58,7 +58,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
if path == "about" { if path == "about" {
path = "" path = ""
} else { } else {
path = streamID path = strings.ToLower(streamID)
} }
} }
@ -97,6 +97,7 @@ func staticHandler() http.Handler {
func statisticsHandler(w http.ResponseWriter, r *http.Request) { func statisticsHandler(w http.ResponseWriter, r *http.Request) {
// Retrieve stream name from URL // Retrieve stream name from URL
name := strings.SplitN(strings.Replace(r.URL.Path[7:], "/", "", -1), "@", 2)[0] name := strings.SplitN(strings.Replace(r.URL.Path[7:], "/", "", -1), "@", 2)[0]
name = strings.ToLower(name)
userCount := 0 userCount := 0
// Clients have a unique generated identifier per session, that expires in 40 seconds. // Clients have a unique generated identifier per session, that expires in 40 seconds.

View File

@ -25,7 +25,7 @@
<ul> <ul>
<li> <li>
<b>Serveur :</b> <b>Serveur :</b>
<code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?IDENTIFIANT:MOT_DE_PASSE</code>, <code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT:MOT_DE_PASSE</code>,
avec <code>IDENTIFIANT</code> et <code>MOT_DE_PASSE</code> avec <code>IDENTIFIANT</code> et <code>MOT_DE_PASSE</code>
vos identifiants. vos identifiants.
</li> </li>