From 122db74fb8d8a1317ad3935f2006b842d9cbce1f Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Thu, 30 Apr 2026 00:50:22 +0200 Subject: [PATCH] Use message passing for changing permissions. We used to handle permission change commands by directly mutating the permissions field from the source client's thread, which is racy. We now use message passing. Thanks to Vinayak Mishra. --- rtpconn/webclient.go | 75 ++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/rtpconn/webclient.go b/rtpconn/webclient.go index d81462c..0ee284e 100644 --- a/rtpconn/webclient.go +++ b/rtpconn/webclient.go @@ -922,6 +922,10 @@ type pushClientAction struct { data map[string]interface{} } +type changePermissionsAction struct { + kind string +} + type permissionsChangedAction struct{} type joinedAction struct { @@ -1222,6 +1226,29 @@ func handleAction(c *webClient, a any) error { } } } + case changePermissionsAction: + switch a.kind { + case "op": + c.permissions = addnew("op", c.permissions) + g := c.Group() + if g != nil && g.Description().AllowRecording { + c.permissions = addnew("record", c.permissions) + } + case "unop": + c.permissions = remove("op", c.permissions) + c.permissions = remove("record", c.permissions) + case "present": + c.permissions = addnew("present", c.permissions) + case "unpresent": + c.permissions = remove("present", c.permissions) + case "shutup": + c.permissions = remove("message", c.permissions) + case "unshutup": + c.permissions = addnew("message", c.permissions) + default: + return group.UserError("unknown permission") + } + c.action(permissionsChangedAction{}) case permissionsChangedAction: g := c.Group() if g == nil { @@ -1338,41 +1365,6 @@ func closeDownConn(c *webClient, id string, message string) error { return nil } -func setPermissions(g *group.Group, id string, perm string) error { - client := g.GetClient(id) - if client == nil { - return group.UserError("no such user") - } - - c, ok := client.(*webClient) - if !ok { - return group.UserError("this is not a real user") - } - - switch perm { - case "op": - c.permissions = addnew("op", c.permissions) - if g.Description().AllowRecording { - c.permissions = addnew("record", c.permissions) - } - case "unop": - c.permissions = remove("op", c.permissions) - c.permissions = remove("record", c.permissions) - case "present": - c.permissions = addnew("present", c.permissions) - case "unpresent": - c.permissions = remove("present", c.permissions) - case "shutup": - c.permissions = remove("message", c.permissions) - case "unshutup": - c.permissions = addnew("message", c.permissions) - default: - return group.UserError("unknown permission") - } - c.action(permissionsChangedAction{}) - return nil -} - func (c *webClient) Kick(id string, user *string, message string) error { c.action(kickAction{id, user, message}) return nil @@ -1912,10 +1904,17 @@ func handleClientMessage(c *webClient, m clientMessage) error { if !member("op", c.permissions) { return c.error(group.UserError("not authorised")) } - err := setPermissions(g, m.Dest, m.Kind) - if err != nil { - return c.error(err) + t := g.GetClient(m.Dest) + if t == nil { + return c.error(group.UserError("no suck user")) } + target, ok := t.(*webClient) + if !ok { + return c.error(group.UserError( + "this is not a real user", + )) + } + target.action(changePermissionsAction{m.Kind}) case "identify": if !member("op", c.permissions) { return c.error(group.UserError("not authorised"))