Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package galene for openSUSE:Factory checked 
in at 2021-11-05 22:59:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/galene (Old)
 and      /work/SRC/openSUSE:Factory/.galene.new.1890 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "galene"

Fri Nov  5 22:59:03 2021 rev:12 rq:929717 version:0.4.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/galene/galene.changes    2021-09-06 
15:58:07.825293815 +0200
+++ /work/SRC/openSUSE:Factory/.galene.new.1890/galene.changes  2021-11-05 
22:59:19.632308768 +0100
@@ -1,0 +2,23 @@
+Fri Nov  5 16:50:04 UTC 2021 - Michael Str??der <mich...@stroeder.com>
+
+- adapted AppArmor profile to new config files in 0.4.1
+
+-------------------------------------------------------------------
+Fri Nov 05 14:43:31 UTC 2021 - mich...@stroeder.com
+
+- Update to version 0.4.1:
+  * Create a new file data/config.json with global configuration.
+  * Remove data/passwd and the -redirect option with entries in config.json;
+    these are incompatible changes.
+  * Change the group URL from /group/name to /group/name/, which allows
+    using relative links.  The old URL is redirected to the new one.
+  * Extend the protocol with the ability to consult the group status before
+    joining; this allows using the group's displayName before the user has
+    logged in, and will be required for new authorisation schemes.
+  * Allow scrolling of the login dialog, useful on small devices.
+  * Fixed a typo that prevented the group name from being displayed.
+  * Made failed videos more visible.
+  * No longer attempt to save passwords in browser JavaScript, it's
+    insecure and not very useful.
+
+-------------------------------------------------------------------

Old:
----
  galene-0.4.tar.gz

New:
----
  galene-0.4.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ galene.spec ++++++
--- /var/tmp/diff_new_pack.ZryNPI/_old  2021-11-05 22:59:20.164309123 +0100
+++ /var/tmp/diff_new_pack.ZryNPI/_new  2021-11-05 22:59:20.168309125 +0100
@@ -25,7 +25,7 @@
 %bcond_without  apparmor
 
 Name:           galene
-Version:        0.4
+Version:        0.4.1
 Release:        0
 Summary:        Gal??ne videoconferencing server
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.ZryNPI/_old  2021-11-05 22:59:20.196309145 +0100
+++ /var/tmp/diff_new_pack.ZryNPI/_new  2021-11-05 22:59:20.196309145 +0100
@@ -3,8 +3,8 @@
     <param name="url">git://github.com/jech/galene.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">galene-0.4</param>
-    <param name="version">0.4</param>
+    <param name="revision">galene-0.4.1</param>
+    <param name="version">0.4.1</param>
     <param name="changesgenerate">enable</param>
     <!--param name="versionrewrite-pattern">galene-(\d+)</param>
     <param name="versionrewrite-replacement">\1</param-->

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.ZryNPI/_old  2021-11-05 22:59:20.216309158 +0100
+++ /var/tmp/diff_new_pack.ZryNPI/_new  2021-11-05 22:59:20.216309158 +0100
@@ -1,4 +1,4 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">git://github.com/jech/galene.git</param>
-              <param 
name="changesrevision">d33e4dea9ba74818d35ebde8f550b6a0ab0f75ad</param></service></servicedata>
\ No newline at end of file
+              <param 
name="changesrevision">37d6ab5445f5f0279e17694dd774fd86610e6c5e</param></service></servicedata>
\ No newline at end of file

++++++ apparmor-usr.sbin.galene ++++++
--- /var/tmp/diff_new_pack.ZryNPI/_old  2021-11-05 22:59:20.232309168 +0100
+++ /var/tmp/diff_new_pack.ZryNPI/_new  2021-11-05 22:59:20.232309168 +0100
@@ -22,7 +22,7 @@
   /etc/galene/cert.pem r,
   /etc/galene/key.pem r,
   /etc/galene/ice-servers.json r,
-  /etc/galene/passwd r,
+  /etc/galene/config.json r,
 
   # Grant read access to static web content
   /usr/share/galene/static/ r,

++++++ galene-0.4.tar.gz -> galene-0.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/CHANGES new/galene-0.4.1/CHANGES
--- old/galene-0.4/CHANGES      2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/CHANGES    2021-11-05 15:31:09.000000000 +0100
@@ -1,3 +1,19 @@
+4 November 2021: Gal??ne 0.4.1
+
+  * Create a new file data/config.json with global configuration.
+  * Remove data/passwd and the -redirect option with entries in config.json;
+    these are incompatible changes.
+  * Change the group URL from /group/name to /group/name/, which allows
+    using relative links.  The old URL is redirected to the new one.
+  * Extend the protocol with the ability to consult the group status before
+    joining; this allows using the group's displayName before the user has
+    logged in, and will be required for new authorisation schemes.
+  * Allow scrolling of the login dialog, useful on small devices.
+  * Fixed a typo that prevented the group name from being displayed.
+  * Made failed videos more visible.
+  * No longer attempt to save passwords in browser JavaScript, it's
+    insecure and not very useful.
+
 5 September 2021: Gal??ne 0.4
 
   * Implemented simulcast.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/INSTALL new/galene-0.4.1/INSTALL
--- old/galene-0.4/INSTALL      2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/INSTALL    2021-11-05 15:31:09.000000000 +0100
@@ -12,15 +12,6 @@
     go build -ldflags="-s -w"
 
 
-## Set the server administrator credentials
-
-This step is optional, it is currently only relevant for access to the
-`/stats.html` and `/stats.json` locations.
-
-    mkdir data
-    echo 'god:topsecret' > data/passwd
-
-
 ## Set up a group
 
 Set up a group called *test* by creating a file `groups/test.json`:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/README new/galene-0.4.1/README
--- old/galene-0.4/README       2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/README     2021-11-05 15:31:09.000000000 +0100
@@ -37,6 +37,23 @@
 not.
 
 
