From 833d02b032fb8d4063d16660cb8952fb34a2013d Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 13 Jul 2021 19:02:16 +0100 Subject: [PATCH] matrix: end-to-end encryption by default Existing chats will remain unencrypted but new ones will be. --- LICENSE | 3 + api.go | 12 +-- config.go | 3 + config/config-base.json | 8 ++ email.go | 2 +- go.mod | 1 + go.sum | 48 +++++++++++- main.go | 3 +- matrix.go | 160 +++++++++++++++++++++++++++++++++------- storage.go | 34 ++++----- 10 files changed, 221 insertions(+), 53 deletions(-) diff --git a/LICENSE b/LICENSE index f3247cf..3d8b6da 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +---jfa-go--- + MIT License Copyright (c) 2021 Harvey Tindall @@ -19,3 +21,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/api.go b/api.go index ffaf2d9..f0afce1 100644 --- a/api.go +++ b/api.go @@ -2571,18 +2571,20 @@ func (app *appContext) MatrixConnect(gc *gin.Context) { if app.storage.matrix == nil { app.storage.matrix = map[string]MatrixUser{} } - roomID, err := app.matrix.CreateRoom(req.UserID) + roomID, encrypted, err := app.matrix.CreateRoom(req.UserID) if err != nil { app.err.Printf("Matrix: Failed to create room: %v", err) respondBool(500, false, gc) return } app.storage.matrix[req.JellyfinID] = MatrixUser{ - UserID: req.UserID, - RoomID: string(roomID), - Lang: "en-us", - Contact: true, + UserID: req.UserID, + RoomID: string(roomID), + Lang: "en-us", + Contact: true, + Encrypted: encrypted, } + app.matrix.isEncrypted[roomID] = encrypted if err := app.storage.storeMatrixUsers(); err != nil { app.err.Printf("Failed to store Matrix users: %v", err) respondBool(500, false, gc) diff --git a/config.go b/config.go index f1af63f..e3a8032 100644 --- a/config.go +++ b/config.go @@ -47,6 +47,9 @@ func (app *appContext) loadConfig() error { for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users", "announcements"} { app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json")))) } + for _, key := range []string{"matrix_sql"} { + app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db")))) + } app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/") app.config.Section("email").Key("no_username").SetValue(strconv.FormatBool(app.config.Section("email").Key("no_username").MustBool(false))) diff --git a/config/config-base.json b/config/config-base.json index 58a2558..3c4fb5b 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -1324,6 +1324,14 @@ "value": "", "description": "Stores matrix user IDs and language preferences." }, + "matrix_sql": { + "name": "Matrix encryption DB", + "required": false, + "requires_restart": false, + "type": "text", + "value": "", + "description": "Stores cryptographic material for Matrix end-to-end encryption." + }, "discord_users": { "name": "Discord users", "required": false, diff --git a/email.go b/email.go index d5b4492..76b9d92 100644 --- a/email.go +++ b/email.go @@ -819,7 +819,7 @@ func (app *appContext) sendByID(email *Message, ID ...string) error { } } if mxChat, ok := app.storage.matrix[id]; ok && mxChat.Contact && matrixEnabled { - err = app.matrix.Send(email, mxChat.RoomID) + err = app.matrix.Send(email, mxChat) if err != nil { return err } diff --git a/go.mod b/go.mod index 1f6ad2c..d4091d5 100644 --- a/go.mod +++ b/go.mod @@ -56,4 +56,5 @@ require ( google.golang.org/protobuf v1.25.0 // indirect gopkg.in/ini.v1 v1.62.0 maunium.net/go/mautrix v0.9.14 + modernc.org/sqlite v1.11.2 ) diff --git a/go.sum b/go.sum index ca96962..05a5030 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs= github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM= @@ -141,8 +143,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= @@ -173,6 +176,8 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -183,6 +188,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs= github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw= @@ -202,6 +208,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -223,6 +230,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -252,9 +261,13 @@ github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E= github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE= github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= @@ -338,6 +351,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -365,6 +380,7 @@ golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -413,6 +429,36 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclp gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= maunium.net/go/mautrix v0.9.14 h1:2MMJ630VM+xfa4Q5AooMAhPG1+wQnQybSr/z8PlRZ8A= maunium.net/go/mautrix v0.9.14/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8= +modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc= +modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk= +modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8= +modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E= +modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w= +modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A= +modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/tcl v1.5.5 h1:N03RwthgTR/l/eQvz3UjfYnvVVj1G2sZqzFGfoD4HE4= +modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc= +modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= diff --git a/main.go b/main.go index 736a660..2c47deb 100644 --- a/main.go +++ b/main.go @@ -652,7 +652,8 @@ func main() { Exit(r) } }() - defer logOutput()() + f := logOutput() + defer f() printVersion() SOCK = filepath.Join(temp, SOCK) fmt.Println("Socket:", SOCK) diff --git a/matrix.go b/matrix.go index a69755e..ce543bf 100644 --- a/matrix.go +++ b/matrix.go @@ -4,10 +4,15 @@ import ( "fmt" "strings" + "database/sql" + "github.com/gomarkdown/markdown" gomatrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" + + _ "modernc.org/sqlite" ) type MatrixDaemon struct { @@ -17,6 +22,10 @@ type MatrixDaemon struct { userID id.UserID tokens map[string]UnverifiedUser // Map of tokens to users languages map[id.RoomID]string // Map of roomIDs to language codes + isEncrypted map[id.RoomID]bool + cryptoStore *crypto.SQLCryptoStore + olm *crypto.OlmMachine + db *sql.DB app *appContext } @@ -26,18 +35,47 @@ type UnverifiedUser struct { } type MatrixUser struct { - RoomID string - UserID string - Lang string - Contact bool + RoomID string + Encrypted bool + UserID string + Lang string + Contact bool } -type MatrixIdentifier struct { - User string `json:"user"` - IdentType string `json:"type"` +func (m *MatrixDaemon) IsEncrypted(roomID id.RoomID) bool { + return m.isEncrypted[roomID] } -func (m MatrixIdentifier) Type() string { return m.IdentType } +func (m *MatrixDaemon) GetEncryptionEvent(roomID id.RoomID) *event.EncryptionEventContent { + return &event.EncryptionEventContent{ + Algorithm: id.AlgorithmMegolmV1, + RotationPeriodMillis: 7 * 24 * 60 * 60 * 1000, + RotationPeriodMessages: 100, + } +} + +// Users are assumed to only have one common channel with the bot, so we can stub this out. +func (m *MatrixDaemon) FindSharedRooms(userID id.UserID) []id.RoomID { return []id.RoomID{} } + +type olmLogger struct { + app *appContext +} + +func (o olmLogger) Error(message string, args ...interface{}) { + o.app.err.Printf("OLM: "+message+"\n", args) +} + +func (o olmLogger) Warn(message string, args ...interface{}) { + o.app.info.Printf("OLM: "+message+"\n", args) +} + +func (o olmLogger) Debug(message string, args ...interface{}) { + o.app.debug.Printf("OLM: "+message+"\n", args) +} + +func (o olmLogger) Trace(message string, args ...interface{}) { + o.app.debug.Printf("OLM [TRACE]: "+message+"\n", args) +} var matrixFilter = gomatrix.Filter{ Room: gomatrix.RoomFilter{ @@ -68,6 +106,7 @@ func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) { userID: id.UserID(matrix.Key("user_id").String()), tokens: map[string]UnverifiedUser{}, languages: map[id.RoomID]string{}, + isEncrypted: map[id.RoomID]bool{}, app: app, } d.bot, err = gomatrix.NewClient(homeserver, d.userID, token) @@ -80,6 +119,19 @@ func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) { if user.Lang != "" { d.languages[id.RoomID(user.RoomID)] = user.Lang } + d.isEncrypted[id.RoomID(user.RoomID)] = user.Encrypted + } + d.db, err = sql.Open("sqlite", app.config.Section("files").Key("matrix_sql").String()) + olmLog := &olmLogger{app} + d.cryptoStore = crypto.NewSQLCryptoStore(d.db, "sqlite3", "jfa-go", "jfa-go", []byte("jfa-go"), olmLog) + err = d.cryptoStore.CreateTables() + if err != nil { + return + } + d.olm = crypto.NewOlmMachine(d.bot, olmLog, d.cryptoStore, crypto.StateStore(d)) + err = d.olm.Load() + if err != nil { + return } return } @@ -108,7 +160,20 @@ func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string func (d *MatrixDaemon) run() { d.app.info.Println("Starting Matrix bot daemon") syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer) - syncer.OnEventType(event.NewEventType("m.room.message"), d.handleMessage) + syncer.OnEventType(event.EventMessage, d.handleMessage) + syncer.OnEventType(event.EventEncrypted, func(source gomatrix.EventSource, evt *event.Event) { + decrypted, err := d.olm.DecryptMegolmEvent(evt) + if err != nil { + d.app.err.Printf("Failed to decrypt Matrix message: %v", err) + return + } + d.handleMessage(source, decrypted) + }) + syncer.OnSync(d.olm.ProcessSyncResponse) + syncer.OnEventType(event.StateMember, func(source gomatrix.EventSource, evt *event.Event) { + d.olm.HandleMemberEvent(evt) + }) + // syncer.OnEventType("m.room.member", d.handleMembership) if err := d.bot.Sync(); err != nil { d.app.err.Printf("Matrix sync failed: %v", err) @@ -117,6 +182,7 @@ func (d *MatrixDaemon) run() { func (d *MatrixDaemon) Shutdown() { d.bot.StopSync() + d.db.Close() d.Stopped = true close(d.ShutdownChannel) } @@ -170,20 +236,32 @@ func (d *MatrixDaemon) commandLang(evt *event.Event, code, lang string) { } } -func (d *MatrixDaemon) CreateRoom(userID string) (id.RoomID, error) { - room, err := d.bot.CreateRoom(&gomatrix.ReqCreateRoom{ +func (d *MatrixDaemon) CreateRoom(userID string) (roomID id.RoomID, encrypted bool, err error) { + var room *gomatrix.RespCreateRoom + room, err = d.bot.CreateRoom(&gomatrix.ReqCreateRoom{ Visibility: "private", Invite: []id.UserID{id.UserID(userID)}, Topic: d.app.config.Section("matrix").Key("topic").String(), }) if err != nil { - return "", err + return } - return room.RoomID, nil + _, err = d.bot.SendStateEvent(room.RoomID, event.StateEncryption, "", &event.EncryptionEventContent{ + Algorithm: id.AlgorithmMegolmV1, + RotationPeriodMillis: 7 * 24 * 60 * 60 * 1000, + RotationPeriodMessages: 100, + }) + if err == nil { + encrypted = true + } else { + d.app.debug.Printf("Matrix: Failed to enable encryption in room: %v", err) + } + roomID = room.RoomID + return } func (d *MatrixDaemon) SendStart(userID string) (ok bool) { - roomID, err := d.CreateRoom(userID) + roomID, encrypted, err := d.CreateRoom(userID) if err != nil { d.app.err.Printf("Failed to create room for user \"%s\": %v", userID, err) return @@ -193,11 +271,13 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) { d.tokens[pin] = UnverifiedUser{ false, &MatrixUser{ - RoomID: string(roomID), - UserID: userID, - Lang: lang, + RoomID: string(roomID), + UserID: userID, + Lang: lang, + Encrypted: encrypted, }, } + d.isEncrypted[roomID] = encrypted _, err = d.bot.SendText( roomID, d.app.storage.lang.Telegram[lang].Strings.get("matrixStartMessage")+"\n\n"+pin+"\n\n"+ @@ -211,23 +291,25 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) { return } -func (d *MatrixDaemon) Send(message *Message, roomID ...string) (err error) { +func (d *MatrixDaemon) Send(message *Message, users ...MatrixUser) (err error) { md := "" if message.Markdown != "" { // Convert images to links md = string(markdown.ToHTML([]byte(strings.ReplaceAll(message.Markdown, "![", "[")), nil, renderer)) } - for _, ident := range roomID { - - if md != "" { - _, err = d.bot.SendMessageEvent(id.RoomID(ident), event.NewEventType("m.room.message"), map[string]interface{}{ - "msgtype": "m.text", - "body": message.Text, - "formatted_body": md, - "format": "org.matrix.custom.html", - }, gomatrix.ReqSendEvent{}) + content := event.MessageEventContent{ + MsgType: "m.text", + Body: message.Text, + } + if md != "" { + content.FormattedBody = md + content.Format = "org.matrix.custom.html" + } + for _, user := range users { + if user.Encrypted { + err = d.SendEncrypted(&content, user) } else { - _, err = d.bot.SendText(id.RoomID(ident), message.Text) + _, err = d.bot.SendMessageEvent(id.RoomID(user.RoomID), event.NewEventType("m.room.message"), content, gomatrix.ReqSendEvent{}) } if err != nil { return @@ -236,6 +318,28 @@ func (d *MatrixDaemon) Send(message *Message, roomID ...string) (err error) { return } +func (d *MatrixDaemon) SendEncrypted(content *event.MessageEventContent, users ...MatrixUser) (err error) { + for _, user := range users { + var encrypted *event.EncryptedEventContent + encrypted, err = d.olm.EncryptMegolmEvent(id.RoomID(user.RoomID), event.EventMessage, content) + if err == crypto.SessionExpired || err == crypto.SessionNotShared || err == crypto.NoGroupSession { + err = d.olm.ShareGroupSession(id.RoomID(user.RoomID), []id.UserID{id.UserID(user.UserID)}) + if err != nil { + return + } + encrypted, err = d.olm.EncryptMegolmEvent(id.RoomID(user.RoomID), event.EventMessage, content) + } + if err != nil { + return + } + _, err = d.bot.SendMessageEvent(id.RoomID(user.RoomID), event.EventEncrypted, encrypted) + if err != nil { + return + } + } + return +} + // User enters ID on sign-up, a PIN is sent to them. They enter it on sign-up. // Message the user first, to avoid E2EE by default diff --git a/storage.go b/storage.go index 9c3b777..3296f5c 100644 --- a/storage.go +++ b/storage.go @@ -15,23 +15,23 @@ import ( ) type Storage struct { - timePattern string - invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path string - users map[string]time.Time - invites Invites - profiles map[string]Profile - defaultProfile string - displayprefs, ombi_template map[string]interface{} - emails map[string]EmailAddress - telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users. - discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users. - matrix map[string]MatrixUser // Map of Jellyfin user IDs to Matrix users. - customEmails customEmails - policy mediabrowser.Policy - configuration mediabrowser.Configuration - lang Lang - announcements map[string]announcementTemplate - invitesLock, usersLock sync.Mutex + timePattern string + invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path string + users map[string]time.Time + invites Invites + profiles map[string]Profile + defaultProfile string + displayprefs, ombi_template map[string]interface{} + emails map[string]EmailAddress + telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users. + discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users. + matrix map[string]MatrixUser // Map of Jellyfin user IDs to Matrix users. + customEmails customEmails + policy mediabrowser.Policy + configuration mediabrowser.Configuration + lang Lang + announcements map[string]announcementTemplate + invitesLock, usersLock sync.Mutex } type TelegramUser struct {