1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-11-12 19:55:59 +01:00
galene/group.go

519 lines
10 KiB
Go
Raw Normal View History

2020-04-24 19:38:21 +02:00
// Copyright (c) 2020 by Juliusz Chroboczek.
// This is not open source software. Copy it, and I'll break into your
// house and tell your three year-old that Santa doesn't exist.
package main
import (
2020-04-25 02:25:51 +02:00
"encoding/json"
2020-04-24 19:38:21 +02:00
"log"
2020-04-25 02:25:51 +02:00
"os"
"path/filepath"
2020-04-29 16:08:07 +02:00
"sort"
2020-04-25 04:08:43 +02:00
"strings"
2020-04-25 17:36:35 +02:00
"sync"
"time"
2020-04-24 19:38:21 +02:00
2020-07-16 20:17:32 +02:00
"github.com/pion/webrtc/v3"
2020-04-24 19:38:21 +02:00
)
2020-04-25 21:16:49 +02:00
type chatHistoryEntry struct {
id string
user string
2020-08-12 12:17:56 +02:00
kind string
2020-04-25 21:16:49 +02:00
value string
}
const (
minBitrate = 200000
)
2020-04-24 19:38:21 +02:00
type group struct {
name string
2020-04-24 19:38:21 +02:00
mu sync.Mutex
description *groupDescription
// indicates that the group no longer exists, but it still has clients
dead bool
locked bool
2020-05-28 02:35:09 +02:00
clients map[string]client
2020-04-25 21:16:49 +02:00
history []chatHistoryEntry
2020-04-24 19:38:21 +02:00
}
func (g *group) Locked() bool {
g.mu.Lock()
defer g.mu.Unlock()
return g.locked
}
func (g *group) SetLocked(locked bool) {
g.mu.Lock()
defer g.mu.Unlock()
g.locked = locked
}
func (g *group) Public() bool {
g.mu.Lock()
defer g.mu.Unlock()
return g.description.Public
}
func (g *group) Redirect() string {
g.mu.Lock()
defer g.mu.Unlock()
return g.description.Redirect
}
func (g *group) AllowRecording() bool {
g.mu.Lock()
defer g.mu.Unlock()
return g.description.AllowRecording
}
2020-09-13 13:34:53 +02:00
func (g *group) Redirect() string {
return g.description.Redirect
}
func (g *group) AllowRecording() bool {
return g.description.AllowRecording
}
2020-04-24 19:38:21 +02:00
var groups struct {
mu sync.Mutex
groups map[string]*group
api *webrtc.API
}
2020-09-13 13:01:06 +02:00
func (g *group) API() *webrtc.API {
return groups.api
}
2020-04-25 04:08:43 +02:00
func addGroup(name string, desc *groupDescription) (*group, error) {
2020-04-24 19:38:21 +02:00
groups.mu.Lock()
defer groups.mu.Unlock()
if groups.groups == nil {
groups.groups = make(map[string]*group)
2020-04-27 03:06:45 +02:00
s := webrtc.SettingEngine{}
2020-04-24 19:38:21 +02:00
m := webrtc.MediaEngine{}
m.RegisterCodec(webrtc.NewRTPVP8CodecExt(
webrtc.DefaultPayloadTypeVP8, 90000,
[]webrtc.RTCPFeedback{
2020-04-27 03:08:03 +02:00
{"goog-remb", ""},
{"nack", ""},
2020-04-27 03:08:03 +02:00
{"nack", "pli"},
2020-05-21 00:24:10 +02:00
{"ccm", "fir"},
},
"",
))
2020-04-24 19:38:21 +02:00
m.RegisterCodec(webrtc.NewRTPOpusCodec(
webrtc.DefaultPayloadTypeOpus, 48000,
))
2020-04-24 19:38:21 +02:00
groups.api = webrtc.NewAPI(
2020-04-27 03:06:45 +02:00
webrtc.WithSettingEngine(s),
2020-04-24 19:38:21 +02:00
webrtc.WithMediaEngine(m),
)
}
2020-04-25 04:08:43 +02:00
var err error
2020-04-24 19:38:21 +02:00
g := groups.groups[name]
if g == nil {
2020-04-25 17:36:35 +02:00
if desc == nil {
2020-04-25 04:08:43 +02:00
desc, err = getDescription(name)
if err != nil {
return nil, err
}
2020-04-25 02:25:51 +02:00
}
2020-04-24 19:38:21 +02:00
g = &group{
2020-04-25 02:25:51 +02:00
name: name,
description: desc,
2020-05-28 02:35:09 +02:00
clients: make(map[string]client),
2020-04-24 19:38:21 +02:00
}
groups.groups[name] = g
return g, nil
}
g.mu.Lock()
defer g.mu.Unlock()
if desc != nil {
2020-04-25 04:08:43 +02:00
g.description = desc
g.dead = false
return g, nil
}
if g.dead || time.Since(g.description.loadTime) > 5*time.Second {
changed, err := descriptionChanged(name, g.description)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Reading group %v: %v", name, err)
}
g.dead = true
delGroupUnlocked(name)
return nil, err
}
if changed {
desc, err := getDescription(name)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Reading group %v: %v",
name, err)
}
g.dead = true
delGroupUnlocked(name)
return nil, err
}
g.dead = false
g.description = desc
} else {
g.description.loadTime = time.Now()
}
2020-04-24 19:38:21 +02:00
}
return g, nil
}
2020-09-13 12:24:06 +02:00
func rangeGroups(f func(g *group) bool) {
2020-04-29 16:08:07 +02:00
groups.mu.Lock()
defer groups.mu.Unlock()
2020-09-13 12:24:06 +02:00
for _, g := range groups.groups {
ok := f(g)
if !ok {
break
}
2020-04-29 16:08:07 +02:00
}
2020-09-13 12:24:06 +02:00
}
func getGroupNames() []string {
names := make([]string, 0)
rangeGroups(func(g *group) bool {
names = append(names, g.name)
return true
})
2020-04-29 16:08:07 +02:00
return names
}
func getGroup(name string) *group {
groups.mu.Lock()
defer groups.mu.Unlock()
return groups.groups[name]
}
2020-04-25 02:01:04 +02:00
func delGroupUnlocked(name string) bool {
2020-04-24 19:38:21 +02:00
g := groups.groups[name]
if g == nil {
return true
}
if len(g.clients) != 0 {
return false
}
delete(groups.groups, name)
return true
}
func addClient(name string, c client) (*group, error) {
2020-04-25 04:08:43 +02:00
g, err := addGroup(name, nil)
2020-04-24 19:38:21 +02:00
if err != nil {
return nil, err
2020-04-24 19:38:21 +02:00
}
g.mu.Lock()
defer g.mu.Unlock()
perms, err := getPermission(g.description, c.Credentials())
2020-04-25 02:25:51 +02:00
if err != nil {
return nil, err
2020-04-25 02:25:51 +02:00
}
c.SetPermissions(perms)
2020-04-25 02:25:51 +02:00
if !perms.Op && g.locked {
return nil, userError("group is locked")
2020-05-18 15:24:04 +02:00
}
2020-04-25 18:09:31 +02:00
if !perms.Op && g.description.MaxClients > 0 {
2020-04-25 02:37:41 +02:00
if len(g.clients) >= g.description.MaxClients {
return nil, userError("too many users")
2020-04-25 02:37:41 +02:00
}
}
2020-06-08 22:14:28 +02:00
if g.clients[c.Id()] != nil {
return nil, protocolError("duplicate client id")
2020-04-25 21:29:21 +02:00
}
2020-06-08 22:14:28 +02:00
g.clients[c.Id()] = c
go func(clients []client) {
u := c.Credentials().Username
c.pushClient(c.Id(), u, true)
for _, cc := range clients {
uu := cc.Credentials().Username
err := c.pushClient(cc.Id(), uu, true)
if err == ErrClientDead {
return
}
cc.pushClient(c.Id(), u, true)
}
}(g.getClientsUnlocked(c))
return g, nil
2020-04-24 19:38:21 +02:00
}
2020-05-28 02:35:09 +02:00
func delClient(c client) {
2020-06-08 22:14:28 +02:00
g := c.Group()
if g == nil {
return
}
2020-05-28 02:35:09 +02:00
g.mu.Lock()
defer g.mu.Unlock()
2020-04-25 21:29:21 +02:00
2020-06-08 22:14:28 +02:00
if g.clients[c.Id()] != c {
2020-04-25 21:29:21 +02:00
log.Printf("Deleting unknown client")
return
}
2020-06-08 22:14:28 +02:00
delete(g.clients, c.Id())
go func(clients []client) {
for _, cc := range clients {
cc.pushClient(c.Id(), c.Credentials().Username, false)
}
}(g.getClientsUnlocked(nil))
2020-04-24 19:38:21 +02:00
}
2020-05-28 02:35:09 +02:00
func (g *group) getClients(except client) []client {
2020-04-24 19:38:21 +02:00
g.mu.Lock()
defer g.mu.Unlock()
return g.getClientsUnlocked(except)
}
func (g *group) getClientsUnlocked(except client) []client {
2020-05-28 02:35:09 +02:00
clients := make([]client, 0, len(g.clients))
2020-04-24 19:38:21 +02:00
for _, c := range g.clients {
if c != except {
clients = append(clients, c)
}
}
return clients
}
func (g *group) getClient(id string) client {
g.mu.Lock()
defer g.mu.Unlock()
return g.getClientUnlocked(id)
}
2020-05-28 02:35:09 +02:00
func (g *group) getClientUnlocked(id string) client {
for idd, c := range g.clients {
if idd == id {
2020-04-25 17:36:35 +02:00
return c
}
}
return nil
}
2020-05-28 02:35:09 +02:00
func (g *group) Range(f func(c client) bool) {
2020-04-24 19:38:21 +02:00
g.mu.Lock()
defer g.mu.Unlock()
for _, c := range g.clients {
ok := f(c)
2020-04-25 02:25:51 +02:00
if !ok {
break
2020-04-24 19:38:21 +02:00
}
}
}
func (g *group) shutdown(message string) {
g.Range(func(c client) bool {
cc, ok := c.(kickable)
if ok {
cc.kick(message)
}
return true
})
}
2020-04-25 21:16:49 +02:00
const maxChatHistory = 20
2020-04-30 19:13:10 +02:00
func (g *group) clearChatHistory() {
g.mu.Lock()
defer g.mu.Unlock()
g.history = nil
}
2020-08-12 12:17:56 +02:00
func (g *group) addToChatHistory(id, user, kind, value string) {
2020-04-25 21:16:49 +02:00
g.mu.Lock()
defer g.mu.Unlock()
if len(g.history) >= maxChatHistory {
copy(g.history, g.history[1:])
g.history = g.history[:len(g.history)-1]
}
g.history = append(g.history,
2020-08-12 12:17:56 +02:00
chatHistoryEntry{id: id, user: user, kind: kind, value: value},
2020-04-25 21:16:49 +02:00
)
}
func (g *group) getChatHistory() []chatHistoryEntry {
g.mu.Lock()
2020-05-12 12:48:56 +02:00
defer g.mu.Unlock()
2020-04-25 21:16:49 +02:00
h := make([]chatHistoryEntry, len(g.history))
copy(h, g.history)
return h
}
func matchUser(user clientCredentials, users []clientCredentials) (bool, bool) {
2020-04-25 02:25:51 +02:00
for _, u := range users {
if u.Username == "" {
if u.Password == "" || u.Password == user.Password {
return true, true
}
} else if u.Username == user.Username {
return true,
(u.Password == "" || u.Password == user.Password)
2020-04-25 02:25:51 +02:00
}
}
return false, false
}
type groupDescription struct {
loadTime time.Time `json:"-"`
modTime time.Time `json:"-"`
fileSize int64 `json:"-"`
2020-09-10 13:55:57 +02:00
Redirect string `json:"redirect,omitempty"`
Public bool `json:"public,omitempty"`
MaxClients int `json:"max-clients,omitempty"`
AllowAnonymous bool `json:"allow-anonymous,omitempty"`
AllowRecording bool `json:"allow-recording,omitempty"`
Op []clientCredentials `json:"op,omitempty"`
Presenter []clientCredentials `json:"presenter,omitempty"`
Other []clientCredentials `json:"other,omitempty"`
2020-04-25 02:25:51 +02:00
}
func descriptionChanged(name string, old *groupDescription) (bool, error) {
fi, err := os.Stat(filepath.Join(groupsDir, name+".json"))
if err != nil {
return false, err
}
if fi.Size() != old.fileSize || fi.ModTime() != old.modTime {
return true, err
}
return false, err
}
2020-04-25 02:25:51 +02:00
func getDescription(name string) (*groupDescription, error) {
r, err := os.Open(filepath.Join(groupsDir, name+".json"))
if err != nil {
return nil, err
}
defer r.Close()
var desc groupDescription
fi, err := r.Stat()
if err != nil {
return nil, err
}
desc.fileSize = fi.Size()
desc.modTime = fi.ModTime()
2020-04-25 02:25:51 +02:00
d := json.NewDecoder(r)
err = d.Decode(&desc)
if err != nil {
return nil, err
}
desc.loadTime = time.Now()
2020-04-25 02:25:51 +02:00
return &desc, nil
}
func getPermission(desc *groupDescription, creds clientCredentials) (clientPermissions, error) {
var p clientPermissions
if !desc.AllowAnonymous && creds.Username == "" {
2020-04-25 16:54:20 +02:00
return p, userError("anonymous users not allowed in this group, please choose a username")
2020-04-25 02:25:51 +02:00
}
if found, good := matchUser(creds, desc.Op); found {
2020-04-25 02:25:51 +02:00
if good {
2020-04-25 18:09:31 +02:00
p.Op = true
2020-04-25 02:25:51 +02:00
p.Present = true
2020-05-30 00:23:54 +02:00
if desc.AllowRecording {
p.Record = true
}
2020-04-25 02:25:51 +02:00
return p, nil
}
2020-04-25 17:36:35 +02:00
return p, userError("not authorised")
2020-04-25 02:25:51 +02:00
}
if found, good := matchUser(creds, desc.Presenter); found {
2020-04-25 02:25:51 +02:00
if good {
p.Present = true
return p, nil
}
2020-04-25 17:36:35 +02:00
return p, userError("not authorised")
2020-04-25 02:25:51 +02:00
}
if found, good := matchUser(creds, desc.Other); found {
2020-04-25 02:25:51 +02:00
if good {
return p, nil
}
2020-04-25 17:36:35 +02:00
return p, userError("not authorised")
2020-04-25 02:25:51 +02:00
}
2020-04-25 17:36:35 +02:00
return p, userError("not authorised")
}
2020-04-24 19:38:21 +02:00
type publicGroup struct {
Name string `json:"name"`
ClientCount int `json:"clientCount"`
}
func getPublicGroups() []publicGroup {
gs := make([]publicGroup, 0)
rangeGroups(func(g *group) bool {
if g.Public() {
2020-04-24 19:38:21 +02:00
gs = append(gs, publicGroup{
Name: g.name,
ClientCount: len(g.clients),
})
}
2020-09-13 12:24:06 +02:00
return true
})
2020-04-30 22:32:44 +02:00
sort.Slice(gs, func(i, j int) bool {
return gs[i].Name < gs[j].Name
})
2020-04-24 19:38:21 +02:00
return gs
}
2020-04-25 04:08:43 +02:00
func readPublicGroups() {
dir, err := os.Open(groupsDir)
if err != nil {
return
}
defer dir.Close()
fis, err := dir.Readdir(-1)
if err != nil {
log.Printf("readPublicGroups: %v", err)
return
}
for _, fi := range fis {
if !strings.HasSuffix(fi.Name(), ".json") {
continue
}
2020-04-25 17:36:35 +02:00
name := fi.Name()[:len(fi.Name())-5]
2020-04-25 04:08:43 +02:00
desc, err := getDescription(name)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Reading group %v: %v", name, err)
}
2020-04-25 04:08:43 +02:00
continue
}
if desc.Public {
addGroup(name, desc)
}
}
}