+# The global configuration file
+
+The server may be configured in the JSON file `data/config.json`.  This
+file may look as follows:
+
+    {
+        "canonicalHost: "galene.example.org",
+        "admin":[{"username":"root","password":"secret"}]
+    }
+
+The fields are as follows:
+
+- `canonicalHost`: the canonical name of the host running the server;
+- `admin` defines the users allowed to look at the `/stats.html` file; it
+  has the same syntax as user definitions in groups (see below).
+
+
 # Group definitions
 
 Groups are defined by files in the `./groups` directory (this may be
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/README.PROTOCOL 
new/galene-0.4.1/README.PROTOCOL
--- old/galene-0.4/README.PROTOCOL      2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/README.PROTOCOL    2021-11-05 15:31:09.000000000 +0100
@@ -1,31 +1,12 @@
 # Gal??ne's protocol
 
-Gal??ne uses a symmetric, asynchronous protocol.  In client-server
-usage, some messages are only sent in the client to server or in the
-server to client direction.
-
-## Message syntax
-
-All messages are sent as JSON objects.  All fields except `type` are
-optional; however, there are some fields that are common across multiple
-message types.
-
- - `type`, the type of the message;
- - `kind`, the subtype of the message;
- - `id`, the id of the object being manipulated;
- - `source`, the client-id of the originating client;
- - `username`, the username of the originating client;
- - `dest`, the client-id of the destination client;
- - `privileged`, set by the server to indicate that the originating client
-   had the `op` privilege at the time it sent the message.
-
 ## Data structures
 
 ### Group
 
 A group is a set of clients.  It is identified by a human-readable name
-that must not start or end with a slash "`/`" and must not have the
-substrings "`/../`" or "`/./`".
+that must not start or end with a slash "`/`", must not start with
+a period "`.`", and must not contain the substrings "`/../`" or "`/./`".
 
 ### Client
 
@@ -41,6 +22,48 @@
 allowed).  The offerer is also the RTP sender (i.e. all tracks sent by the
 offerer are of type `sendonly`).
 
+Gal??ne uses a symmetric, asynchronous protocol.  In client-server
+usage, some messages are only sent in the client to server or in the
+server to client direction.
+
+## Before connecting
+
+Before it connects and joins a group, a client may perform an HTTP GET
+request on the URL `/public-groups.json`.  This yields a JSON array of
+objects, one for each group that has been marked public in its
+configuration file.  Each object has the following fields:
+
+ - `name`: the group's name
+ - `displayName` (optional): a longer version of the name used for display;
+ - `description` (optional): a user-readable description.
+ - `locked`: true if the group is locked;
+ - `clientCount`: the number of clients currently in the group.
+
+A client may also fetch the URL `/group/name/.status.json` to retrieve the
+status of a single group.  If the group has not been marked as public,
+then the fields `locked` and `clientCount` are omitted.
+
+## Connecting
+
+The client connects to the websocket at `/ws`.  Galene uses a symmetric,
+asynchronous protocol: there are no requests and responses, and most
+messages may be sent by either peer.
+
+## Message syntax
+
+All messages are sent as JSON objects.  All fields except `type` are
+optional; however, there are some fields that are common across multiple
+message types:
+
+ - `type`, the type of the message;
+ - `kind`, the subtype of the message;
+ - `id`, the id of the object being manipulated;
+ - `source`, the client-id of the originating client;
+ - `username`, the username of the originating client;
+ - `dest`, the client-id of the destination client;
+ - `privileged`, set by the server to indicate that the originating client
+   had the `op` privilege at the time when it sent the message.
+
 ## Establishing and maintaining a connection
 
 The peer establishing the connection (the WebSocket client) sends
@@ -107,8 +130,8 @@
 
 The `permissions` field is an array of strings that may contain the values
 `present`, `op` and `record`.  The `status` field is a dictionary that
-contains random information that can be usefully displayed in the user
-interface.
+contains status information about the group, in the same format as at the
+`.status.json` URL above.
 
 ## Maintaining group membership
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/diskwriter/diskwriter.go 
new/galene-0.4.1/diskwriter/diskwriter.go
--- old/galene-0.4/diskwriter/diskwriter.go     2021-09-05 18:35:19.000000000 
+0200
+++ new/galene-0.4.1/diskwriter/diskwriter.go   2021-11-05 15:31:09.000000000 
+0100
@@ -59,10 +59,6 @@
        return "RECORDING"
 }
 
-func (client *Client) Challenge(group string, cred group.ClientCredentials) 
bool {
-       return true
-}
-
 func (client *Client) SetPermissions(perms group.ClientPermissions) {
        return
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/galene-0.4/galene-password-generator/galene-password-generator.go 
new/galene-0.4.1/galene-password-generator/galene-password-generator.go
--- old/galene-0.4/galene-password-generator/galene-password-generator.go       
2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/galene-password-generator/galene-password-generator.go     
2021-11-05 15:31:09.000000000 +0100
@@ -56,7 +56,7 @@
                }
                e := json.NewEncoder(os.Stdout)
                if username != "" {
-                       creds := group.ClientCredentials{
+                       creds := group.ClientPattern{
                                Username: username,
                                Password: &p,
                        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/galene.go new/galene-0.4.1/galene.go
--- old/galene-0.4/galene.go    2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/galene.go  2021-11-05 15:31:09.000000000 +0100
@@ -21,17 +21,15 @@
 )
 
 func main() {
-       var cpuprofile, memprofile, mutexprofile, httpAddr, dataDir string
+       var cpuprofile, memprofile, mutexprofile, httpAddr string
        var udpRange string
 
        flag.StringVar(&httpAddr, "http", ":8443", "web server `address`")
        flag.StringVar(&webserver.StaticRoot, "static", "./static/",
                "web server root `directory`")
-       flag.StringVar(&webserver.Redirect, "redirect", "",
-               "redirect to canonical `host`")
        flag.BoolVar(&webserver.Insecure, "insecure", false,
                "act as an HTTP server rather than HTTPS")
-       flag.StringVar(&dataDir, "data", "./data/",
+       flag.StringVar(&group.DataDirectory, "data", "./data/",
                "data `directory`")
        flag.StringVar(&group.Directory, "groups", "./groups/",
                "group description `directory`")
@@ -112,7 +110,7 @@
                log.Printf("File descriptor limit is %v, please increase it!", 
n)
        }
 
-       ice.ICEFilename = filepath.Join(dataDir, "ice-servers.json")
+       ice.ICEFilename = filepath.Join(group.DataDirectory, "ice-servers.json")
 
        // make sure the list of public groups is updated early
        go group.Update()
@@ -123,7 +121,7 @@
 
        serverDone := make(chan struct{})
        go func() {
-               err := webserver.Serve(httpAddr, dataDir)
+               err := webserver.Serve(httpAddr, group.DataDirectory)
                if err != nil {
                        log.Printf("Server: %v", err)
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/go.mod new/galene-0.4.1/go.mod
--- old/galene-0.4/go.mod       2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/go.mod     2021-11-05 15:31:09.000000000 +0100
@@ -7,13 +7,12 @@
        github.com/gorilla/websocket v1.4.2
        github.com/jech/cert v0.0.0-20210819231831-aca735647728
        github.com/jech/samplebuilder v0.0.0-20210823163459-dd92d75bae48
-       github.com/pion/ice/v2 v2.1.12
-       github.com/pion/rtcp v1.2.6
-       github.com/pion/rtp v1.7.2
+       github.com/pion/ice/v2 v2.1.13
+       github.com/pion/rtcp v1.2.8
+       github.com/pion/rtp v1.7.4
        github.com/pion/sdp/v3 v3.0.4
        github.com/pion/turn/v2 v2.0.5
-       github.com/pion/webrtc/v3 v3.1.0-beta.6
-       golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
-       golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
-       golang.org/x/sys v0.0.0-20210903071746-97244b99971b
+       github.com/pion/webrtc/v3 v3.1.6
+       golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
+       golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/go.sum new/galene-0.4.1/go.sum
--- old/galene-0.4/go.sum       2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/go.sum     2021-11-05 15:31:09.000000000 +0100
@@ -42,25 +42,29 @@
 github.com/onsi/gomega v1.11.0/go.mod 
h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
 github.com/pion/datachannel v1.4.21 
h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
 github.com/pion/datachannel v1.4.21/go.mod 
h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
-github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
 github.com/pion/dtls/v2 v2.0.9/go.mod 
h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
+github.com/pion/dtls/v2 v2.0.10 h1:wgys7gPR1NMbWjmjJ3CW7lkUGaun8djgH8nahpNLnxI=
+github.com/pion/dtls/v2 v2.0.10/go.mod 
h1:00OxfeCRWHShcqT9jx8pKKmBWuTt0NCZoVPCaC4VKvU=
 github.com/pion/ice/v2 v2.1.10/go.mod 
h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
-github.com/pion/ice/v2 v2.1.12 h1:ZDBuZz+fEI7iDifZCYFVzI4p0Foy0YhdSSZ87ZtRcRE=
-github.com/pion/ice/v2 v2.1.12/go.mod 
h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU=
-github.com/pion/interceptor v0.0.15 
h1:pQFkBUL8akUHiGoFr+pM94Q/15x7sLFh0K3Nj+DCC6s=
+github.com/pion/ice/v2 v2.1.13 h1:/YNYcIw56LT/whwuzkTnrprcRnapj2ZNqUsR0W8elmo=
+github.com/pion/ice/v2 v2.1.13/go.mod 
h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU=
 github.com/pion/interceptor v0.0.15/go.mod 
h1:pg3J253eGi5bqyKzA74+ej5Y19ez2jkWANVnF+Z9Dfk=
+github.com/pion/interceptor v0.1.0 
h1:SlXKaDlEvSl7cr4j8fJykzVz4UdH+7UDtcvx+u01wLU=
+github.com/pion/interceptor v0.1.0/go.mod 
h1:j5NIl3tJJPB3u8+Z2Xz8MZs/VV6rc+If9mXEKNuFmEM=
 github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
 github.com/pion/logging v0.2.2/go.mod 
h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
 github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
 github.com/pion/mdns v0.0.5/go.mod 
h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
 github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
 github.com/pion/randutil v0.1.0/go.mod 
h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
-github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
 github.com/pion/rtcp v1.2.6/go.mod 
h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
+github.com/pion/rtcp v1.2.8 h1:Cys8X6r0xxU65ESTmXkqr8eU1Q1Wx+lNkoZCUH4zD7E=
+github.com/pion/rtcp v1.2.8/go.mod 
h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
 github.com/pion/rtp v1.7.0/go.mod 
h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
 github.com/pion/rtp v1.7.1/go.mod 
h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
-github.com/pion/rtp v1.7.2 h1:HCDKDCixh7PVjkQTsqHAbk1lg+bx059EHxcnyl42dYs=
 github.com/pion/rtp v1.7.2/go.mod 
h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
+github.com/pion/rtp v1.7.4 h1:4dMbjb1SuynU5OpA3kz1zHK+u+eOCQjW3MAeVHf1ODA=
+github.com/pion/rtp v1.7.4/go.mod 
h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
 github.com/pion/sctp v1.7.10/go.mod 
h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
 github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
 github.com/pion/sctp v1.7.12/go.mod 
h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
@@ -79,8 +83,8 @@
 github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
 github.com/pion/udp v0.1.1/go.mod 
h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
 github.com/pion/webrtc/v3 v3.1.0-beta.3/go.mod 
h1:I4O6v2pkiXdVmcn7sUhCNwHUAepGU19PVEyR204s1qc=
-github.com/pion/webrtc/v3 v3.1.0-beta.6 
h1:HAEH0dObc+g0BleuMSow+rE4jpXYhJjmSchnmJFFso8=
-github.com/pion/webrtc/v3 v3.1.0-beta.6/go.mod 
h1:KQH/wVKKJzBTQ6sX1bDTxsvTpQ6gEjVZSJlzYaB58aM=
+github.com/pion/webrtc/v3 v3.1.6 
h1:r6WQRayW2SyKTYeRl4vBUQ43XXp7RSwBJ9+tNQWI5zQ=
+github.com/pion/webrtc/v3 v3.1.6/go.mod 
h1:tkwdWNYdZhc200hH/wPx6AtNo/rcTAM6MICA6dg1je8=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -96,8 +100,8 @@
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod 
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod 
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod 
h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 
h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 
h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod 
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -112,9 +116,9 @@
 golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod 
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f 
h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
-golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309 
h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod 
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -131,8 +135,8 @@
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210903071746-97244b99971b 
h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
-golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 
h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/group/client.go 
new/galene-0.4.1/group/client.go
--- old/galene-0.4/group/client.go      2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/group/client.go    2021-11-05 15:31:09.000000000 +0100
@@ -76,7 +76,7 @@
        return json.Marshal(RawPassword(p))
 }
 
-type ClientCredentials struct {
+type ClientPattern struct {
        Username string    `json:"username,omitempty"`
        Password *Password `json:"password,omitempty"`
 }
@@ -88,15 +88,16 @@
        System  bool `json:"system,omitempty"`
 }
 
-type Challengeable interface {
-       Username() string
-       Challenge(string, ClientCredentials) bool
+type ClientCredentials struct {
+       System   bool
+       Username string
+       Password string
 }
 
 type Client interface {
        Group() *Group
        Id() string
-       Challengeable
+       Username() string
        Permissions() ClientPermissions
        SetPermissions(ClientPermissions)
        Status() map[string]interface{}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/group/group.go 
new/galene-0.4.1/group/group.go
--- old/galene-0.4/group/group.go       2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/group/group.go     2021-11-05 15:31:09.000000000 +0100
@@ -17,11 +17,12 @@
        "github.com/pion/webrtc/v3"
 )
 
-var Directory string
+var Directory, DataDirectory string
 var UseMDNS bool
 var UDPMin, UDPMax uint16
 
 var ErrNotAuthorised = errors.New("not authorised")
+var ErrAnonymousNotAuthorised = errors.New("anonymous users not authorised in 
this group")
 
 type UserError string
 
@@ -105,28 +106,16 @@
        }
 }
 
-func (g *Group) Public() bool {
+func (g *Group) Description() *Description {
        g.mu.Lock()
        defer g.mu.Unlock()
-       return g.description.Public
+       return g.description
 }
 
-func (g *Group) Redirect() string {
+func (g *Group) ClientCount() int {
        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
-}
-
-func (g *Group) DisplayName() string {
-       g.mu.Lock()
-       defer g.mu.Unlock()
-       return g.description.DisplayName
+       return len(g.clients)
 }
 
 func (g *Group) EmptyTime() time.Duration {
@@ -495,7 +484,9 @@
 func Get(name string) *Group {
        groups.mu.Lock()
        defer groups.mu.Unlock()
-
+       if groups.groups == nil {
+               return nil
+       }
        return groups.groups[name]
 }
 
@@ -522,7 +513,7 @@
        return true
 }
 
-func AddClient(group string, c Client) (*Group, error) {
+func AddClient(group string, c Client, creds ClientCredentials) (*Group, 
error) {
        g, err := Add(group, nil)
        if err != nil {
                return nil, err
@@ -534,7 +525,7 @@
        clients := g.getClientsUnlocked(nil)
 
        if !c.Permissions().System {
-               perms, err := g.description.GetPermission(group, c)
+               perms, err := g.description.GetPermission(group, creds)
                if err != nil {
                        return nil, err
                }
@@ -698,8 +689,12 @@
        })
 }
 
-func (g *Group) Shutdown(message string) {
-       kickall(g, message)
+func Shutdown(message string) {
+       Range(func(g *Group) bool {
+               g.SetLocked(true, message)
+               kickall(g, message)
+               return true
+       })
 }
 
 type warner interface {
@@ -781,12 +776,16 @@
        return h
 }
 
-func matchClient(group string, c Challengeable, users []ClientCredentials) 
(bool, bool) {
+func matchClient(group string, creds ClientCredentials, users []ClientPattern) 
(bool, bool) {
        matched := false
        for _, u := range users {
-               if u.Username == c.Username() {
+               if u.Username == creds.Username {
                        matched = true
-                       if c.Challenge(group, u) {
+                       if u.Password == nil {
+                               return true, true
+                       }
+                       m, _ := u.Password.Match(creds.Password)
+                       if m {
                                return true, true
                        }
                }
@@ -797,7 +796,11 @@
 
        for _, u := range users {
                if u.Username == "" {
-                       if c.Challenge(group, u) {
+                       if u.Password == nil {
+                               return true, true
+                       }
+                       m, _ := u.Password.Match(creds.Password)
+                       if m {
                                return true, true
                        }
                }
@@ -805,8 +808,71 @@
        return false, false
 }
 
-// Type Description represents a group description together with some
-// metadata about the JSON file it was deserialised from.
+// Configuration represents the contents of the data/config.json file.
+type Configuration struct {
+       // The modtime and size of the file.  These are used to detect
+       // when a file has changed on disk.
+       modTime  time.Time `json:"-"`
+       fileSize int64     `json:"-"`
+
+       CanonicalHost string          `json:"canonicalHost"`
+       Admin         []ClientPattern `json:"admin"`
+}
+
+func (conf Configuration) Zero() bool {
+       return conf.modTime.Equal(time.Time{}) &&
+               conf.fileSize == 0
+}
+
+var configuration struct {
+       mu            sync.Mutex
+       configuration *Configuration
+}
+
+func GetConfiguration() (*Configuration, error) {
+       configuration.mu.Lock()
+       defer configuration.mu.Unlock()
+
+       if configuration.configuration == nil {
+               configuration.configuration = &Configuration{}
+       }
+
+       filename := filepath.Join(DataDirectory, "config.json")
+       fi, err := os.Stat(filename)
+       if err != nil {
+               if os.IsNotExist(err) {
+                       if !configuration.configuration.Zero() {
+                               configuration.configuration = &Configuration{}
+                       }
+                       return configuration.configuration, nil
+               }
+               return nil, err
+       }
+
+       if configuration.configuration.modTime.Equal(fi.ModTime()) &&
+               configuration.configuration.fileSize == fi.Size() {
+               return configuration.configuration, nil
+       }
+
+       f, err := os.Open(filename)
+       if err != nil {
+               return nil, err
+       }
+       defer f.Close()
+
+       d := json.NewDecoder(f)
+       d.DisallowUnknownFields()
+       var conf Configuration
+       err = d.Decode(&conf)
+       if err != nil {
+               return nil, err
+       }
+       configuration.configuration = &conf
+       return configuration.configuration, nil
+}
+
+// Description represents a group description together with some metadata
+// about the JSON file it was deserialised from.
 type Description struct {
        // The file this was deserialised from.  This is not necessarily
        // the name of the group, for example in case of a subgroup.
@@ -858,13 +924,13 @@
        Autokick bool `json:"autokick,omitempty"`
 
        // A list of logins for ops.
-       Op []ClientCredentials `json:"op,omitempty"`
+       Op []ClientPattern `json:"op,omitempty"`
 
        // A list of logins for presenters.
-       Presenter []ClientCredentials `json:"presenter,omitempty"`
+       Presenter []ClientPattern `json:"presenter,omitempty"`
 
        // A list of logins for non-presenting users.
-       Other []ClientCredentials `json:"other,omitempty"`
+       Other []ClientPattern `json:"other,omitempty"`
 
        // Codec preferences.  If empty, a suitable default is chosen in
        // the APIFromNames function.
@@ -963,12 +1029,12 @@
        return &desc, nil
 }
 
-func (desc *Description) GetPermission(group string, c Challengeable) 
(ClientPermissions, error) {
+func (desc *Description) GetPermission(group string, creds ClientCredentials) 
(ClientPermissions, error) {
        var p ClientPermissions
-       if !desc.AllowAnonymous && c.Username() == "" {
-               return p, UserError("anonymous users not allowed in this group, 
please choose a username")
+       if !desc.AllowAnonymous && creds.Username == "" {
+               return p, ErrAnonymousNotAuthorised
        }
-       if found, good := matchClient(group, c, desc.Op); found {
+       if found, good := matchClient(group, creds, desc.Op); found {
                if good {
                        p.Op = true
                        p.Present = true
@@ -979,14 +1045,14 @@
                }
                return p, ErrNotAuthorised
        }
-       if found, good := matchClient(group, c, desc.Presenter); found {
+       if found, good := matchClient(group, creds, desc.Presenter); found {
                if good {
                        p.Present = true
                        return p, nil
                }
                return p, ErrNotAuthorised
        }
-       if found, good := matchClient(group, c, desc.Other); found {
+       if found, good := matchClient(group, creds, desc.Other); found {
                if good {
                        return p, nil
                }
@@ -995,26 +1061,37 @@
        return p, ErrNotAuthorised
 }
 
-type Public struct {
+type Status struct {
        Name        string `json:"name"`
        DisplayName string `json:"displayName,omitempty"`
        Description string `json:"description,omitempty"`
        Locked      bool   `json:"locked,omitempty"`
-       ClientCount int    `json:"clientCount"`
+       ClientCount *int   `json:"clientCount,omitempty"`
 }
 
-func GetPublic() []Public {
-       gs := make([]Public, 0)
+func GetStatus(g *Group, authentified bool) Status {
+       desc := g.Description()
+       d := Status{
+               Name:        g.name,
+               DisplayName: desc.DisplayName,
+               Description: desc.Description,
+       }
+
+       if authentified || desc.Public {
+               // these are considered private information
+               locked, _ := g.Locked()
+               count := g.ClientCount()
+               d.Locked = locked
+               d.ClientCount = &count
+       }
+       return d
+}
+
+func GetPublic() []Status {
+       gs := make([]Status, 0)
        Range(func(g *Group) bool {
-               if g.Public() {
-                       locked, _ := g.Locked()
-                       gs = append(gs, Public{
-                               Name:        g.name,
-                               DisplayName: g.DisplayName(),
-                               Description: g.description.Description,
-                               Locked:      locked,
-                               ClientCount: len(g.clients),
-                       })
+               if g.Description().Public {
+                       gs = append(gs, GetStatus(g, false))
                }
                return true
        })
@@ -1028,6 +1105,14 @@
 // list of public groups.  It also removes from memory any non-public
 // groups that haven't been accessed in maxHistoryAge.
 func Update() {
+       _, err := GetConfiguration()
+       if err != nil {
+               log.Printf("%v: %v",
+                       filepath.Join(DataDirectory, "config.json"),
+                       err,
+               )
+       }
+
        names := GetNames()
 
        for _, name := range names {
@@ -1048,7 +1133,7 @@
                }
        }
 
-       err := filepath.Walk(
+       err = filepath.Walk(
                Directory,
                func(path string, fi os.FileInfo, err error) error {
                        if err != nil {
@@ -1070,6 +1155,11 @@
                                )
                                return nil
                        }
+                       base := filepath.Base(filename)
+                       if base[0] == '.' {
+                               log.Printf("Group file %v ignored", filename)
+                               return nil
+                       }
                        name := filename[:len(filename)-5]
                        desc, err := GetDescription(name)
                        if err != nil {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/group/group_test.go 
new/galene-0.4.1/group/group_test.go
--- old/galene-0.4/group/group_test.go  2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/group/group_test.go        2021-11-05 15:31:09.000000000 
+0100
@@ -29,18 +29,6 @@
        if locked, _ := g.Locked(); locked {
                t.Errorf("Locked: expected false, got %v", locked)
        }
-       if public := g.Public(); public {
-               t.Errorf("Public: expected false, got %v", public)
-       }
-       if public := g2.Public(); !public {
-               t.Errorf("Public: expected true, got %v", public)
-       }
-       if redirect := g.Redirect(); redirect != "" {
-               t.Errorf("Redirect: expected empty, got %v", redirect)
-       }
-       if ar := g.AllowRecording(); ar {
-               t.Errorf("Allow Recording: expected false, got %v", ar)
-       }
        api, err := g.API()
        if err != nil || api == nil {
                t.Errorf("Couldn't get API: %v", err)
@@ -136,56 +124,36 @@
        }
 }
 
-type testClient struct {
-       username string
-       password string
+var badClients = []ClientCredentials{
+       {Username: "jch", Password: "foo"},
+       {Username: "john", Password: "foo"},
+       {Username: "james", Password: "foo"},
 }
 
-func (c testClient) Username() string {
-       return c.username
-}
-
-func (c testClient) Challenge(g string, creds ClientCredentials) bool {
-       if creds.Password == nil {
-               return true
-       }
-       m, err := creds.Password.Match(c.password)
-       if err != nil {
-               return false
-       }
-       return m
-}
-
-type testClientPerm struct {
-       c testClient
+type credPerm struct {
+       c ClientCredentials
        p ClientPermissions
 }
 
-var badClients = []testClient{
-       testClient{"jch", "foo"},
-       testClient{"john", "foo"},
-       testClient{"james", "foo"},
-}
-
-var goodClients = []testClientPerm{
+var goodClients = []credPerm{
        {
-               testClient{"jch", "topsecret"},
+               ClientCredentials{Username: "jch", Password: "topsecret"},
                ClientPermissions{Op: true, Present: true},
        },
        {
-               testClient{"john", "secret"},
+               ClientCredentials{Username: "john", Password: "secret"},
                ClientPermissions{Present: true},
        },
        {
-               testClient{"john", "secret2"},
+               ClientCredentials{Username: "john", Password: "secret2"},
                ClientPermissions{Present: true},
        },
        {
-               testClient{"james", "secret3"},
+               ClientCredentials{Username: "james", Password: "secret3"},
                ClientPermissions{},
        },
        {
-               testClient{"paul", "secret3"},
+               ClientCredentials{Username: "paul", Password: "secret3"},
                ClientPermissions{},
        },
 }
@@ -198,7 +166,7 @@
        }
 
        for _, c := range badClients {
-               t.Run("bad "+c.Username(), func(t *testing.T) {
+               t.Run("bad "+c.Username, func(t *testing.T) {
                        p, err := d.GetPermission("test", c)
                        if err != ErrNotAuthorised {
                                t.Errorf("GetPermission %v: %v %v", c, err, p)
@@ -207,7 +175,7 @@
        }
 
        for _, cp := range goodClients {
-               t.Run("good "+cp.c.Username(), func(t *testing.T) {
+               t.Run("good "+cp.c.Username, func(t *testing.T) {
                        p, err := d.GetPermission("test", cp.c)
                        if err != nil {
                                t.Errorf("GetPermission %v: %v", cp.c, err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/rtpconn/rtpconn.go 
new/galene-0.4.1/rtpconn/rtpconn.go
--- old/galene-0.4/rtpconn/rtpconn.go   2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/rtpconn/rtpconn.go 2021-11-05 15:31:09.000000000 +0100
@@ -508,8 +508,6 @@
        id            string
        client        group.Client
        label         string
-       userId        string
-       username      string
        pc            *webrtc.PeerConnection
        iceCandidates []*webrtc.ICECandidateInit
 
@@ -548,7 +546,7 @@
 }
 
 func (up *rtpUpConnection) User() (string, string) {
-       return up.userId, up.username
+       return up.client.Id(), up.client.Username()
 }
 
 func (up *rtpUpConnection) AddLocal(local conn.Down) error {
@@ -1003,7 +1001,7 @@
        if rate < ^uint64(0) && len(ssrcs) > 0 {
                packets = append(packets,
                        &rtcp.ReceiverEstimatedMaximumBitrate{
-                               Bitrate: rate,
+                               Bitrate: float32(rate),
                                SSRCs:   ssrcs,
                        },
                )
@@ -1185,7 +1183,8 @@
                                        track.remote.RequestKeyframe()
                                }
                        case *rtcp.ReceiverEstimatedMaximumBitrate:
-                               track.maxREMBBitrate.Set(p.Bitrate, jiffies)
+                               rate := uint64(p.Bitrate + 0.5)
+                               track.maxREMBBitrate.Set(rate, jiffies)
                                adjust = true
                        case *rtcp.ReceiverReport:
                                for _, r := range p.Reports {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/rtpconn/rtpconn_test.go 
new/galene-0.4.1/rtpconn/rtpconn_test.go
--- old/galene-0.4/rtpconn/rtpconn_test.go      2021-09-05 18:35:19.000000000 
+0200
+++ new/galene-0.4.1/rtpconn/rtpconn_test.go    2021-11-05 15:31:09.000000000 
+0100
@@ -37,4 +37,3 @@
                t.Errorf("Expected %v, got %v", info, info2)
        }
 }
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/rtpconn/webclient.go 
new/galene-0.4.1/rtpconn/webclient.go
--- old/galene-0.4/rtpconn/webclient.go 2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/rtpconn/webclient.go       2021-11-05 15:31:09.000000000 
+0100
@@ -56,7 +56,6 @@
        group       *group.Group
        id          string
        username    string
-       password    string
        permissions group.ClientPermissions
        status      map[string]interface{}
        requested   map[string][]string
@@ -83,18 +82,6 @@
        return c.username
 }
 
-func (c *webClient) Challenge(group string, creds group.ClientCredentials) 
bool {
-       if creds.Password == nil {
-               return true
-       }
-       m, err := creds.Password.Match(c.password)
-       if err != nil {
-               log.Printf("Password match: %v", err)
-               return false
-       }
-       return m
-}
-
 func (c *webClient) Permissions() group.ClientPermissions {
        return c.permissions
 }
@@ -124,7 +111,7 @@
        Password         string                   `json:"password,omitempty"`
        Privileged       bool                     `json:"privileged,omitempty"`
        Permissions      *group.ClientPermissions `json:"permissions,omitempty"`
-       Status           map[string]interface{}   `json:"status,omitempty"`
+       Status           interface{}              `json:"status,omitempty"`
        Group            string                   `json:"group,omitempty"`
        Value            interface{}              `json:"value,omitempty"`
        NoEcho           bool                     `json:"noecho,omitempty"`
@@ -211,9 +198,12 @@
                c.mu.Unlock()
                return os.ErrNotExist
        }
-       if userId != "" && conn.userId != userId {
-               c.mu.Unlock()
-               return ErrUserMismatch
+       if userId != "" {
+               id, _ := conn.User()
+               if id != userId {
+                       c.mu.Unlock()
+                       return ErrUserMismatch
+               }
        }
 
        replace := conn.getReplace(false)
@@ -365,7 +355,7 @@
                id = remoteTrack.track.Kind().String()
        }
        msid := remoteTrack.track.StreamID()
-       if msid == "" {
+       if msid == "" || msid == "-" {
                log.Println("Got track with empty msid")
                msid = remoteTrack.conn.Label()
        }
@@ -576,8 +566,6 @@
                return err
        }
 
-       up.userId = c.Id()
-       up.username = c.Username()
        if replace != "" {
                up.replace = replace
                delUpConn(c, replace, c.Id(), false)
@@ -825,21 +813,6 @@
        return nil
 }
 
-func getGroupStatus(g *group.Group) map[string]interface{} {
-       status := make(map[string]interface{})
-       if locked, message := g.Locked(); locked {
-               if message == "" {
-                       status["locked"] = true
-               } else {
-                       status["locked"] = message
-               }
-       }
-       if dn := g.DisplayName(); dn != "" {
-               status["displayName"] = dn
-       }
-       return status
-}
-
 func readMessage(conn *websocket.Conn, m *clientMessage) error {
        err := conn.SetReadDeadline(time.Now().Add(15 * time.Second))
        if err != nil {
@@ -1132,11 +1105,11 @@
                        Status:      a.status,
                })
        case joinedAction:
-               var status map[string]interface{}
+               var status interface{}
                if a.group != "" {
                        g := group.Get(a.group)
                        if g != nil {
-                               status = getGroupStatus(g)
+                               status = group.GetStatus(g, true)
                        }
                }
                perms := c.permissions
@@ -1161,7 +1134,7 @@
                        Group:            g.Name(),
                        Username:         c.username,
                        Permissions:      &perms,
-                       Status:           getGroupStatus(g),
+                       Status:           group.GetStatus(g, true),
                        RTCConfiguration: ice.ICEConfiguration(),
                })
                if !c.permissions.Present {
@@ -1277,7 +1250,7 @@
        switch perm {
        case "op":
                c.permissions.Op = true
-               if g.AllowRecording() {
+               if g.Description().AllowRecording {
                        c.permissions.Record = true
                }
        case "unop":
@@ -1342,8 +1315,12 @@
                        return group.ProtocolError("cannot join multiple 
groups")
                }
                c.username = m.Username
-               c.password = m.Password
-               g, err := group.AddClient(m.Group, c)
+               g, err := group.AddClient(m.Group, c,
+                       group.ClientCredentials{
+                               Username: m.Username,
+                               Password: m.Password,
+                       },
+               )
                if err != nil {
                        var s string
                        if os.IsNotExist(err) {
@@ -1351,6 +1328,8 @@
                        } else if err == group.ErrNotAuthorised {
                                s = "not authorised"
                                time.Sleep(200 * time.Millisecond)
+                       } else if err == group.ErrAnonymousNotAuthorised {
+                               s = "please choose a username"
                        } else if e, ok := err.(group.UserError); ok {
                                s = string(e)
                        } else {
@@ -1366,7 +1345,7 @@
                                Value:       s,
                        })
                }
-               if redirect := g.Redirect(); redirect != "" {
+               if redirect := g.Description().Redirect; redirect != "" {
                        // We normally redirect at the HTTP level, but the group
                        // description could have been edited in the meantime.
                        return c.write(clientMessage{
@@ -1580,7 +1559,11 @@
                                }
                        }
                        disk := diskwriter.New(g)
-                       _, err := group.AddClient(g.Name(), disk)
+                       _, err := group.AddClient(g.Name(), disk,
+                               group.ClientCredentials{
+                                       System: true,
+                               },
+                       )
                        if err != nil {
                                disk.Close()
                                return c.error(err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/static/galene.css 
new/galene-0.4.1/static/galene.css
--- old/galene-0.4/static/galene.css    2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/static/galene.css  2021-11-05 15:31:09.000000000 +0100
@@ -583,6 +583,7 @@
     position: relative;
     display: flex;
     justify-content: center;
+    overflow: scroll;
 }
 
 .login-box {
@@ -912,7 +913,7 @@
 }
 
 .media-failed {
-    opacity: 0.7;
+    filter: grayscale(0.5) contrast(0.5);
 }
 
 .mirror {
@@ -992,7 +993,7 @@
     padding: 10px;
     background: #fff;
     height: calc(100% - 56px);
-    overflow-y: scroll;
+    overflow-y: auto;
     overflow-x: hidden;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/static/galene.js 
new/galene-0.4.1/static/galene.js
--- old/galene-0.4/static/galene.js     2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/static/galene.js   2021-11-05 15:31:09.000000000 +0100
@@ -26,52 +26,11 @@
 /** @type {ServerConnection} */
 let serverConnection;
 
-/**
- * @typedef {Object} userpass
- * @property {string} username
- * @property {string} password
- */
-
-/* Some browsers disable session storage when cookies are disabled,
-   we fall back to a global variable. */
-/**
- * @type {userpass}
- */
-let fallbackUserPass = null;
+/** @type {Object} */
+let groupStatus = {};
 
-
-/**
- * @param {string} username
- * @param {string} password
- */
-function storeUserPass(username, password) {
-    let userpass = {username: username, password: password};
-    try {
-        window.sessionStorage.setItem('userpass', JSON.stringify(userpass));
-        fallbackUserPass = null;
-    } catch(e) {
-        console.warn("Couldn't store password:", e);
-        fallbackUserPass = userpass;
-    }
-}
-
-/**
- * Returns null if the user hasn't logged in yet.
- *
- * @returns {userpass}
- */
-function getUserPass() {
-    /** @type{userpass} */
-    let userpass;
-    try {
-        let json = window.sessionStorage.getItem('userpass');
-        userpass = JSON.parse(json);
-    } catch(e) {
-        console.warn("Couldn't retrieve password:", e);
-        userpass = fallbackUserPass;
-    }
-    return userpass || null;
-}
+/** @type {string} */
+let username = null;
 
 /**
  * @typedef {Object} settings
@@ -288,14 +247,6 @@
     scheduleReconsiderDownRate();
 }
 
-function fillLogin() {
-    let userpass = getUserPass();
-    getInputElement('username').value =
-        userpass ? userpass.username : '';
-    getInputElement('password').value =
-        userpass ? userpass.password : '';
-}
-
 /**
   * @param{boolean} connected
   */
@@ -311,7 +262,6 @@
             scheduleReconsiderDownRate();
         }
     } else {
-        fillLogin();
         userbox.classList.add('invisible');
         connectionbox.classList.remove('invisible');
         displayError('Disconnected', 'error');
@@ -322,9 +272,17 @@
 
 /** @this {ServerConnection} */
 function gotConnected() {
+    username = getInputElement('username').value.trim();
     setConnected(true);
-    let up = getUserPass();
-    this.join(group, up.username, up.password);
+    try {
+        let pw = getInputElement('password').value;
+        getInputElement('password').value = '';
+        this.join(group, username, pw);
+    } catch(e) {
+        console.error(e);
+        displayError(e);
+        serverConnection.close();
+    }
 }
 
 /**
@@ -2077,10 +2035,8 @@
 }
 
 function displayUsername() {
-    let userpass = getUserPass();
+    document.getElementById('userspan').textContent = username;
     let text = '';
-    if(userpass && userpass.username)
-        document.getElementById('userspan').textContent = userpass.username;
     if(serverConnection.permissions.op && serverConnection.permissions.present)
         text = '(op, presenter)';
     else if(serverConnection.permissions.op)
@@ -2111,7 +2067,8 @@
     }
     if(title)
         set(title);
-    set('Gal??ne');
+    else
+        set('Gal??ne');
 }
 
 
@@ -2142,6 +2099,7 @@
         return;
     case 'join':
     case 'change':
+        groupStatus = status;
         setTitle((status && status.displayName) || capitalise(group));
         displayUsername();
         setButtonsVisibility();
@@ -2986,10 +2944,7 @@
         return;
     connecting = true;
     try {
-        let username = getInputElement('username').value.trim();
-        let password = getInputElement('password').value;
-        storeUserPass(username, password);
-        serverConnect();
+        await serverConnect();
     } finally {
         connecting = false;
     }
@@ -3088,13 +3043,25 @@
     }
 }
 
-function start() {
-    group = decodeURIComponent(location.pathname.replace(/^\/[a-z]*\//, ''));
-    setTitle(capitalise(group));
+async function start() {
+    group = decodeURIComponent(
+        location.pathname.replace(/^\/[a-z]*\//, '').replace(/\/$/, '')
+    );
+    /** @type {Object} */
+    try {
+        let r = await fetch(".status.json")
+        if(!r.ok)
+            throw new Error(`${r.status} ${r.statusText}`);
+        groupStatus = await r.json()
+    } catch(e) {
+        console.error(e);
+        return;
+    }
+
+    setTitle(groupStatus.displayName || capitalise(group));
     addFilters();
     setMediaChoices(false).then(e => reflectSettings());
 
-    fillLogin();
     document.getElementById("login-container").classList.remove('invisible');
     setViewportHeight();
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/static/mainpage.js 
new/galene-0.4.1/static/mainpage.js
--- old/galene-0.4/static/mainpage.js   2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/static/mainpage.js 2021-11-05 15:31:09.000000000 +0100
@@ -24,7 +24,7 @@
     e.preventDefault();
     let group = document.getElementById('group').value.trim();
     if(group !== '')
-        location.href = '/group/' + group;
+        location.href = '/group/' + group + '/';
 };
 
 async function listPublicGroups() {
@@ -59,8 +59,8 @@
         let tr = document.createElement('tr');
         let td = document.createElement('td');
         let a = document.createElement('a');
-        a.textContent = group.name;
-        a.href = '/group/' + encodeURIComponent(group.displayName || 
group.name);
+        a.textContent = group.displayName || group.name;
+        a.href = '/group/' + group.name + '/';
         td.appendChild(a);
         tr.appendChild(td);
         let td2 = document.createElement('td');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/static/protocol.js 
new/galene-0.4.1/static/protocol.js
--- old/galene-0.4/static/protocol.js   2021-09-05 18:35:19.000000000 +0200
+++ new/galene-0.4.1/static/protocol.js 2021-11-05 15:31:09.000000000 +0100
@@ -677,6 +677,10 @@
         };
 
         c.pc.ontrack = function(e) {
+            if(e.streams.length < 1) {
+                console.error("Got track with no stream");
+                return;
+            }
             c.stream = e.streams[0];
             let changed = recomputeUserStreams(sc, source, c);
             if(c.ondowntrack) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/galene-0.4/webserver/webserver.go 
new/galene-0.4.1/webserver/webserver.go
--- old/galene-0.4/webserver/webserver.go       2021-09-05 18:35:19.000000000 
+0200
+++ new/galene-0.4.1/webserver/webserver.go     2021-11-05 15:31:09.000000000 
+0100
@@ -1,7 +1,6 @@
 package webserver
 
 import (
-       "bufio"
        "context"
        "crypto/tls"
        "encoding/json"
@@ -33,8 +32,6 @@
 
 var StaticRoot string
 
-var Redirect string
-
 var Insecure bool
 
 func Serve(address string, dataDir string) error {
@@ -70,10 +67,7 @@
                }
        }
        s.RegisterOnShutdown(func() {
-               group.Range(func(g *group.Group) bool {
-                       go g.Shutdown("server is shutting down")
-                       return true
-               })
+               group.Shutdown("server is shutting down")
        })
 
        server.Store(s)
@@ -134,13 +128,18 @@
 )
 
 func redirect(w http.ResponseWriter, r *http.Request) bool {
-       if Redirect == "" || strings.EqualFold(r.Host, Redirect) {
+       conf, err := group.GetConfiguration()
+       if err != nil || conf.CanonicalHost == "" {
+               return false
+       }
+
+       if strings.EqualFold(r.Host, conf.CanonicalHost) {
                return false
        }
 
        u := url.URL{
                Scheme: "https",
-               Host:   Redirect,
+               Host:   conf.CanonicalHost,
                Path:   r.URL.Path,
        }
        http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
@@ -265,6 +264,10 @@
                return ""
        }
 
+       if name[0] == '.' {
+               return ""
+       }
+
        if filepath.Separator != '/' &&
                strings.ContainsRune(name, filepath.Separator) {
                return ""
@@ -279,15 +282,49 @@
                return
        }
 
-       mungeHeader(w)
+       if strings.HasSuffix(r.URL.Path, "/.status.json") {
+               groupStatusHandler(w, r)
+               return
+       }
+
        name := parseGroupName("/group/", r.URL.Path)
        if name == "" {
                notFound(w)
                return
        }
 
-       if r.URL.Path != "/group/"+name {
-               http.Redirect(w, r, "/group/"+name, 
http.StatusPermanentRedirect)
+       g, err := group.Add(name, nil)
+       if err != nil {
+               if os.IsNotExist(err) {
+                       notFound(w)
+               } else {
+                       log.Printf("group.Add: %v", err)
+                       http.Error(w, "Internal server error",
+                               http.StatusInternalServerError)
+               }
+               return
+       }
+
+       if r.URL.Path != "/group/"+name+"/" {
+               http.Redirect(w, r, "/group/"+name+"/",
+                       http.StatusPermanentRedirect)
+               return
+       }
+
+       if redirect := g.Description().Redirect; redirect != "" {
+               http.Redirect(w, r, redirect, http.StatusPermanentRedirect)
+               return
+       }
+
+       mungeHeader(w)
+       serveFile(w, r, filepath.Join(StaticRoot, "galene.html"))
+}
+
+func groupStatusHandler(w http.ResponseWriter, r *http.Request) {
+       path := path.Dir(r.URL.Path)
+       name := parseGroupName("/group/", path)
+       if name == "" {
+               notFound(w)
                return
        }
 
@@ -296,19 +333,23 @@
                if os.IsNotExist(err) {
                        notFound(w)
                } else {
-                       log.Printf("addGroup: %v", err)
                        http.Error(w, "Internal server error",
                                http.StatusInternalServerError)
                }
                return
        }
 
-       if redirect := g.Redirect(); redirect != "" {
-               http.Redirect(w, r, redirect, http.StatusPermanentRedirect)
+       d := group.GetStatus(g, false)
+       w.Header().Set("content-type", "application/json")
+       w.Header().Set("cache-control", "no-cache")
+
+       if r.Method == "HEAD" {
                return
        }
 
-       serveFile(w, r, filepath.Join(StaticRoot, "galene.html"))
+       e := json.NewEncoder(w)
+       e.Encode(d)
+       return
 }
 
 func publicHandler(w http.ResponseWriter, r *http.Request) {
@@ -325,26 +366,20 @@
        return
 }
 
-func getPassword(dataDir string) (string, string, error) {
-       f, err := os.Open(filepath.Join(dataDir, "passwd"))
-       if err != nil {
-               return "", "", err
-       }
-       defer f.Close()
-
-       r := bufio.NewReader(f)
-
-       s, err := r.ReadString('\n')
+func adminMatch(username, password string) (bool, error) {
+       conf, err := group.GetConfiguration()
        if err != nil {
-               return "", "", err
+               return false, err
        }
 
-       l := strings.SplitN(strings.TrimSpace(s), ":", 2)
-       if len(l) != 2 {
-               return "", "", errors.New("couldn't parse passwords")
+       for _, cred := range conf.Admin {
+               if cred.Username == "" || cred.Username == username {
+                       if ok, _ := cred.Password.Match(password); ok {
+                               return true, nil
+                       }
+               }
        }
-
-       return l[0], l[1], nil
+       return false, nil
 }
 
 func failAuthentication(w http.ResponseWriter, realm string) {
@@ -354,15 +389,16 @@
 }
 
 func statsHandler(w http.ResponseWriter, r *http.Request, dataDir string) {
-       u, p, err := getPassword(dataDir)
-       if err != nil {
-               log.Printf("Passwd: %v", err)
+       username, password, ok := r.BasicAuth()
+       if !ok {
                failAuthentication(w, "stats")
                return
        }
 
-       username, password, ok := r.BasicAuth()
-       if !ok || username != u || password != p {
+       if ok, err := adminMatch(username, password); !ok {
+               if err != nil {
+                       log.Printf("Administrator password: %v", err)
+               }
                failAuthentication(w, "stats")
                return
        }
@@ -375,7 +411,7 @@
 
        ss := stats.GetGroups()
        e := json.NewEncoder(w)
-       err = e.Encode(ss)
+       err := e.Encode(ss)
        if err != nil {
                log.Printf("stats.json: %v", err)
        }
@@ -536,27 +572,6 @@
        }
 }
 
-type httpClient struct {
-       username string
-       password string
-}
-
-func (c httpClient) Username() string {
-       return c.username
-}
-
-func (c httpClient) Challenge(group string, creds group.ClientCredentials) 
bool {
-       if creds.Password == nil {
-               return true
-       }
-       m, err := creds.Password.Match(c.password)
-       if err != nil {
-               log.Printf("Password match: %v", err)
-               return false
-       }
-       return m
-}
-
 func checkGroupPermissions(w http.ResponseWriter, r *http.Request, groupname 
string) bool {
        desc, err := group.GetDescription(groupname)
        if err != nil {
@@ -568,7 +583,12 @@
                return false
        }
 
-       p, err := desc.GetPermission(groupname, httpClient{user, pass})
+       p, err := desc.GetPermission(groupname,
+               group.ClientCredentials{
+                       Username: user,
+                       Password: pass,
+               },
+       )
        if err != nil || !p.Record {
                if err == group.ErrNotAuthorised {
                        time.Sleep(200 * time.Millisecond)

++++++ vendor.tar.gz ++++++
++++ 14430 lines of diff (skipped)

Reply via email to